From 77590f4a624a130717d237abdbd322bf5fc8b790 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Mon, 11 May 2026 18:53:59 +1000 Subject: [PATCH] Phase 5 step 1 --- CMakeLists.txt | 19 ++ .../runtime/live/RuntimeStateLayerModel.cpp | 230 ++++++++++++++++++ .../runtime/live/RuntimeStateLayerModel.h | 61 +++++ docs/PHASE_5_LIVE_STATE_LAYERING_DESIGN.md | 11 +- docs/subsystems/RenderEngine.md | 2 + docs/subsystems/RuntimeCoordinator.md | 2 + docs/subsystems/RuntimeStore.md | 2 + tests/RuntimeStateLayerModelTests.cpp | 88 +++++++ 8 files changed, 410 insertions(+), 5 deletions(-) create mode 100644 apps/LoopThroughWithOpenGLCompositing/runtime/live/RuntimeStateLayerModel.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/runtime/live/RuntimeStateLayerModel.h create mode 100644 tests/RuntimeStateLayerModelTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index eb90633..8e6b514 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,6 +119,8 @@ set(APP_SOURCES "${APP_DIR}/runtime/events/RuntimeEventType.h" "${APP_DIR}/runtime/live/RenderStateComposer.cpp" "${APP_DIR}/runtime/live/RenderStateComposer.h" + "${APP_DIR}/runtime/live/RuntimeStateLayerModel.cpp" + "${APP_DIR}/runtime/live/RuntimeStateLayerModel.h" "${APP_DIR}/runtime/live/RuntimeLiveState.cpp" "${APP_DIR}/runtime/live/RuntimeLiveState.h" "${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp" @@ -316,6 +318,23 @@ endif() add_test(NAME RuntimeLiveStateTests COMMAND RuntimeLiveStateTests) +add_executable(RuntimeStateLayerModelTests + "${APP_DIR}/runtime/live/RuntimeStateLayerModel.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeStateLayerModelTests.cpp" +) + +target_include_directories(RuntimeStateLayerModelTests PRIVATE + "${APP_DIR}" + "${APP_DIR}/runtime" + "${APP_DIR}/runtime/live" +) + +if(MSVC) + target_compile_options(RuntimeStateLayerModelTests PRIVATE /W3) +endif() + +add_test(NAME RuntimeStateLayerModelTests COMMAND RuntimeStateLayerModelTests) + add_executable(RuntimeSubsystemTests "${APP_DIR}/runtime/coordination/RuntimeCoordinator.cpp" "${APP_DIR}/runtime/snapshot/RenderSnapshotBuilder.cpp" diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/live/RuntimeStateLayerModel.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/live/RuntimeStateLayerModel.cpp new file mode 100644 index 0000000..906c791 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/live/RuntimeStateLayerModel.cpp @@ -0,0 +1,230 @@ +#include "RuntimeStateLayerModel.h" + +const char* RuntimeStateLayerKindName(RuntimeStateLayerKind kind) +{ + switch (kind) + { + case RuntimeStateLayerKind::BasePersisted: + return "base persisted"; + case RuntimeStateLayerKind::CommittedLive: + return "committed live"; + case RuntimeStateLayerKind::TransientAutomation: + return "transient automation"; + case RuntimeStateLayerKind::RenderLocal: + return "render local"; + case RuntimeStateLayerKind::HealthConfig: + return "health/config"; + default: + return "unknown"; + } +} + +int RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind kind) +{ + switch (kind) + { + case RuntimeStateLayerKind::BasePersisted: + return 0; + case RuntimeStateLayerKind::CommittedLive: + return 1; + case RuntimeStateLayerKind::TransientAutomation: + return 2; + case RuntimeStateLayerKind::RenderLocal: + case RuntimeStateLayerKind::HealthConfig: + default: + return -1; + } +} + +bool RuntimeStateLayerIsDurable(RuntimeStateLayerKind kind) +{ + return kind == RuntimeStateLayerKind::BasePersisted; +} + +bool RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind kind) +{ + return RuntimeStateLayerCompositionPrecedence(kind) >= 0; +} + +bool RuntimeStateLayerIsRenderLocal(RuntimeStateLayerKind kind) +{ + return kind == RuntimeStateLayerKind::RenderLocal; +} + +RuntimeStateLayerKind ClassifyRuntimeStateField(RuntimeStateField field) +{ + switch (field) + { + case RuntimeStateField::PersistedLayerStack: + case RuntimeStateField::PersistedParameterValues: + case RuntimeStateField::StackPresets: + return RuntimeStateLayerKind::BasePersisted; + case RuntimeStateField::CommittedSessionParameterValues: + case RuntimeStateField::CommittedLayerBypass: + case RuntimeStateField::RuntimeCompileReloadFlags: + return RuntimeStateLayerKind::CommittedLive; + case RuntimeStateField::TransientOscOverlay: + case RuntimeStateField::TransientAutomationCommitState: + return RuntimeStateLayerKind::TransientAutomation; + case RuntimeStateField::RenderLocalTemporalHistory: + case RuntimeStateField::RenderLocalFeedbackState: + case RuntimeStateField::RenderLocalInputFrames: + case RuntimeStateField::RenderLocalOutputFrames: + return RuntimeStateLayerKind::RenderLocal; + case RuntimeStateField::RuntimeConfiguration: + case RuntimeStateField::HealthTelemetry: + default: + return RuntimeStateLayerKind::HealthConfig; + } +} + +std::vector GetRuntimeStateLayerInventory() +{ + return { + { + RuntimeStateLayerKind::BasePersisted, + "Base persisted state", + "RuntimeStore / LayerStackStore", + "Survives restart", + "Written to disk", + "Default layer stack, shader selections, saved parameter values" + }, + { + RuntimeStateLayerKind::CommittedLive, + "Committed live state", + "RuntimeCoordinator, physically backed by RuntimeStore during migration", + "Current running session", + "May request persistence depending on mutation policy", + "Operator/session truth until changed again" + }, + { + RuntimeStateLayerKind::TransientAutomation, + "Transient automation overlay", + "RuntimeLiveState / RuntimeServiceLiveBridge", + "High-rate and short-lived", + "Not persisted directly", + "Temporary OSC/automation target applied over committed truth" + }, + { + RuntimeStateLayerKind::RenderLocal, + "Render-local state", + "RenderEngine", + "Render-thread/resource lifetime", + "Not persisted", + "Temporal history, feedback, input/output queues, and GL-local caches" + }, + { + RuntimeStateLayerKind::HealthConfig, + "Health/config state", + "RuntimeConfigStore / HealthTelemetry", + "Config survives restart; health is observational", + "Config is file-backed; health is reported, not composed", + "Does not participate in parameter composition" + } + }; +} + +std::vector GetRuntimeStateFieldInventory() +{ + return { + { + RuntimeStateField::PersistedLayerStack, + ClassifyRuntimeStateField(RuntimeStateField::PersistedLayerStack), + "persisted layer stack", + "LayerStackStore", + "Durable layer order, ids, shader selections, and bypass flags" + }, + { + RuntimeStateField::PersistedParameterValues, + ClassifyRuntimeStateField(RuntimeStateField::PersistedParameterValues), + "persisted parameter values", + "LayerStackStore", + "Saved parameter values used as the baseline for snapshots and presets" + }, + { + RuntimeStateField::StackPresets, + ClassifyRuntimeStateField(RuntimeStateField::StackPresets), + "stack presets", + "RuntimeStore / LayerStackStore", + "Durable preset files and preset serialization shape" + }, + { + RuntimeStateField::CommittedSessionParameterValues, + ClassifyRuntimeStateField(RuntimeStateField::CommittedSessionParameterValues), + "committed session parameter values", + "RuntimeCoordinator policy, RuntimeStore backing during migration", + "Operator/API truth after accepted mutations" + }, + { + RuntimeStateField::CommittedLayerBypass, + ClassifyRuntimeStateField(RuntimeStateField::CommittedLayerBypass), + "committed layer bypass", + "RuntimeCoordinator policy, RuntimeStore backing during migration", + "Current operator/API bypass state" + }, + { + RuntimeStateField::RuntimeCompileReloadFlags, + ClassifyRuntimeStateField(RuntimeStateField::RuntimeCompileReloadFlags), + "runtime compile/reload flags", + "RuntimeCoordinator / RuntimeUpdateController", + "Session coordination state used to request snapshot or render rebuild work" + }, + { + RuntimeStateField::TransientOscOverlay, + ClassifyRuntimeStateField(RuntimeStateField::TransientOscOverlay), + "transient OSC overlays", + "RuntimeLiveState", + "High-rate automation values applied above committed state" + }, + { + RuntimeStateField::TransientAutomationCommitState, + ClassifyRuntimeStateField(RuntimeStateField::TransientAutomationCommitState), + "transient automation commit state", + "RuntimeLiveState / RuntimeServiceLiveBridge", + "Generation and completion bookkeeping for settled overlay commits" + }, + { + RuntimeStateField::RenderLocalTemporalHistory, + ClassifyRuntimeStateField(RuntimeStateField::RenderLocalTemporalHistory), + "render-local temporal history", + "RenderEngine", + "GL/resource history that must stay out of parameter layering" + }, + { + RuntimeStateField::RenderLocalFeedbackState, + ClassifyRuntimeStateField(RuntimeStateField::RenderLocalFeedbackState), + "render-local feedback state", + "RenderEngine", + "Feedback buffers and ping-pong resources" + }, + { + RuntimeStateField::RenderLocalInputFrames, + ClassifyRuntimeStateField(RuntimeStateField::RenderLocalInputFrames), + "render-local input frames", + "RenderEngine", + "Latest accepted input frame payloads and upload staging" + }, + { + RuntimeStateField::RenderLocalOutputFrames, + ClassifyRuntimeStateField(RuntimeStateField::RenderLocalOutputFrames), + "render-local output frames", + "RenderEngine", + "Readback, packed output, screenshot, and preview staging" + }, + { + RuntimeStateField::RuntimeConfiguration, + ClassifyRuntimeStateField(RuntimeStateField::RuntimeConfiguration), + "runtime configuration", + "RuntimeConfigStore", + "File-backed config, not a live parameter layer" + }, + { + RuntimeStateField::HealthTelemetry, + ClassifyRuntimeStateField(RuntimeStateField::HealthTelemetry), + "health telemetry", + "HealthTelemetry", + "Operational observations, not source state for render values" + } + }; +} + diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/live/RuntimeStateLayerModel.h b/apps/LoopThroughWithOpenGLCompositing/runtime/live/RuntimeStateLayerModel.h new file mode 100644 index 0000000..802ef9b --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/live/RuntimeStateLayerModel.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +enum class RuntimeStateLayerKind +{ + BasePersisted, + CommittedLive, + TransientAutomation, + RenderLocal, + HealthConfig +}; + +enum class RuntimeStateField +{ + PersistedLayerStack, + PersistedParameterValues, + StackPresets, + CommittedSessionParameterValues, + CommittedLayerBypass, + RuntimeCompileReloadFlags, + TransientOscOverlay, + TransientAutomationCommitState, + RenderLocalTemporalHistory, + RenderLocalFeedbackState, + RenderLocalInputFrames, + RenderLocalOutputFrames, + RuntimeConfiguration, + HealthTelemetry +}; + +struct RuntimeStateLayerDescriptor +{ + RuntimeStateLayerKind kind = RuntimeStateLayerKind::BasePersisted; + const char* name = ""; + const char* owner = ""; + const char* lifetime = ""; + const char* persistence = ""; + const char* renderRole = ""; +}; + +struct RuntimeStateFieldDescriptor +{ + RuntimeStateField field = RuntimeStateField::PersistedLayerStack; + RuntimeStateLayerKind layerKind = RuntimeStateLayerKind::BasePersisted; + const char* name = ""; + const char* currentOwner = ""; + const char* notes = ""; +}; + +const char* RuntimeStateLayerKindName(RuntimeStateLayerKind kind); +int RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind kind); +bool RuntimeStateLayerIsDurable(RuntimeStateLayerKind kind); +bool RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind kind); +bool RuntimeStateLayerIsRenderLocal(RuntimeStateLayerKind kind); + +RuntimeStateLayerKind ClassifyRuntimeStateField(RuntimeStateField field); +std::vector GetRuntimeStateLayerInventory(); +std::vector GetRuntimeStateFieldInventory(); + diff --git a/docs/PHASE_5_LIVE_STATE_LAYERING_DESIGN.md b/docs/PHASE_5_LIVE_STATE_LAYERING_DESIGN.md index 3b3bfc1..7d7e6b3 100644 --- a/docs/PHASE_5_LIVE_STATE_LAYERING_DESIGN.md +++ b/docs/PHASE_5_LIVE_STATE_LAYERING_DESIGN.md @@ -7,8 +7,8 @@ Phase 1 named the subsystems. Phase 2 added the typed event substrate. Phase 3 m ## Status - Phase 5 design package: proposed. -- Phase 5 implementation: not started. -- Current alignment: Phase 3 introduced the first pure composition boundary and transient OSC overlay owner, but committed runtime values are still physically stored through `RuntimeStore`/`LayerStackStore`, and transient OSC overlay state is applied through render-facing helpers rather than through a first-class layered state model. +- Phase 5 implementation: Step 1 started. +- Current alignment: Phase 3 introduced the first pure composition boundary and transient OSC overlay owner, and Phase 5 now has a small `RuntimeStateLayerModel` inventory that names the current state categories. Committed runtime values are still physically stored through `RuntimeStore`/`LayerStackStore`, and transient OSC overlay state is still applied through render-facing helpers rather than through the final layered composition contract. Current live-state footholds: @@ -18,6 +18,7 @@ Current live-state footholds: - `RuntimeLiveState` owns transient OSC overlay bookkeeping, smoothing, generation tracking, and commit-settlement policy. - `RenderStateComposer` combines base render states with live overlay state and returns final per-frame layer states plus settled commit requests. - `RuntimeServiceLiveBridge` drains OSC ingress/completion queues and applies them to render live state during frame preparation. +- `RuntimeStateLayerModel` names the Phase 5 state categories and classifies current fields as base persisted, committed live, transient automation, render-local, or health/config state. ## Why Phase 5 Exists @@ -239,9 +240,9 @@ Document and/or encode where each current state category lives: Initial target: -- identify which fields are durable, committed-live, transient automation, render-local, or health/config -- update subsystem docs where the current ownership is misleading -- add small tests for classification if a pure helper exists +- [x] identify which fields are durable, committed-live, transient automation, render-local, or health/config +- [x] update subsystem docs where the current ownership is misleading +- [x] add small tests for classification if a pure helper exists ### Step 2. Name The Layered Composition Input diff --git a/docs/subsystems/RenderEngine.md b/docs/subsystems/RenderEngine.md index c40e105..204d0b4 100644 --- a/docs/subsystems/RenderEngine.md +++ b/docs/subsystems/RenderEngine.md @@ -109,6 +109,8 @@ Examples: This state should remain render-local even when it influences visible output. +Phase 5's `RuntimeStateLayerModel` explicitly keeps temporal history, feedback state, accepted input frames, staged output frames, preview staging, and screenshot/readback staging in the render-local category. These are deliberately outside the persisted/committed/transient-automation parameter composition rule. + ### 5. Shader Build Application Compilation itself may eventually move into a separate build service, but once shader build outputs exist, `RenderEngine` owns: diff --git a/docs/subsystems/RuntimeCoordinator.md b/docs/subsystems/RuntimeCoordinator.md index 8e8ceb9..8cc3294 100644 --- a/docs/subsystems/RuntimeCoordinator.md +++ b/docs/subsystems/RuntimeCoordinator.md @@ -80,6 +80,8 @@ The coordinator decides which state category a mutation affects: The design rule is that classification belongs here, not in the ingress layer and not in render code. +Phase 5 has started codifying the shared vocabulary for this classification in `RuntimeStateLayerModel`. The current model records committed session parameter values, layer bypass state, and runtime compile/reload flags as committed-live/session coordination state, even though some of those values are still physically backed by `RuntimeStore` during migration. + ### 4. Snapshot publication requests When a mutation changes render-facing state, the coordinator asks `RuntimeSnapshotProvider` to publish a new snapshot or mark one dirty for publication. diff --git a/docs/subsystems/RuntimeStore.md b/docs/subsystems/RuntimeStore.md index 4e0a046..b8038d9 100644 --- a/docs/subsystems/RuntimeStore.md +++ b/docs/subsystems/RuntimeStore.md @@ -95,6 +95,8 @@ Those are coordinator concerns, not store concerns. `RuntimeStore` should own the following state categories. +Phase 5 names this boundary in code through `RuntimeStateLayerModel`: persisted layer stack data, saved parameter values, and stack presets are classified as base persisted state. Operator/session values may still be backed by the store during migration, but their mutation policy is committed-live policy owned by the coordinator, not durable-store policy by default. + ### Runtime Configuration Examples: diff --git a/tests/RuntimeStateLayerModelTests.cpp b/tests/RuntimeStateLayerModelTests.cpp new file mode 100644 index 0000000..693ee07 --- /dev/null +++ b/tests/RuntimeStateLayerModelTests.cpp @@ -0,0 +1,88 @@ +#include "RuntimeStateLayerModel.h" + +#include + +namespace +{ +int gFailures = 0; + +void Expect(bool condition, const char* message) +{ + if (condition) + return; + + std::cerr << "FAIL: " << message << "\n"; + ++gFailures; +} + +void TestLayerPrecedence() +{ + Expect(RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind::BasePersisted) == 0, + "base persisted state is the first composition layer"); + Expect(RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind::CommittedLive) == 1, + "committed live state overlays persisted state"); + Expect(RuntimeStateLayerCompositionPrecedence(RuntimeStateLayerKind::TransientAutomation) == 2, + "transient automation overlays committed state"); + Expect(!RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind::RenderLocal), + "render-local state does not participate in parameter composition"); + Expect(!RuntimeStateLayerParticipatesInParameterComposition(RuntimeStateLayerKind::HealthConfig), + "health/config state does not participate in parameter composition"); +} + +void TestFieldClassification() +{ + Expect(ClassifyRuntimeStateField(RuntimeStateField::PersistedLayerStack) == RuntimeStateLayerKind::BasePersisted, + "layer stack is base persisted state"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::PersistedParameterValues) == RuntimeStateLayerKind::BasePersisted, + "saved parameters are base persisted state"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::CommittedSessionParameterValues) == RuntimeStateLayerKind::CommittedLive, + "session parameter values are committed live state"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::RuntimeCompileReloadFlags) == RuntimeStateLayerKind::CommittedLive, + "compile/reload flags are committed session coordination state"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::TransientOscOverlay) == RuntimeStateLayerKind::TransientAutomation, + "OSC overlays are transient automation state"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::TransientAutomationCommitState) == RuntimeStateLayerKind::TransientAutomation, + "overlay commit generations are transient automation state"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::RenderLocalTemporalHistory) == RuntimeStateLayerKind::RenderLocal, + "temporal history is render-local state"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::RenderLocalFeedbackState) == RuntimeStateLayerKind::RenderLocal, + "feedback state is render-local state"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::RuntimeConfiguration) == RuntimeStateLayerKind::HealthConfig, + "runtime configuration is outside parameter composition"); + Expect(ClassifyRuntimeStateField(RuntimeStateField::HealthTelemetry) == RuntimeStateLayerKind::HealthConfig, + "health telemetry is outside parameter composition"); +} + +void TestInventoriesStayInSyncWithEnums() +{ + const std::vector layers = GetRuntimeStateLayerInventory(); + Expect(layers.size() == 5, "layer inventory names all Phase 5 state categories"); + + const std::vector fields = GetRuntimeStateFieldInventory(); + Expect(fields.size() == 14, "field inventory names current state categories"); + for (const RuntimeStateFieldDescriptor& field : fields) + { + Expect(field.layerKind == ClassifyRuntimeStateField(field.field), + "field inventory layer kind matches classifier"); + Expect(field.name != nullptr && field.name[0] != '\0', "field inventory has a display name"); + Expect(field.currentOwner != nullptr && field.currentOwner[0] != '\0', "field inventory has an owner"); + } +} +} + +int main() +{ + TestLayerPrecedence(); + TestFieldClassification(); + TestInventoriesStayInSyncWithEnums(); + + if (gFailures != 0) + { + std::cerr << gFailures << " RuntimeStateLayerModel test failure(s).\n"; + return 1; + } + + std::cout << "RuntimeStateLayerModel tests passed.\n"; + return 0; +} +