feat: add production-like particle field feed

This commit is contained in:
OpenAI Codex
2026-09-04 14:51:42 +08:00
parent 885e7aebef
commit 7c679f09ac
23 changed files with 545 additions and 26 deletions

View File

@@ -1,6 +1,7 @@
#include <mbgl/style/sources/raster_array_source.hpp>
#include <mbgl/particle/field/offline_fixture_particle_adapter.hpp>
#include <mbgl/particle/field/local_particle_field_feed.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/style/source_observer.hpp>
@@ -203,6 +204,9 @@ RasterArraySource::RasterArraySource(std::string id,
std::move(nextFieldURL),
std::move(runtimeFields))) {}
RasterArraySource::RasterArraySource(std::string id, std::vector<RasterArrayLocalFieldDescriptor> localFields)
: Source(makeMutable<Impl>(std::move(id), std::move(localFields))) {}
RasterArraySource::~RasterArraySource() {
weakFactory.invalidateWeakPtrs();
}
@@ -213,6 +217,39 @@ const RasterArraySource::Impl& RasterArraySource::impl() const {
void RasterArraySource::loadDescription(FileSource& fileSource) {
if (metadataRequest || fieldRequest) return;
if (!impl().localFields.empty()) {
localFields.reserve(impl().localFields.size());
for (const auto& descriptor : impl().localFields) {
LocalFieldCandidate candidate;
candidate.descriptor = descriptor;
localFields.push_back(std::move(candidate));
const auto index = localFields.size() - 1;
auto& local = localFields.back();
local.metadataRequest = fileSource.request(Resource::source(local.descriptor.metadataURL),
[this, self = makeWeakPtr(), index](const Response& response) {
if (auto guard = self.lock(); self) {
if (response.error || response.noContent || !response.data) {
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error("particle local metadata request failed")));
return;
}
localFields[index].metadata = *response.data;
tryLoadLocalField(index);
}
});
local.fieldRequest = fileSource.request(Resource::source(local.descriptor.fieldURL),
[this, self = makeWeakPtr(), index](const Response& response) {
if (auto guard = self.lock(); self) {
if (response.error || response.noContent || !response.data) {
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error("particle local field request failed")));
return;
}
localFields[index].fieldBytes = std::vector<char>(response.data->begin(), response.data->end());
tryLoadLocalField(index);
}
});
}
return;
}
metadataRequest = fileSource.request(
Resource::source(impl().metadataURL), [this, self = makeWeakPtr()](const Response& response) {
if (auto guard = self.lock(); self) {
@@ -371,17 +408,68 @@ void RasterArraySource::tryLoadRuntimeField(const size_t index) {
}
}
void RasterArraySource::tryLoadLocalField(const size_t index) {
auto& candidate = localFields.at(index);
if (candidate.snapshot || !candidate.metadata || !candidate.fieldBytes) return;
const auto result = particle::LocalParticleFieldFeed::load(*candidate.metadata, *candidate.fieldBytes);
if (!result) {
const auto fieldID = candidate.metadata->find("fieldId") == std::string::npos ? "" : "metadata";
Log::Warning(Event::Render, "ParticleField: event=ADAPTER_REJECTED sourceId=" + getID() +
" errorCode=" + particle::particleFieldAdapterErrorName(result.error) +
" fieldId=" + fieldID + " payloadBytes=" + std::to_string(candidate.fieldBytes->size()) +
" error=" + result.message);
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(result.message)));
return;
}
candidate.snapshot = std::move(*result.snapshot);
Log::Info(Event::Render, "ParticleField: event=PRODUCTION_FEED_LOAD fieldId=" + candidate.snapshot->fieldID +
" generation=" + std::to_string(candidate.snapshot->generation) +
" validTime=" + std::to_string(candidate.snapshot->validTime) +
" payloadBytes=" + std::to_string(candidate.fieldBytes->size()));
tryInitializeTimeline();
}
void RasterArraySource::tryInitializeTimeline() {
if (timeline || !loaded || !impl().field || !impl().nextField || runtimeFields.empty()) return;
if (timeline) return;
if (!impl().localFields.empty()) {
if (localFields.size() != impl().localFields.size() || localFields.size() < 2) return;
for (const auto& candidate : localFields) if (!candidate.snapshot) return;
std::vector<RasterArrayTimelineRuntime::Entry> entries;
entries.reserve(localFields.size());
for (const auto& candidate : localFields) {
entries.push_back({std::make_shared<particle::ParticleFieldSnapshot>(*candidate.snapshot), candidate.snapshot->validTime});
}
try {
if (submitInitialRuntimeFieldWindow(*localFields[0].snapshot, *localFields[1].snapshot) !=
RasterArrayRuntimeFieldUpdateResult::Applied) {
return;
}
timeline = std::make_shared<RasterArrayTimelineRuntime>(std::move(entries));
auto updated = makeMutable<Impl>(impl());
updated->timeline = timeline;
baseImpl = std::move(updated);
loaded = true;
Log::Info(Event::Render, "ParticleTimeline: event=READY snapshots=" + std::to_string(localFields.size()));
observer->onSourceLoaded(*this);
observer->onSourceChanged(*this);
} catch (const std::exception& error) {
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(error.what())));
}
return;
}
if (!loaded || !impl().field || !impl().nextField || runtimeFields.empty()) return;
std::vector<RasterArrayTimelineRuntime::Entry> entries;
entries.reserve(runtimeFields.size() + 2);
entries.push_back({std::make_shared<particle::ParticleFieldSnapshot>(*impl().field), 0});
entries.push_back({std::make_shared<particle::ParticleFieldSnapshot>(*impl().nextField), 40000});
entries.push_back({std::make_shared<particle::ParticleFieldSnapshot>(*impl().field),
impl().field->validTime ? impl().field->validTime : 0});
entries.push_back({std::make_shared<particle::ParticleFieldSnapshot>(*impl().nextField),
impl().nextField->validTime ? impl().nextField->validTime : 40000});
for (size_t index = 0; index < runtimeFields.size(); ++index) {
if (!runtimeFields[index].snapshot) return;
auto field = *runtimeFields[index].snapshot;
entries.push_back({std::make_shared<particle::ParticleFieldSnapshot>(std::move(field)),
80000 + static_cast<uint64_t>(index) * 40000});
runtimeFields[index].descriptor.timestamp ? runtimeFields[index].descriptor.timestamp
: 80000 + static_cast<uint64_t>(index) * 40000});
}
try {
timeline = std::make_shared<RasterArrayTimelineRuntime>(std::move(entries));
@@ -403,6 +491,19 @@ void RasterArraySource::tryInitializeTimeline() {
}
}
RasterArrayRuntimeFieldUpdateResult RasterArraySource::submitInitialRuntimeFieldWindow(
particle::ParticleFieldSnapshot t0, particle::ParticleFieldSnapshot t1) {
if (loaded || !t0.isValid() || !t1.isValid() || t0.generation == 0 || t1.generation == 0 ||
t0.validTime >= t1.validTime || !t0.isTimeInterpolationCompatibleWith(t1)) {
return rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult::RejectedInvalidSnapshot,
"INVALID_INITIAL_WINDOW");
}
baseImpl = makeMutable<Impl>(impl(), std::move(t0), std::move(t1));
loaded = true;
Log::Info(Event::Render, "ParticleField: event=SNAPSHOT_SUBMIT kind=INITIAL_T0_T1");
return RasterArrayRuntimeFieldUpdateResult::Applied;
}
RasterArrayRuntimeFieldUpdateResult RasterArraySource::rejectRuntimeFieldUpdate(
const RasterArrayRuntimeFieldUpdateResult result, const std::string& reason) const {
Log::Warning(Event::Render,