317 lines
13 KiB
C++
317 lines
13 KiB
C++
#pragma once
|
|
|
|
#include <mbgl/particle/field/field_binding.hpp>
|
|
#include <mbgl/particle/field/offline_fixture_particle_adapter.hpp>
|
|
#include <mbgl/particle/ingress/qbin_v2_decoder.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;
|
|
};
|
|
|
|
// 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<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);
|
|
|
|
// 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<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();
|
|
|
|
// 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<uint8_t>& 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);
|
|
particle::ColorProjectionResult resolveColorProjection() const;
|
|
particle::ParticleProjectionResult resolveParticleProjection() const;
|
|
bool clearEncodedField(const std::string& fieldKey);
|
|
void clearAllEncodedFields();
|
|
// 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();
|
|
// 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<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;
|
|
// 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::optional<particle::ColorBinding> colorBinding;
|
|
std::optional<particle::ParticleBinding> particleBinding;
|
|
mapbox::base::WeakPtrFactory<Source> weakFactory{this};
|
|
};
|
|
|
|
template <>
|
|
inline bool Source::is<RasterArraySource>() const {
|
|
return getType() == SourceType::RasterArray;
|
|
}
|
|
|
|
} // namespace style
|
|
} // namespace mbgl
|