Phase 6 step 1
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m40s
CI / Windows Release Package (push) Successful in 2m47s

This commit is contained in:
Aiden
2026-05-11 19:44:35 +10:00
parent a91cc91a21
commit 68503256dc
7 changed files with 79 additions and 17 deletions

View File

@@ -125,6 +125,7 @@ set(APP_SOURCES
"${APP_DIR}/runtime/live/RuntimeStateLayerModel.h" "${APP_DIR}/runtime/live/RuntimeStateLayerModel.h"
"${APP_DIR}/runtime/live/RuntimeLiveState.cpp" "${APP_DIR}/runtime/live/RuntimeLiveState.cpp"
"${APP_DIR}/runtime/live/RuntimeLiveState.h" "${APP_DIR}/runtime/live/RuntimeLiveState.h"
"${APP_DIR}/runtime/persistence/PersistenceRequest.h"
"${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp" "${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp"
"${APP_DIR}/runtime/presentation/RuntimeStateJson.h" "${APP_DIR}/runtime/presentation/RuntimeStateJson.h"
"${APP_DIR}/runtime/presentation/RuntimeStatePresenter.cpp" "${APP_DIR}/runtime/presentation/RuntimeStatePresenter.cpp"
@@ -184,6 +185,7 @@ target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
"${APP_DIR}/runtime/coordination" "${APP_DIR}/runtime/coordination"
"${APP_DIR}/runtime/events" "${APP_DIR}/runtime/events"
"${APP_DIR}/runtime/live" "${APP_DIR}/runtime/live"
"${APP_DIR}/runtime/persistence"
"${APP_DIR}/runtime/presentation" "${APP_DIR}/runtime/presentation"
"${APP_DIR}/runtime/snapshot" "${APP_DIR}/runtime/snapshot"
"${APP_DIR}/runtime/store" "${APP_DIR}/runtime/store"
@@ -293,6 +295,7 @@ target_include_directories(RuntimeEventTypeTests PRIVATE
"${APP_DIR}" "${APP_DIR}"
"${APP_DIR}/runtime" "${APP_DIR}/runtime"
"${APP_DIR}/runtime/events" "${APP_DIR}/runtime/events"
"${APP_DIR}/runtime/persistence"
) )
if(MSVC) if(MSVC)
@@ -366,6 +369,7 @@ target_include_directories(RuntimeSubsystemTests PRIVATE
"${APP_DIR}/runtime/coordination" "${APP_DIR}/runtime/coordination"
"${APP_DIR}/runtime/events" "${APP_DIR}/runtime/events"
"${APP_DIR}/runtime/live" "${APP_DIR}/runtime/live"
"${APP_DIR}/runtime/persistence"
"${APP_DIR}/runtime/presentation" "${APP_DIR}/runtime/presentation"
"${APP_DIR}/runtime/snapshot" "${APP_DIR}/runtime/snapshot"
"${APP_DIR}/runtime/store" "${APP_DIR}/runtime/store"

View File

@@ -580,8 +580,7 @@ void RuntimeCoordinator::PublishCoordinatorFollowUpEvents(const std::string& act
if (result.persistenceRequested) if (result.persistenceRequested)
{ {
RuntimePersistenceRequestedEvent persistenceRequested; RuntimePersistenceRequestedEvent persistenceRequested;
persistenceRequested.reason = action; persistenceRequested.request = PersistenceRequest::RuntimeStateRequest(action);
persistenceRequested.debounceAllowed = true;
mRuntimeEventDispatcher.PublishPayload(persistenceRequested, "RuntimeCoordinator"); mRuntimeEventDispatcher.PublishPayload(persistenceRequested, "RuntimeCoordinator");
} }

View File

@@ -4,6 +4,8 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include "PersistenceRequest.h"
#include <string> #include <string>
enum class RuntimeEventSeverity enum class RuntimeEventSeverity
@@ -109,8 +111,7 @@ struct RuntimeStateChangedEvent
struct RuntimePersistenceRequestedEvent struct RuntimePersistenceRequestedEvent
{ {
std::string reason; PersistenceRequest request;
bool debounceAllowed = true;
}; };
struct RuntimeReloadRequestedEvent struct RuntimeReloadRequestedEvent

View File

@@ -0,0 +1,41 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <string>
enum class PersistenceTargetKind
{
RuntimeState,
StackPreset,
RuntimeConfig
};
struct PersistenceRequest
{
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
std::string reason;
std::string debounceKey = "runtime-state";
bool debounceAllowed = true;
bool flushRequested = false;
uint64_t sequence = 0;
static PersistenceRequest RuntimeStateRequest(const std::string& reason)
{
PersistenceRequest request;
request.targetKind = PersistenceTargetKind::RuntimeState;
request.reason = reason;
request.debounceKey = "runtime-state";
request.debounceAllowed = true;
return request;
}
};
struct PersistenceSnapshot
{
PersistenceTargetKind targetKind = PersistenceTargetKind::RuntimeState;
std::filesystem::path targetPath;
std::string contents;
std::string reason;
uint64_t generation = 0;
};

View File

@@ -7,15 +7,16 @@ Phases 1-5 separate durable state, coordination policy, render-facing snapshots,
## Status ## Status
- Phase 6 design package: proposed. - Phase 6 design package: proposed.
- Phase 6 implementation: not started. - Phase 6 implementation: Step 1 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` already publishes explicit persistence-request outcomes for persisted mutations. The remaining issue is that actual disk writes are still synchronous store work rather than queued, debounced, atomic background writes. - 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.
Current persistence footholds: Current persistence footholds:
- `RuntimeStore` owns persistent runtime-state serialization, stack preset serialization, and durable file IO. - `RuntimeStore` owns persistent runtime-state serialization, stack preset serialization, and durable file IO.
- `CommittedLiveState` owns current committed/session layer and parameter state. - `CommittedLiveState` owns current committed/session layer and parameter state.
- `RuntimeCoordinatorResult::persistenceRequested` exists as an explicit mutation outcome. - `RuntimeCoordinatorResult::persistenceRequested` exists as an explicit mutation outcome.
- `RuntimeEventType::RuntimePersistenceRequested` exists as the event-level persistence request. - `RuntimeEventType::RuntimePersistenceRequested` now carries a `PersistenceRequest`.
- `PersistenceRequest` and `PersistenceSnapshot` name the request/snapshot contract that later steps will hand to the writer.
- Phase 5 clarified which live-state mutations are durable, committed-live, transient automation, or render-local. Settled OSC commits are session-only by default and do not request persistence. - Phase 5 clarified which live-state mutations are durable, committed-live, transient automation, or render-local. Settled OSC commits are session-only by default and do not request persistence.
## Why Phase 6 Exists ## Why Phase 6 Exists
@@ -183,9 +184,16 @@ Make request types and event payloads explicit enough that callers stop thinking
Initial target: Initial target:
- keep existing coordinator persistence decisions - [x] keep existing coordinator persistence decisions
- introduce a `PersistenceRequest`/`PersistenceSnapshot` shape - [x] introduce a `PersistenceRequest`/`PersistenceSnapshot` shape
- document which requests are debounceable - [x] document which requests are debounceable
Current implementation:
- `runtime/persistence/PersistenceRequest.h` defines `PersistenceTargetKind`, `PersistenceRequest`, and `PersistenceSnapshot`.
- `RuntimePersistenceRequestedEvent` carries a typed `PersistenceRequest`.
- `RuntimeCoordinator` emits runtime-state persistence requests with reason, debounce key, and debounce policy.
- Existing synchronous save behavior is intentionally unchanged until Step 2/3.
### Step 2. Extract Snapshot Writing From `RuntimeStore` ### Step 2. Extract Snapshot Writing From `RuntimeStore`

View File

@@ -62,10 +62,19 @@ void TestRuntimeEventPayloadTypes()
Expect(rejectedMutation.errorMessage == "Unknown layer.", "mutation payload carries rejection error"); Expect(rejectedMutation.errorMessage == "Unknown layer.", "mutation payload carries rejection error");
RuntimePersistenceRequestedEvent persistence; RuntimePersistenceRequestedEvent persistence;
persistence.reason = "UpdateLayerParameter"; persistence.request = PersistenceRequest::RuntimeStateRequest("UpdateLayerParameter");
persistence.debounceAllowed = true;
Expect(RuntimeEventPayloadType(persistence) == RuntimeEventType::RuntimePersistenceRequested, "runtime persistence payload maps to persistence event type"); Expect(RuntimeEventPayloadType(persistence) == RuntimeEventType::RuntimePersistenceRequested, "runtime persistence payload maps to persistence event type");
Expect(persistence.debounceAllowed, "runtime persistence payload carries debounce policy"); Expect(persistence.request.targetKind == PersistenceTargetKind::RuntimeState, "runtime persistence payload carries target kind");
Expect(persistence.request.reason == "UpdateLayerParameter", "runtime persistence payload carries request reason");
Expect(persistence.request.debounceAllowed, "runtime persistence payload carries debounce policy");
Expect(persistence.request.debounceKey == "runtime-state", "runtime persistence payload carries debounce key");
PersistenceSnapshot persistenceSnapshot;
persistenceSnapshot.targetKind = PersistenceTargetKind::RuntimeState;
persistenceSnapshot.reason = persistence.request.reason;
persistenceSnapshot.contents = "{}";
Expect(persistenceSnapshot.reason == "UpdateLayerParameter", "persistence snapshot carries capture reason");
Expect(persistenceSnapshot.contents == "{}", "persistence snapshot carries serialized content");
FileChangeDetectedEvent fileChange; FileChangeDetectedEvent fileChange;
fileChange.path = "PollRuntimeStoreChanges"; fileChange.path = "PollRuntimeStoreChanges";
@@ -459,8 +468,7 @@ void TestAcceptedMutationFollowUps()
stateChanged.persistenceRequested = true; stateChanged.persistenceRequested = true;
RuntimePersistenceRequestedEvent persistence; RuntimePersistenceRequestedEvent persistence;
persistence.reason = mutation.action; persistence.request = PersistenceRequest::RuntimeStateRequest(mutation.action);
persistence.debounceAllowed = true;
RuntimeReloadRequestedEvent reload; RuntimeReloadRequestedEvent reload;
reload.reason = mutation.action; reload.reason = mutation.action;
@@ -487,7 +495,8 @@ void TestAcceptedMutationFollowUps()
const RuntimeEvent* persistenceEvent = harness.LastSeen(RuntimeEventType::RuntimePersistenceRequested); const RuntimeEvent* persistenceEvent = harness.LastSeen(RuntimeEventType::RuntimePersistenceRequested);
const auto* persistencePayload = persistenceEvent ? std::get_if<RuntimePersistenceRequestedEvent>(&persistenceEvent->payload) : nullptr; const auto* persistencePayload = persistenceEvent ? std::get_if<RuntimePersistenceRequestedEvent>(&persistenceEvent->payload) : nullptr;
Expect(persistencePayload && persistencePayload->reason == "SetLayerShader", "persistence follow-up preserves mutation action reason"); Expect(persistencePayload && persistencePayload->request.reason == "SetLayerShader", "persistence follow-up preserves mutation action reason");
Expect(persistencePayload && persistencePayload->request.debounceKey == "runtime-state", "persistence follow-up preserves debounce key");
} }
void TestAppLevelBroadcastAndBuildCoalescing() void TestAppLevelBroadcastAndBuildCoalescing()

View File

@@ -259,7 +259,7 @@ void TestRuntimeCoordinatorPersistenceEvents()
if (event.type != RuntimeEventType::RuntimePersistenceRequested) if (event.type != RuntimeEventType::RuntimePersistenceRequested)
continue; continue;
const auto* payload = std::get_if<RuntimePersistenceRequestedEvent>(&event.payload); const auto* payload = std::get_if<RuntimePersistenceRequestedEvent>(&event.payload);
return payload ? payload->reason : std::string(); return payload ? payload->request.reason : std::string();
} }
return std::string(); return std::string();
}; };