#pragma once #include #include #include #include #include #include #include #include #include #include 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 t0; std::shared_ptr 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; }; // A backend-neutral cadence gate for renderer-owned visual simulation. Unlike // the legacy timeline it has no field entries and never advances forecast // time; Android/iOS may merely request a future MapLibre frame at its deadline. class RasterArrayRendererCadence final { public: bool consumeIfDue(std::chrono::steady_clock::time_point); bool cadenceDue(std::chrono::steady_clock::time_point) const; void setTargetFps(RasterArrayTargetFps); RasterArrayTargetFps targetFps() const; uint64_t revision() const; private: mutable std::mutex mutex; RasterArrayTargetFps targetFps_ = RasterArrayTargetFps::Auto; std::chrono::steady_clock::time_point lastTick{}; uint64_t revision_ = 0; }; // 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 field; uint64_t timestamp = 0; }; explicit RasterArrayTimelineRuntime(std::vector); 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 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); // Stable outcome for the isolated QBIN V4 Host ingress. This is deliberately // separate from the legacy V3 host-field result above: V4 does not populate a // renderer-facing T0/T1 timeline. enum class RasterArrayEncodedFieldSubmitResult : int8_t { RejectedDecode = -1, RejectedContract = -2, RejectedInvalidFieldKey = -3, RejectedIdentityConflict = -4, RejectedStaleRevision = -5, RejectedIncompatible = -6, RejectedCapacity = -7, AcceptedInserted = 1, AcceptedReplacedRevision = 2, Duplicate = 3, }; const char* rasterArrayEncodedFieldSubmitResultName(RasterArrayEncodedFieldSubmitResult); 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 runtimeFields = {}); RasterArraySource(std::string id, std::vector 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& 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(); // V4 QBIN ingress is a parallel, Core-only owner. It retains per-field // series and a requested forecast clock but never writes V3 T0/T1/MIX or // renderer snapshots. RasterArrayEncodedFieldSubmitResult submitEncodedField( const std::string& fieldKey, const particle::ingress::EncodedFieldRuntimeMetadata& runtimeMetadata, const particle::ResolvedFieldProfileContract& contract, const std::vector& rawQbinBytes); void setColorBinding(particle::ColorBinding); void setParticleBinding(particle::ParticleBinding); bool setRequestedForecastTime(uint64_t timestampMs); void setForecastPlaybackState(particle::ForecastPlaybackState); bool setForecastPlaybackSpeed(float); bool advanceForecastClock(uint64_t elapsedMs); // Called once per Map update on the owner thread; render layers only read // the resulting immutable projection and never advance this clock. bool tickForecastClock(std::chrono::steady_clock::time_point now); particle::ColorProjectionResult resolveColorProjection() const; particle::ParticleProjectionResult resolveParticleProjection() const; bool clearEncodedField(const std::string& fieldKey); void clearAllEncodedFields(); // Explicit terminal cleanup for an attached WeatherRuntime. Unlike an ordinary // V4 window clear, this also drops bindings, forecast clock and V3 host ingress. // Source and Style layers remain installed for the next runtime generation. bool resetWeatherRuntimeState(); // 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 makeWeakPtr() override { return weakFactory.makeWeakPtr(); } protected: Mutable createMutable() const noexcept override; private: RasterArrayHostFieldSubmitResult submitDecodedHostField(particle::ParticleFieldSnapshot snapshot); struct RuntimeFieldCandidate { RasterArrayRuntimeFieldDescriptor descriptor; std::unique_ptr metadataRequest; std::unique_ptr fieldRequest; std::optional metadata; std::optional> fieldBytes; std::optional snapshot; }; struct LocalFieldCandidate { RasterArrayLocalFieldDescriptor descriptor; std::unique_ptr metadataRequest; std::unique_ptr fieldRequest; std::optional metadata; std::optional> fieldBytes; std::optional snapshot; }; void tryLoad(); void tryLoadRuntimeField(size_t); void tryLoadLocalField(size_t); void tryInitializeTimeline(); // Re-resolve and materialize the independent V4 render view. This never // writes legacy timeline/field state. void refreshV4RendererState(); RasterArrayRuntimeFieldUpdateResult rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult, const std::string& reason) const; std::unique_ptr metadataRequest; std::unique_ptr fieldRequest; std::unique_ptr nextMetadataRequest; std::unique_ptr nextFieldRequest; std::optional metadata; std::optional> fieldBytes; std::optional nextMetadata; std::optional> nextFieldBytes; std::vector runtimeFields; std::vector localFields; std::optional pendingHostField; size_t nextRuntimeField = 0; uint64_t runtimeUpdateSequence = 0; std::shared_ptr timeline; RasterArrayTargetFps targetFps = RasterArrayTargetFps::Auto; // Deliberately independent from `timeline`: V4 bindings retain Host intent // across clear operations and resolve their own field-keyed windows. particle::EncodedFieldStore encodedFieldStore; particle::ForecastClock forecastClock; std::chrono::steady_clock::time_point lastForecastTick{}; // A Source change synchronously invokes Style::onUpdate, which re-enters // Map::onUpdate. Prevent a second forecast tick during the first notification. bool forecastTickInProgress = false; std::optional colorBinding; std::optional particleBinding; mapbox::base::WeakPtrFactory weakFactory{this}; }; template <> inline bool Source::is() const { return getType() == SourceType::RasterArray; } } // namespace style } // namespace mbgl