Files
MapLibreParticleLab/include/mbgl/style/sources/raster_array_source.hpp
2026-09-11 09:00:29 +08:00

247 lines
9.7 KiB
C++

#pragma once
#include <mbgl/particle/field/offline_fixture_particle_adapter.hpp>
#include <mbgl/style/source.hpp>
#include <chrono>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <vector>
namespace mbgl {
class AsyncRequest;
namespace style {
// A Source-owned, preloaded validation candidate. Production runtimes can use
// updateRuntimeFieldWindow() directly with an already decoded snapshot instead.
struct RasterArrayRuntimeFieldDescriptor {
std::string fixtureID;
std::string metadataURL;
std::string fieldURL;
// Validation-only logical time in milliseconds. A production adapter owns
// its own timeline metadata; this keeps fixtures deterministic.
uint64_t timestamp = 0;
};
// A provider-neutral local feed pair. The source only transports the two
// resources; LocalParticleFieldFeed owns metadata decoding and validation.
struct RasterArrayLocalFieldDescriptor {
std::string metadataURL;
std::string fieldURL;
};
enum class RasterArrayPlaybackState : uint8_t {
Ready,
Playing,
Paused,
Waiting,
Ended
};
enum class RasterArrayTargetFps : uint8_t { Auto = 0, Fps30 = 30, Fps60 = 60 };
struct RasterArrayTimelineFrame {
std::shared_ptr<const particle::ParticleFieldSnapshot> t0;
std::shared_ptr<const particle::ParticleFieldSnapshot> t1;
float mix = 0.0f;
size_t t0Index = 0;
size_t t1Index = 0;
uint64_t windowRevision = 0;
uint64_t cadenceRevision = 0;
RasterArrayPlaybackState state = RasterArrayPlaybackState::Ready;
};
struct RasterArrayCadenceAdvance {
RasterArrayTimelineFrame frame;
bool advanced = false;
};
// Source-owned playback state. Android only sends low-frequency commands;
// render frames obtain an immutable T0/T1 pair and scalar mix from Core.
class RasterArrayTimelineRuntime {
public:
struct Entry {
std::shared_ptr<const particle::ParticleFieldSnapshot> field;
uint64_t timestamp = 0;
};
explicit RasterArrayTimelineRuntime(std::vector<Entry>);
bool append(Entry);
// Atomically claims one shared cadence deadline and advances wall-time
// playback at most once. Either weather renderer may consume it.
RasterArrayCadenceAdvance advanceIfDue(std::chrono::steady_clock::time_point);
RasterArrayTimelineFrame tick();
bool play();
bool pause();
bool setSpeed(float);
bool seek(size_t);
bool seekTimestamp(uint64_t);
bool step(int);
bool injectGap();
bool injectInvalid();
bool recover();
bool setTargetFps(RasterArrayTargetFps);
RasterArrayTargetFps targetFps() const;
bool cadenceDue(std::chrono::steady_clock::time_point) const;
void markCadenceTick(std::chrono::steady_clock::time_point);
RasterArrayTimelineFrame frame() const;
float playbackSpeed() const;
size_t size() const;
Entry lastEntry() const;
size_t cacheCapacity() const { return cacheCapacity_; }
private:
RasterArrayTimelineFrame frameLocked() const;
bool moveToLocked(size_t);
mutable std::mutex mutex;
std::vector<Entry> entries;
size_t t0 = 0;
float mix = 0.0f;
float speed = 1.0f;
RasterArrayPlaybackState state = RasterArrayPlaybackState::Ready;
bool gap = false;
bool invalid = false;
RasterArrayPlaybackState stateBeforeWait = RasterArrayPlaybackState::Paused;
uint64_t windowRevision = 1;
uint64_t cadenceRevision = 0;
std::chrono::steady_clock::time_point lastTick{};
std::chrono::steady_clock::time_point lastCadenceTick{};
std::chrono::steady_clock::time_point lastCadenceLog{};
uint64_t cadenceTickCount = 0;
RasterArrayTargetFps targetFps_ = RasterArrayTargetFps::Auto;
const size_t cacheCapacity_ = 8;
};
enum class RasterArrayRuntimeFieldUpdateResult : uint8_t {
Applied,
Noop,
RejectedNotLoaded,
RejectedInvalidSnapshot,
RejectedIncompatibleSnapshot,
RejectedNonMonotonicGeneration,
RejectedCandidateNotReady,
RejectedNoCandidate,
};
// Stable low-frequency Host -> Core submission result. Positive values are
// accepted outcomes, while negative values are structured fail-closed errors.
enum class RasterArrayHostFieldSubmitResult : int8_t {
RejectedSourceMode = -1,
RejectedAdapter = -2,
RejectedIncompatibleSnapshot = -3,
RejectedNonMonotonicSnapshot = -4,
RejectedCapacity = -5,
AcceptedPendingInitialWindow = 1,
InitialWindowReady = 2,
AcceptedNextSnapshot = 3,
Duplicate = 4,
};
const char* rasterArrayHostFieldSubmitResultName(RasterArrayHostFieldSubmitResult);
class RasterArraySource final : public Source {
public:
RasterArraySource(std::string id,
std::string fixtureID,
std::string metadataURL,
std::string fieldURL,
std::string nextFixtureID = {},
std::string nextMetadataURL = {},
std::string nextFieldURL = {},
std::vector<RasterArrayRuntimeFieldDescriptor> runtimeFields = {});
RasterArraySource(std::string id, std::vector<RasterArrayLocalFieldDescriptor> localFields);
// Creates an empty Source ingress. The Host submits one canonical Field at
// a time as metadata plus contiguous interleaved U/V bytes.
explicit RasterArraySource(std::string id);
~RasterArraySource() override;
class Impl;
const Impl& impl() const;
void loadDescription(FileSource&) override;
// Advances the authoritative Source time window from T0/T1 to T1/newT1.
// The caller transfers an immutable, already decoded Field snapshot; the
// renderer observes the resulting Source change and owns GPU reuse/upload.
RasterArrayRuntimeFieldUpdateResult updateRuntimeFieldWindow(particle::ParticleFieldSnapshot newT1);
// Thin Core-owned submit point for a producer-supplied initial window.
RasterArrayRuntimeFieldUpdateResult submitInitialRuntimeFieldWindow(particle::ParticleFieldSnapshot t0,
particle::ParticleFieldSnapshot t1);
// Validation convenience: advance using the next Source-owned preloaded
// candidate. Android supplies no T0/T1 state and no GPU resource here.
RasterArrayRuntimeFieldUpdateResult advanceRuntimeFieldWindow();
// Validation convenience: resubmit the current T1 through the formal
// update API. This must return Noop without changing the Source window.
RasterArrayRuntimeFieldUpdateResult repeatRuntimeFieldWindow();
RasterArrayHostFieldSubmitResult submitHostField(const std::string& metadataText, const std::vector<char>& payload);
// v2 ingress preserves explicit scalar/vector/validity plane presence.
RasterArrayHostFieldSubmitResult submitHostField(const std::string& metadataText,
const particle::ParticleFieldPayloadBundle& payloads);
// Clears producer-owned Host ingress state while retaining the Source and
// its Style layers for a subsequent T0/T1 submission.
bool clearHostFields();
// Producer-facing timestamp seek. Core alone selects T0/T1 and MIX.
bool seekRuntimeTimelineToTimestamp(uint64_t timestamp);
// Low-frequency JNI commands for the Source-owned validation timeline.
bool controlRuntimeTimeline(int command, float value = 0.0f);
bool setRuntimeTargetFps(uint8_t targetFps);
uint8_t runtimeTargetFps() const;
bool supportsLayerType(const LayerTypeInfo*) const override;
mapbox::base::WeakPtr<Source> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
protected:
Mutable<Source::Impl> createMutable() const noexcept override;
private:
RasterArrayHostFieldSubmitResult submitDecodedHostField(particle::ParticleFieldSnapshot snapshot);
struct RuntimeFieldCandidate {
RasterArrayRuntimeFieldDescriptor descriptor;
std::unique_ptr<AsyncRequest> metadataRequest;
std::unique_ptr<AsyncRequest> fieldRequest;
std::optional<std::string> metadata;
std::optional<std::vector<char>> fieldBytes;
std::optional<particle::ParticleFieldSnapshot> snapshot;
};
struct LocalFieldCandidate {
RasterArrayLocalFieldDescriptor descriptor;
std::unique_ptr<AsyncRequest> metadataRequest;
std::unique_ptr<AsyncRequest> fieldRequest;
std::optional<std::string> metadata;
std::optional<std::vector<char>> fieldBytes;
std::optional<particle::ParticleFieldSnapshot> snapshot;
};
void tryLoad();
void tryLoadRuntimeField(size_t);
void tryLoadLocalField(size_t);
void tryInitializeTimeline();
RasterArrayRuntimeFieldUpdateResult rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult,
const std::string& reason) const;
std::unique_ptr<AsyncRequest> metadataRequest;
std::unique_ptr<AsyncRequest> fieldRequest;
std::unique_ptr<AsyncRequest> nextMetadataRequest;
std::unique_ptr<AsyncRequest> nextFieldRequest;
std::optional<std::string> metadata;
std::optional<std::vector<char>> fieldBytes;
std::optional<std::string> nextMetadata;
std::optional<std::vector<char>> nextFieldBytes;
std::vector<RuntimeFieldCandidate> runtimeFields;
std::vector<LocalFieldCandidate> localFields;
std::optional<particle::ParticleFieldSnapshot> pendingHostField;
size_t nextRuntimeField = 0;
uint64_t runtimeUpdateSequence = 0;
std::shared_ptr<RasterArrayTimelineRuntime> timeline;
RasterArrayTargetFps targetFps = RasterArrayTargetFps::Auto;
mapbox::base::WeakPtrFactory<Source> weakFactory{this};
};
template <>
inline bool Source::is<RasterArraySource>() const {
return getType() == SourceType::RasterArray;
}
} // namespace style
} // namespace mbgl