Files
MapLibreParticleLab/include/mbgl/particle/field/field_runtime.hpp

178 lines
5.7 KiB
C++

#pragma once
#include <mbgl/particle/field/field_profile_contract.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace mbgl::particle {
enum class FieldDType : uint8_t {
Int16 = 1,
UInt16 = 2,
UInt8 = 3,
Int8 = 4,
Float32 = 5,
Bitset = 6,
};
struct FrameContentIdentity {
std::string fieldID;
uint64_t generation = 0;
uint64_t dataVersion = 0;
uint64_t validTimeMs = 0;
bool isValid() const;
bool operator==(const FrameContentIdentity&) const;
bool operator!=(const FrameContentIdentity& other) const { return !(*this == other); }
};
struct FieldPlaneKey {
uint16_t semanticID = 0;
uint16_t groupID = 0;
// Component is a plane descriptor property, not part of the QBIN V2
// plane identity. A Field may contain only one (semanticID, groupID).
FieldComponent component = FieldComponent::None;
bool operator==(const FieldPlaneKey&) const;
bool operator<(const FieldPlaneKey&) const;
};
struct DecodedFieldPlane {
FieldPlaneKey key;
FieldDType dtype = FieldDType::Float32;
FieldValueClass valueClass = FieldValueClass::Scalar;
uint16_t unitID = 0;
// REQUIRED is transport metadata retained by the provider-neutral runtime.
bool requiredFlag = false;
uint32_t count = 0;
float scale = 1.0f;
float offset = 0.0f;
std::string contentIdentity;
std::shared_ptr<const std::vector<uint8_t>> storage;
bool isStructurallyValid(uint64_t cellCount) const;
bool isInterpolationCompatibleWith(const DecodedFieldPlane&) const;
};
struct DecodedFieldGrid {
uint8_t layout = 1;
uint8_t projection = 0;
uint8_t sampleOrder = 0;
uint8_t gridRegistration = 0;
uint32_t width = 0;
uint32_t height = 0;
double west = -180.0;
double south = -85.0;
double east = 180.0;
double north = 85.0;
bool isValid() const;
bool operator==(const DecodedFieldGrid&) const;
};
struct DecodedFieldFrame {
FrameContentIdentity frameContentIdentity;
uint64_t referenceTimeMs = 0;
// Validation provenance retained so a stored frame cannot be interpolated
// across two otherwise byte-compatible but semantically different profiles.
std::string resolvedProfileID;
uint32_t resolvedProfileVersion = 0;
DecodedFieldGrid grid;
// One frame-wide validity domain. The descriptor and payload share storage;
// projections can retain the same payload without copying it.
DecodedFieldPlane validityPlane;
std::string validityContentIdentity;
std::shared_ptr<const std::vector<uint8_t>> validityBits;
std::vector<DecodedFieldPlane> planes;
bool isStructurallyValid() const;
bool isSeriesCompatibleWith(const DecodedFieldFrame&) const;
bool isInterpolationCompatibleWith(const DecodedFieldFrame&) const;
bool hasSameContentAs(const DecodedFieldFrame&) const;
// Raw decoded plane payload plus the shared validity bitset; excludes
// separately materialized CPU snapshots and renderer/GPU resources.
uint64_t decodedByteSize() const;
};
enum class FieldSeriesSubmitResult : uint8_t {
Inserted,
ReplacedRevision,
Duplicate,
RejectedInvalidFieldKey,
RejectedInvalidFrame,
RejectedIdentityConflict,
RejectedStaleRevision,
RejectedIncompatibleFrame,
RejectedCapacity,
};
struct FieldSeriesWindow {
std::shared_ptr<const DecodedFieldFrame> t0;
std::shared_ptr<const DecodedFieldFrame> t1;
float mix = 0.0f;
uint64_t requestedForecastTimeMs = 0;
};
class FieldSeries final {
public:
explicit FieldSeries(size_t capacity = 8);
FieldSeriesSubmitResult submit(std::shared_ptr<const DecodedFieldFrame>);
std::optional<FieldSeriesWindow> resolve(uint64_t requestedForecastTimeMs) const;
size_t size() const { return frames.size(); }
size_t capacity() const { return capacity_; }
uint64_t decodedByteSize() const;
const std::vector<std::shared_ptr<const DecodedFieldFrame>>& entries() const { return frames; }
private:
size_t capacity_;
std::vector<std::shared_ptr<const DecodedFieldFrame>> frames;
};
enum class ForecastPlaybackState : uint8_t { Paused, Playing };
class ForecastClock final {
public:
bool setRequestedForecastTime(uint64_t timestampMs);
bool setPlaybackSpeed(float value);
void play() { state_ = ForecastPlaybackState::Playing; }
void pause() { state_ = ForecastPlaybackState::Paused; }
bool advance(uint64_t elapsedMs);
uint64_t requestedForecastTime() const { return requestedForecastTimeMs_; }
float playbackSpeed() const { return playbackSpeed_; }
ForecastPlaybackState playbackState() const { return state_; }
private:
uint64_t requestedForecastTimeMs_ = 0;
// The supported playback rates are exact multiples of half a millisecond.
// Keep the sub-millisecond half-unit so short HALF ticks do not round up.
uint8_t fractionalHalfMillisecond_ = 0;
float playbackSpeed_ = 1.0f;
ForecastPlaybackState state_ = ForecastPlaybackState::Paused;
};
class EncodedFieldStore final {
public:
explicit EncodedFieldStore(size_t perSeriesCapacity = 8) : perSeriesCapacity_(perSeriesCapacity) {}
FieldSeriesSubmitResult submit(const std::string& fieldKey, std::shared_ptr<const DecodedFieldFrame>);
std::optional<FieldSeriesWindow> resolve(const std::string& fieldKey, uint64_t requestedForecastTimeMs) const;
const FieldSeries* find(const std::string& fieldKey) const;
bool clear(const std::string& fieldKey);
void clearAll() { series.clear(); }
size_t fieldCount() const { return series.size(); }
uint64_t decodedByteSize() const;
private:
size_t perSeriesCapacity_;
std::unordered_map<std::string, FieldSeries> series;
};
} // namespace mbgl::particle