249 lines
12 KiB
C++
249 lines
12 KiB
C++
#include <mbgl/particle/field/field_runtime.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <set>
|
|
|
|
namespace mbgl::particle {
|
|
namespace {
|
|
|
|
bool validProfileID(const std::string& value) {
|
|
if (value.empty() || value.size() > 64) return false;
|
|
return std::all_of(value.begin(), value.end(), [](const unsigned char c) {
|
|
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-';
|
|
});
|
|
}
|
|
|
|
bool newerRevision(const FrameContentIdentity& next, const FrameContentIdentity& current) {
|
|
if (next.generation != current.generation) return next.generation > current.generation;
|
|
if (next.dataVersion != current.dataVersion) return next.dataVersion > current.dataVersion;
|
|
return false;
|
|
}
|
|
|
|
bool samePlaneSchema(const DecodedFieldPlane& a, const DecodedFieldPlane& b) {
|
|
return a.key == b.key && a.dtype == b.dtype && a.valueClass == b.valueClass && a.unitID == b.unitID &&
|
|
a.key.component == b.key.component && a.count == b.count && a.scale == b.scale && a.offset == b.offset;
|
|
}
|
|
|
|
bool validValueClass(const FieldValueClass value) {
|
|
return value <= FieldValueClass::Validity;
|
|
}
|
|
|
|
bool validComponent(const FieldComponent value) {
|
|
return value <= FieldComponent::WUp;
|
|
}
|
|
|
|
bool validDType(const FieldDType value) {
|
|
return value >= FieldDType::Int16 && value <= FieldDType::Bitset;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool ResolvedFieldProfileContract::isStructurallyValid() const {
|
|
if (contractSchemaVersion != 1 || !validProfileID(profileID) || profileVersion == 0 || requiredPlanes.empty() ||
|
|
timeRelation > FieldTimeRelation::ValidEqReference)
|
|
return false;
|
|
std::set<std::pair<uint16_t, uint16_t>> keys;
|
|
for (const auto& plane : requiredPlanes) {
|
|
if (!plane.requiredFlag || plane.semanticID == 0 || !validValueClass(plane.valueClass) ||
|
|
!validComponent(plane.component)) return false;
|
|
const auto key = std::make_pair(plane.semanticID, plane.groupID);
|
|
if (!keys.insert(key).second) return false;
|
|
if (plane.valueClass == FieldValueClass::VelocityComponent &&
|
|
(plane.groupID == 0 || plane.component == FieldComponent::None)) return false;
|
|
if (plane.valueClass == FieldValueClass::Validity &&
|
|
(plane.groupID != 0 || plane.component != FieldComponent::None)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FrameContentIdentity::isValid() const {
|
|
return !fieldID.empty() && generation > 0 && validTimeMs > 0;
|
|
}
|
|
|
|
bool FrameContentIdentity::operator==(const FrameContentIdentity& other) const {
|
|
return fieldID == other.fieldID && generation == other.generation && dataVersion == other.dataVersion &&
|
|
validTimeMs == other.validTimeMs;
|
|
}
|
|
|
|
bool FieldPlaneKey::operator==(const FieldPlaneKey& other) const {
|
|
return semanticID == other.semanticID && groupID == other.groupID;
|
|
}
|
|
|
|
bool FieldPlaneKey::operator<(const FieldPlaneKey& other) const {
|
|
if (semanticID != other.semanticID) return semanticID < other.semanticID;
|
|
return groupID < other.groupID;
|
|
}
|
|
|
|
bool DecodedFieldPlane::isStructurallyValid(const uint64_t cellCount) const {
|
|
return key.semanticID > 0 && validDType(dtype) && validValueClass(valueClass) && validComponent(key.component) &&
|
|
count == cellCount && std::isfinite(scale) && scale > 0.0f && std::isfinite(offset) &&
|
|
!contentIdentity.empty() && storage && !storage->empty();
|
|
}
|
|
|
|
bool DecodedFieldPlane::isInterpolationCompatibleWith(const DecodedFieldPlane& other) const {
|
|
return samePlaneSchema(*this, other);
|
|
}
|
|
|
|
bool DecodedFieldGrid::isValid() const {
|
|
return width > 0 && height > 0 && std::isfinite(west) && std::isfinite(south) && std::isfinite(east) &&
|
|
std::isfinite(north) && west < east && south < north;
|
|
}
|
|
|
|
bool DecodedFieldGrid::operator==(const DecodedFieldGrid& other) const {
|
|
return layout == other.layout && projection == other.projection && sampleOrder == other.sampleOrder &&
|
|
gridRegistration == other.gridRegistration && width == other.width && height == other.height &&
|
|
west == other.west && south == other.south && east == other.east && north == other.north;
|
|
}
|
|
|
|
bool DecodedFieldFrame::isStructurallyValid() const {
|
|
if (!frameContentIdentity.isValid() || referenceTimeMs == 0 || !grid.isValid() || !validityBits ||
|
|
validityBits->empty() || validityContentIdentity.empty()) return false;
|
|
const uint64_t cells = static_cast<uint64_t>(grid.width) * grid.height;
|
|
const uint64_t expectedValidityBytes = (cells + 7u) / 8u;
|
|
if (validityBits->size() != expectedValidityBytes) return false;
|
|
if (!validityPlane.isStructurallyValid(cells) || validityPlane.valueClass != FieldValueClass::Validity ||
|
|
validityPlane.dtype != FieldDType::Bitset || validityPlane.unitID != FieldUnitNone ||
|
|
validityPlane.key.groupID != 0 || validityPlane.key.component != FieldComponent::None ||
|
|
!validityPlane.requiredFlag || validityPlane.contentIdentity != validityContentIdentity ||
|
|
validityPlane.storage != validityBits)
|
|
return false;
|
|
std::set<FieldPlaneKey> keys;
|
|
if (!keys.insert(validityPlane.key).second) return false;
|
|
for (const auto& plane : planes) {
|
|
if (!plane.isStructurallyValid(cells) || plane.valueClass == FieldValueClass::Validity ||
|
|
!keys.insert(plane.key).second)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DecodedFieldFrame::isInterpolationCompatibleWith(const DecodedFieldFrame& other) const {
|
|
if (!(grid == other.grid) || !validityPlane.isInterpolationCompatibleWith(other.validityPlane) ||
|
|
planes.size() != other.planes.size()) return false;
|
|
for (size_t i = 0; i < planes.size(); ++i) {
|
|
if (!planes[i].isInterpolationCompatibleWith(other.planes[i])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DecodedFieldFrame::hasSameContentAs(const DecodedFieldFrame& other) const {
|
|
if (referenceTimeMs != other.referenceTimeMs || !(grid == other.grid) ||
|
|
validityContentIdentity != other.validityContentIdentity ||
|
|
!samePlaneSchema(validityPlane, other.validityPlane) || planes.size() != other.planes.size()) return false;
|
|
for (size_t i = 0; i < planes.size(); ++i) {
|
|
if (!samePlaneSchema(planes[i], other.planes[i]) || planes[i].contentIdentity != other.planes[i].contentIdentity)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FieldSeries::FieldSeries(const size_t capacity) : capacity_(capacity) {}
|
|
|
|
FieldSeriesSubmitResult FieldSeries::submit(std::shared_ptr<const DecodedFieldFrame> frame) {
|
|
if (!frame || !frame->isStructurallyValid()) return FieldSeriesSubmitResult::RejectedInvalidFrame;
|
|
const auto timestamp = frame->frameContentIdentity.validTimeMs;
|
|
auto it = std::lower_bound(frames.begin(), frames.end(), timestamp,
|
|
[](const auto& item, const uint64_t value) {
|
|
return item->frameContentIdentity.validTimeMs < value;
|
|
});
|
|
if (it != frames.end() && (*it)->frameContentIdentity.validTimeMs == timestamp) {
|
|
const auto& current = **it;
|
|
if (frame->frameContentIdentity == current.frameContentIdentity) {
|
|
return frame->hasSameContentAs(current) ? FieldSeriesSubmitResult::Duplicate
|
|
: FieldSeriesSubmitResult::RejectedIdentityConflict;
|
|
}
|
|
if (!newerRevision(frame->frameContentIdentity, current.frameContentIdentity))
|
|
return FieldSeriesSubmitResult::RejectedStaleRevision;
|
|
if (!frame->isInterpolationCompatibleWith(current))
|
|
return FieldSeriesSubmitResult::RejectedIncompatibleFrame;
|
|
if (it != frames.begin() && !frame->isInterpolationCompatibleWith(**std::prev(it)))
|
|
return FieldSeriesSubmitResult::RejectedIncompatibleFrame;
|
|
if (std::next(it) != frames.end() && !frame->isInterpolationCompatibleWith(**std::next(it)))
|
|
return FieldSeriesSubmitResult::RejectedIncompatibleFrame;
|
|
*it = std::move(frame);
|
|
return FieldSeriesSubmitResult::ReplacedRevision;
|
|
}
|
|
if (frames.size() >= capacity_) return FieldSeriesSubmitResult::RejectedCapacity;
|
|
if (it != frames.begin() && !frame->isInterpolationCompatibleWith(**std::prev(it)))
|
|
return FieldSeriesSubmitResult::RejectedIncompatibleFrame;
|
|
if (it != frames.end() && !frame->isInterpolationCompatibleWith(**it))
|
|
return FieldSeriesSubmitResult::RejectedIncompatibleFrame;
|
|
frames.insert(it, std::move(frame));
|
|
return FieldSeriesSubmitResult::Inserted;
|
|
}
|
|
|
|
std::optional<FieldSeriesWindow> FieldSeries::resolve(const uint64_t requestedForecastTimeMs) const {
|
|
if (frames.size() < 2 || requestedForecastTimeMs < frames.front()->frameContentIdentity.validTimeMs ||
|
|
requestedForecastTimeMs > frames.back()->frameContentIdentity.validTimeMs) return std::nullopt;
|
|
if (requestedForecastTimeMs == frames.back()->frameContentIdentity.validTimeMs) {
|
|
return FieldSeriesWindow{frames[frames.size() - 2], frames.back(), 1.0f, requestedForecastTimeMs};
|
|
}
|
|
const auto upper = std::upper_bound(frames.begin(), frames.end(), requestedForecastTimeMs,
|
|
[](const uint64_t value, const auto& item) {
|
|
return value < item->frameContentIdentity.validTimeMs;
|
|
});
|
|
if (upper == frames.begin() || upper == frames.end()) return std::nullopt;
|
|
const auto lower = std::prev(upper);
|
|
const uint64_t t0 = (*lower)->frameContentIdentity.validTimeMs;
|
|
const uint64_t t1 = (*upper)->frameContentIdentity.validTimeMs;
|
|
if (t1 <= t0 || !(*lower)->isInterpolationCompatibleWith(**upper)) return std::nullopt;
|
|
const double ratio = static_cast<double>(requestedForecastTimeMs - t0) / static_cast<double>(t1 - t0);
|
|
return FieldSeriesWindow{*lower, *upper, static_cast<float>(std::clamp(ratio, 0.0, 1.0)), requestedForecastTimeMs};
|
|
}
|
|
|
|
bool ForecastClock::setRequestedForecastTime(const uint64_t timestampMs) {
|
|
if (timestampMs == 0) return false;
|
|
requestedForecastTimeMs_ = timestampMs;
|
|
fractionalHalfMillisecond_ = 0;
|
|
return true;
|
|
}
|
|
|
|
bool ForecastClock::setPlaybackSpeed(const float value) {
|
|
if (value != 0.5f && value != 1.0f && value != 2.0f) return false;
|
|
playbackSpeed_ = value;
|
|
return true;
|
|
}
|
|
|
|
bool ForecastClock::advance(const uint64_t elapsedMs) {
|
|
if (state_ != ForecastPlaybackState::Playing || requestedForecastTimeMs_ == 0) return false;
|
|
const uint64_t halfMillisecondsPerElapsedMs = playbackSpeed_ == 0.5f ? 1 : playbackSpeed_ == 1.0f ? 2 : 4;
|
|
if (elapsedMs > std::numeric_limits<uint64_t>::max() / halfMillisecondsPerElapsedMs) return false;
|
|
const uint64_t elapsedHalfMilliseconds = elapsedMs * halfMillisecondsPerElapsedMs;
|
|
if (elapsedHalfMilliseconds > std::numeric_limits<uint64_t>::max() - fractionalHalfMillisecond_) return false;
|
|
const uint64_t accumulatedHalfMilliseconds = elapsedHalfMilliseconds + fractionalHalfMillisecond_;
|
|
const uint64_t wholeMilliseconds = accumulatedHalfMilliseconds / 2;
|
|
if (wholeMilliseconds > std::numeric_limits<uint64_t>::max() - requestedForecastTimeMs_) return false;
|
|
requestedForecastTimeMs_ += wholeMilliseconds;
|
|
fractionalHalfMillisecond_ = static_cast<uint8_t>(accumulatedHalfMilliseconds % 2);
|
|
return true;
|
|
}
|
|
|
|
FieldSeriesSubmitResult EncodedFieldStore::submit(const std::string& fieldKey,
|
|
std::shared_ptr<const DecodedFieldFrame> frame) {
|
|
if (fieldKey.empty()) return FieldSeriesSubmitResult::RejectedInvalidFieldKey;
|
|
auto it = series.find(fieldKey);
|
|
if (it == series.end()) it = series.emplace(fieldKey, FieldSeries{perSeriesCapacity_}).first;
|
|
const auto result = it->second.submit(std::move(frame));
|
|
if (result == FieldSeriesSubmitResult::RejectedInvalidFrame && it->second.size() == 0) series.erase(it);
|
|
return result;
|
|
}
|
|
|
|
std::optional<FieldSeriesWindow> EncodedFieldStore::resolve(const std::string& fieldKey,
|
|
const uint64_t requestedForecastTimeMs) const {
|
|
const auto* found = find(fieldKey);
|
|
return found ? found->resolve(requestedForecastTimeMs) : std::nullopt;
|
|
}
|
|
|
|
const FieldSeries* EncodedFieldStore::find(const std::string& fieldKey) const {
|
|
const auto it = series.find(fieldKey);
|
|
return it == series.end() ? nullptr : &it->second;
|
|
}
|
|
|
|
bool EncodedFieldStore::clear(const std::string& fieldKey) {
|
|
return series.erase(fieldKey) != 0;
|
|
}
|
|
|
|
} // namespace mbgl::particle
|