This commit is contained in:
Aiden
2026-05-11 20:02:26 +10:00
parent 1629dbc77a
commit 0d57920bc1
6 changed files with 81 additions and 29 deletions

View File

@@ -48,6 +48,9 @@ RuntimeUpdateController::RuntimeUpdateController(
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimeReloadRequested,
[this](const RuntimeEvent& event) { HandleRuntimeReloadRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimePersistenceRequested,
[this](const RuntimeEvent& event) { HandleRuntimePersistenceRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildRequested,
[this](const RuntimeEvent& event) { HandleShaderBuildRequested(event); });
@@ -158,6 +161,16 @@ void RuntimeUpdateController::HandleRuntimeReloadRequested(const RuntimeEvent& e
mRuntimeStore.ClearReloadRequest();
}
void RuntimeUpdateController::HandleRuntimePersistenceRequested(const RuntimeEvent& event)
{
const RuntimePersistenceRequestedEvent* payload = std::get_if<RuntimePersistenceRequestedEvent>(&event.payload);
if (!payload)
return;
std::string error;
mRuntimeStore.RequestPersistence(payload->request, error);
}
void RuntimeUpdateController::HandleShaderBuildRequested(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);

View File

@@ -36,6 +36,7 @@ public:
private:
void HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event);
void HandleRuntimeReloadRequested(const RuntimeEvent& event);
void HandleRuntimePersistenceRequested(const RuntimeEvent& event);
void HandleShaderBuildRequested(const RuntimeEvent& event);
void HandleShaderBuildPrepared(const RuntimeEvent& event);
void HandleShaderBuildFailed(const RuntimeEvent& event);

View File

@@ -43,7 +43,6 @@ std::string PersistenceTargetKindName(PersistenceTargetKind targetKind)
RuntimeStore::RuntimeStore() :
mRenderSnapshotBuilder(*this),
mHealthTelemetry(),
mReloadRequested(false),
mCompileSucceeded(false),
mStartupRandom(GenerateStartupRandom()),
@@ -128,6 +127,35 @@ PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshot(const Per
return BuildRuntimeStatePersistenceSnapshotLocked(request);
}
bool RuntimeStore::RequestPersistence(const PersistenceRequest& request, std::string& error)
{
if (request.targetKind != PersistenceTargetKind::RuntimeState)
{
error = "Unsupported persistence request target: " + PersistenceTargetKindName(request.targetKind);
mHealthTelemetry.RecordPersistenceWriteResult(
false,
PersistenceTargetKindName(request.targetKind),
std::string(),
request.reason,
error,
false);
return false;
}
const PersistenceSnapshot snapshot = BuildRuntimeStatePersistenceSnapshot(request);
if (mPersistenceWriter.EnqueueSnapshot(snapshot, error))
return true;
mHealthTelemetry.RecordPersistenceWriteResult(
false,
PersistenceTargetKindName(request.targetKind),
snapshot.targetPath.string(),
request.reason,
error,
false);
return false;
}
PersistenceSnapshot RuntimeStore::BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const
{
PersistenceSnapshot snapshot;
@@ -211,7 +239,7 @@ bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& e
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
return true;
}
bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error)
@@ -222,7 +250,7 @@ bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& er
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
return true;
}
bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error)
@@ -239,7 +267,7 @@ bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, st
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
return true;
}
bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
@@ -256,7 +284,7 @@ bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
return true;
}
bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
@@ -267,7 +295,7 @@ bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool by
mReloadRequested = true;
MarkParameterStateDirtyLocked();
return SavePersistentState(error);
return true;
}
bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error)
@@ -278,18 +306,19 @@ bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, con
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
return true;
}
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, bool persistState, std::string& error)
{
(void)persistState;
std::lock_guard<std::mutex> lock(mMutex);
if (!mCommittedLiveState.SetParameterValue(layerId, parameterId, value, error))
return false;
MarkParameterStateDirtyLocked();
return !persistState || SavePersistentState(error);
return true;
}
bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error)
@@ -300,7 +329,7 @@ bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, s
return false;
MarkParameterStateDirtyLocked();
return SavePersistentState(error);
return true;
}
bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const
@@ -340,7 +369,7 @@ bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::s
mReloadRequested = true;
MarkRenderStateDirtyLocked();
return SavePersistentState(error);
return true;
}
bool RuntimeStore::HasStoredLayer(const std::string& layerId) const
@@ -503,11 +532,6 @@ bool RuntimeStore::LoadPersistentState(std::string& error)
return mCommittedLiveState.LoadPersistentStateValue(root);
}
bool RuntimeStore::SavePersistentState(std::string& error) const
{
return mPersistenceWriter.EnqueueSnapshot(BuildRuntimeStatePersistenceSnapshotLocked(PersistenceRequest::RuntimeStateRequest("SavePersistentState")), error);
}
PersistenceSnapshot RuntimeStore::BuildStackPresetPersistenceSnapshot(const std::string& presetName) const
{
const std::string safeStem = LayerStackStore::MakeSafePresetFileStem(presetName);

View File

@@ -32,6 +32,7 @@ public:
bool InitializeStore(std::string& error);
std::string BuildPersistentStateJson() const;
PersistenceSnapshot BuildRuntimeStatePersistenceSnapshot(const PersistenceRequest& request) const;
bool RequestPersistence(const PersistenceRequest& request, std::string& error);
bool PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
bool CreateStoredLayer(const std::string& shaderId, std::string& error);
@@ -83,7 +84,6 @@ public:
private:
bool LoadPersistentState(std::string& error);
bool SavePersistentState(std::string& error) const;
PersistenceSnapshot BuildRuntimeStatePersistenceSnapshotLocked(const PersistenceRequest& request) const;
PersistenceSnapshot BuildStackPresetPersistenceSnapshot(const std::string& presetName) const;
bool ScanShaderPackages(std::string& error);
@@ -93,11 +93,11 @@ private:
void MarkParameterStateDirtyLocked();
RenderSnapshotBuilder mRenderSnapshotBuilder;
mutable PersistenceWriter mPersistenceWriter;
RuntimeConfigStore mConfigStore;
ShaderPackageCatalog mShaderCatalog;
CommittedLiveState mCommittedLiveState;
HealthTelemetry mHealthTelemetry;
mutable PersistenceWriter mPersistenceWriter;
mutable std::mutex mMutex;
bool mReloadRequested;
bool mCompileSucceeded;

View File

@@ -7,8 +7,8 @@ Phases 1-5 separate durable state, coordination policy, render-facing snapshots,
## Status
- Phase 6 design package: proposed.
- Phase 6 implementation: Step 4 complete.
- Current alignment: `RuntimeStore` owns durable serialization, config, package metadata, preset IO, and persistence requests; `CommittedLiveState` owns the current committed/session layer state; and `RuntimeCoordinator` publishes typed persistence requests for persisted mutations. The remaining issue is that actual disk writes are still synchronous store work rather than queued, debounced, atomic background writes.
- Phase 6 implementation: Step 5 complete.
- Current alignment: `RuntimeStore` owns durable serialization, config, package metadata, preset IO, and persistence request execution; `CommittedLiveState` owns the current committed/session layer state; and `RuntimeCoordinator` publishes typed persistence requests for persisted mutations. Runtime-state persistence is now requested through the coordinator/event path and executed by the background writer.
Current persistence footholds:
@@ -252,9 +252,17 @@ Route `RuntimePersistenceRequested` or coordinator persistence outcomes into the
Initial target:
- accepted durable mutations request persistence
- transient-only mutations do not
- runtime reload/preset policies remain explicit
- [x] accepted durable mutations request persistence
- [x] transient-only mutations do not
- [x] runtime reload/preset policies remain explicit
Current implementation:
- Store mutation methods update committed durable/session state and mark render state dirty, but no longer enqueue runtime-state writes directly.
- `RuntimeCoordinator` remains the owner of the persistence decision and publishes `RuntimePersistenceRequested` only for accepted durable mutations.
- `RuntimeUpdateController` handles `RuntimePersistenceRequested` and calls `RuntimeStore::RequestPersistence(...)`.
- `RuntimeStore::RequestPersistence(...)` validates the request target, builds the runtime-state snapshot, enqueues it on `PersistenceWriter`, and records enqueue failures in `HealthTelemetry`.
- Stack preset save remains a synchronous preset-file write; preset load updates state and relies on the coordinator persistence request for runtime-state persistence.
### Step 6. Define Shutdown Flush
@@ -307,14 +315,14 @@ Operator-triggered preset save often feels like it should complete before report
Phase 6 can be considered complete once the project can say:
- [ ] durable mutations enqueue persistence instead of directly writing from mutation paths
- [ ] runtime-state writes are debounced/coalesced
- [ ] writes use temp-file/replace or equivalent atomic policy
- [ ] persistence failures are reported through structured health/events
- [ ] transient/live-only mutations do not request persistence
- [x] durable mutations enqueue persistence instead of directly writing from mutation paths
- [x] runtime-state writes are debounced/coalesced
- [x] writes use temp-file/replace or equivalent atomic policy
- [x] persistence failures are reported through structured health/events
- [x] transient/live-only mutations do not request persistence
- [ ] shutdown flush behavior is explicit and tested
- [ ] `RuntimeStore` remains durable-state/serialization owner, not worker policy owner
- [ ] persistence behavior has focused non-render tests
- [x] `RuntimeStore` remains durable-state/serialization owner, not worker policy owner
- [x] persistence behavior has focused non-render tests
## Open Questions

View File

@@ -242,6 +242,12 @@ void TestRuntimeCoordinatorPersistenceEvents()
Expect(snapshot.reason == "unit-test", "runtime-state persistence snapshot preserves request reason");
Expect(snapshot.targetPath.filename().string() == "runtime_state.json", "runtime-state persistence snapshot targets the runtime state file");
Expect(snapshot.contents.find("\"layers\"") != std::string::npos, "runtime-state persistence snapshot contains serialized layer state");
Expect(store.RequestPersistence(PersistenceRequest::RuntimeStateRequest("unit-test-request"), error),
"runtime store accepts runtime-state persistence requests");
PersistenceRequest unsupportedRequest;
unsupportedRequest.targetKind = PersistenceTargetKind::StackPreset;
unsupportedRequest.reason = "unsupported-unit-test";
Expect(!store.RequestPersistence(unsupportedRequest, error), "runtime store rejects unsupported persistence request targets");
RuntimeEventDispatcher dispatcher(64);
std::vector<RuntimeEvent> seenEvents;