feat: add runtime particle field updates
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user