feat: add runtime particle field updates

This commit is contained in:
OpenAI Codex
2026-09-03 18:26:13 +08:00
parent 7af12e7d62
commit 45502a53f4
12 changed files with 346 additions and 15 deletions

View File

@@ -27,14 +27,16 @@ RasterArraySource::RasterArraySource(std::string id,
std::string fieldURL,
std::string nextFixtureID,
std::string nextMetadataURL,
std::string nextFieldURL)
std::string nextFieldURL,
std::vector<RasterArrayRuntimeFieldDescriptor> runtimeFields)
: Source(makeMutable<Impl>(std::move(id),
std::move(fixtureID),
std::move(metadataURL),
std::move(fieldURL),
std::move(nextFixtureID),
std::move(nextMetadataURL),
std::move(nextFieldURL))) {}
std::move(nextFieldURL),
std::move(runtimeFields))) {}
RasterArraySource::~RasterArraySource() {
weakFactory.invalidateWeakPtrs();
@@ -103,6 +105,42 @@ void RasterArraySource::loadDescription(FileSource& fileSource) {
}
});
}
runtimeFields.reserve(impl().runtimeFields.size());
for (const auto& descriptor : impl().runtimeFields) {
RuntimeFieldCandidate runtimeCandidate;
runtimeCandidate.descriptor = descriptor;
runtimeFields.push_back(std::move(runtimeCandidate));
const auto index = runtimeFields.size() - 1;
auto& candidate = runtimeFields.back();
candidate.metadataRequest = fileSource.request(
Resource::source(candidate.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 runtime metadata request failed")));
return;
}
runtimeFields[index].metadata = *response.data;
tryLoadRuntimeField(index);
}
});
candidate.fieldRequest = fileSource.request(
Resource::source(candidate.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 runtime field request failed")));
return;
}
runtimeFields[index].fieldBytes = std::vector<char>(response.data->begin(), response.data->end());
tryLoadRuntimeField(index);
}
});
}
}
void RasterArraySource::tryLoad() {
@@ -140,6 +178,89 @@ void RasterArraySource::tryLoad() {
}
}
void RasterArraySource::tryLoadRuntimeField(const size_t index) {
auto& candidate = runtimeFields.at(index);
if (candidate.snapshot || !candidate.metadata || !candidate.fieldBytes) return;
try {
candidate.snapshot = particle::OfflineFixtureParticleAdapter::load(
candidate.descriptor.fixtureID, *candidate.metadata, *candidate.fieldBytes, 0);
Log::Info(Event::Render,
"ParticleField: runtime candidate ready fixtureId=" + candidate.snapshot->fixtureID +
" dataVersion=" + std::to_string(candidate.snapshot->dataVersion));
} catch (const std::exception& error) {
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(error.what())));
}
}
RasterArrayRuntimeFieldUpdateResult RasterArraySource::rejectRuntimeFieldUpdate(
const RasterArrayRuntimeFieldUpdateResult result, const std::string& reason) const {
Log::Warning(Event::Render,
"ParticleField: event=FIELD_WINDOW_UPDATE_REJECTED reason=" + reason + " sourceId=" + getID());
return result;
}
RasterArrayRuntimeFieldUpdateResult RasterArraySource::updateRuntimeFieldWindow(particle::ParticleFieldSnapshot newT1) {
if (!loaded || !impl().field || !impl().nextField) {
return rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult::RejectedNotLoaded, "SOURCE_NOT_LOADED");
}
if (!newT1.isValid() || newT1.generation == 0) {
return rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult::RejectedInvalidSnapshot,
"INVALID_SNAPSHOT");
}
if (!impl().nextField->isTimeInterpolationCompatibleWith(newT1)) {
return rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult::RejectedIncompatibleSnapshot,
"INCOMPATIBLE_SNAPSHOT");
}
if (particle::sameFieldGpuContent(*impl().nextField, newT1)) {
Log::Info(Event::Render,
"ParticleField: event=FIELD_WINDOW_UPDATE_APPLIED result=NOOP sourceId=" + getID() +
" T0=" + impl().field->fixtureID + " T1=" + impl().nextField->fixtureID);
return RasterArrayRuntimeFieldUpdateResult::Noop;
}
if (newT1.generation <= impl().nextField->generation) {
return rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult::RejectedNonMonotonicGeneration,
"NON_MONOTONIC_GENERATION");
}
const auto oldT0 = impl().field->fixtureID;
const auto oldT1 = impl().nextField->fixtureID;
Log::Info(Event::Render,
"ParticleField: event=FIELD_WINDOW_UPDATE_REQUEST sourceId=" + getID() +
" sequence=" + std::to_string(runtimeUpdateSequence + 1) + " oldT0=" + oldT0 + " oldT1=" + oldT1 +
" newT1=" + newT1.fixtureID + " generation=" + std::to_string(newT1.generation));
auto newT0 = *impl().nextField;
baseImpl = makeMutable<Impl>(impl(), std::move(newT0), std::move(newT1));
++runtimeUpdateSequence;
observer->onSourceChanged(*this);
Log::Info(Event::Render,
"ParticleField: event=FIELD_WINDOW_UPDATE_APPLIED result=APPLIED sourceId=" + getID() +
" sequence=" + std::to_string(runtimeUpdateSequence) + " newT0=" + impl().field->fixtureID +
" newT1=" + impl().nextField->fixtureID +
" generation=" + std::to_string(impl().nextField->generation));
return RasterArrayRuntimeFieldUpdateResult::Applied;
}
RasterArrayRuntimeFieldUpdateResult RasterArraySource::advanceRuntimeFieldWindow() {
if (nextRuntimeField >= runtimeFields.size()) {
return rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult::RejectedNoCandidate, "NO_PRELOADED_FIELD");
}
auto& candidate = runtimeFields[nextRuntimeField];
if (!candidate.snapshot) {
return rejectRuntimeFieldUpdate(RasterArrayRuntimeFieldUpdateResult::RejectedCandidateNotReady,
"PRELOADED_FIELD_NOT_READY");
}
// A preloaded candidate is immutable content, not an active runtime
// revision. Allocate its revision when it enters the authoritative window
// so concurrent asset completion order cannot make it stale.
auto newT1 = *candidate.snapshot;
newT1.generation = ++nextRasterArrayFieldGeneration;
const auto result = updateRuntimeFieldWindow(std::move(newT1));
if (result == RasterArrayRuntimeFieldUpdateResult::Applied || result == RasterArrayRuntimeFieldUpdateResult::Noop) {
++nextRuntimeField;
}
return result;
}
bool RasterArraySource::supportsLayerType(const LayerTypeInfo* info) const {
return info && std::string(info->type) == "raster-particle";
}