From 7740fe209c9303562cfe75da35bccd76a4644c51 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Mon, 11 May 2026 18:56:39 +1000 Subject: [PATCH] Phase 5 step 2 --- .../gl/RenderFrameStateResolver.cpp | 14 ++-- .../runtime/live/RenderStateComposer.cpp | 20 +++-- .../runtime/live/RenderStateComposer.h | 17 ++-- docs/PHASE_5_LIVE_STATE_LAYERING_DESIGN.md | 25 +++--- tests/RuntimeLiveStateTests.cpp | 83 ++++++++++++++----- 5 files changed, 103 insertions(+), 56 deletions(-) diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.cpp index 8f253bc..4f42148 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.cpp @@ -100,13 +100,13 @@ std::vector RenderFrameStateResolver::ComposeLayerStates( double smoothing, std::vector* commitRequests) const { - RenderStateCompositionInput input; - input.baseLayerStates = &baseStates; - input.liveState = &liveState; - input.allowLiveCommits = allowCommit; - input.collectLiveCommitRequests = commitRequests != nullptr; - input.liveSmoothing = smoothing; - input.liveCommitDelay = kOscOverlayCommitDelay; + LayeredRenderStateInput input; + input.committedLiveLayerStates = &baseStates; + input.transientAutomationOverlay = &liveState; + input.allowTransientAutomationCommits = allowCommit; + input.collectTransientAutomationCommitRequests = commitRequests != nullptr; + input.transientAutomationSmoothing = smoothing; + input.transientAutomationCommitDelay = kOscOverlayCommitDelay; input.now = std::chrono::steady_clock::now(); const RenderStateCompositionResult result = mRenderStateComposer.BuildFrameState(input); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/live/RenderStateComposer.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/live/RenderStateComposer.cpp index 1bf4c18..9162cc1 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/live/RenderStateComposer.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/live/RenderStateComposer.cpp @@ -1,24 +1,26 @@ #include "RenderStateComposer.h" -RenderStateCompositionResult RenderStateComposer::BuildFrameState(const RenderStateCompositionInput& input) const +RenderStateCompositionResult RenderStateComposer::BuildFrameState(const LayeredRenderStateInput& input) const { RenderStateCompositionResult result; - if (!input.baseLayerStates) + const std::vector* layerStates = + input.committedLiveLayerStates ? input.committedLiveLayerStates : input.basePersistedLayerStates; + if (!layerStates) return result; - result.layerStates = *input.baseLayerStates; + result.layerStates = *layerStates; result.hasLayerStates = !result.layerStates.empty(); - if (input.liveState) + if (input.transientAutomationOverlay) { RuntimeLiveStateApplyOptions options; - options.allowCommit = input.allowLiveCommits; - options.smoothing = input.liveSmoothing; - options.commitDelay = input.liveCommitDelay; + options.allowCommit = input.allowTransientAutomationCommits; + options.smoothing = input.transientAutomationSmoothing; + options.commitDelay = input.transientAutomationCommitDelay; options.now = input.now; - input.liveState->ApplyToLayerStates( + input.transientAutomationOverlay->ApplyToLayerStates( result.layerStates, options, - input.collectLiveCommitRequests ? &result.commitRequests : nullptr); + input.collectTransientAutomationCommitRequests ? &result.commitRequests : nullptr); } return result; } diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/live/RenderStateComposer.h b/apps/LoopThroughWithOpenGLCompositing/runtime/live/RenderStateComposer.h index 5c8c2ef..5a96278 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/live/RenderStateComposer.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/live/RenderStateComposer.h @@ -5,14 +5,15 @@ #include #include -struct RenderStateCompositionInput +struct LayeredRenderStateInput { - const std::vector* baseLayerStates = nullptr; - RuntimeLiveState* liveState = nullptr; - bool allowLiveCommits = false; - bool collectLiveCommitRequests = true; - double liveSmoothing = 0.0; - std::chrono::milliseconds liveCommitDelay = std::chrono::milliseconds(150); + const std::vector* basePersistedLayerStates = nullptr; + const std::vector* committedLiveLayerStates = nullptr; + RuntimeLiveState* transientAutomationOverlay = nullptr; + bool allowTransientAutomationCommits = false; + bool collectTransientAutomationCommitRequests = true; + double transientAutomationSmoothing = 0.0; + std::chrono::milliseconds transientAutomationCommitDelay = std::chrono::milliseconds(150); std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); }; @@ -26,5 +27,5 @@ struct RenderStateCompositionResult class RenderStateComposer { public: - RenderStateCompositionResult BuildFrameState(const RenderStateCompositionInput& input) const; + RenderStateCompositionResult BuildFrameState(const LayeredRenderStateInput& input) const; }; diff --git a/docs/PHASE_5_LIVE_STATE_LAYERING_DESIGN.md b/docs/PHASE_5_LIVE_STATE_LAYERING_DESIGN.md index 7d7e6b3..31eeb11 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: 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. +- Phase 5 implementation: Step 2 started. +- Current alignment: Phase 3 introduced the first pure composition boundary and transient OSC overlay owner. Phase 5 now has a small `RuntimeStateLayerModel` inventory that names the current state categories, and `RenderStateComposer` consumes a `LayeredRenderStateInput` whose fields make base persisted, committed live, and transient automation inputs explicit. Committed runtime values are still physically stored through `RuntimeStore`/`LayerStackStore`, and transient OSC overlay state is still applied through `RuntimeLiveState`. Current live-state footholds: @@ -16,7 +16,7 @@ Current live-state footholds: - `RuntimeCoordinator` owns mutation validation, classification, accepted/rejected event publication, snapshot/reload follow-ups, and the policy switch between committed states and live snapshots. - `RuntimeSnapshotProvider` publishes render-facing snapshots from committed runtime state. - `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. +- `RenderStateComposer` consumes `LayeredRenderStateInput`, chooses committed-live layer states over base-persisted layer states when both are supplied, applies transient automation on top, 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. @@ -246,19 +246,18 @@ Initial target: ### Step 2. Name The Layered Composition Input -Introduce a named composition input model around the current `RenderStateCompositionInput`. +Introduce a named composition input model around the previous `RenderStateCompositionInput`. Initial target: -- make base/committed/transient inputs visible in type names or field names -- keep `RenderStateComposer` behavior unchanged at first -- add tests that assert precedence with no GL +- [x] make base/committed/transient inputs visible in type names or field names +- [x] keep `RenderStateComposer` behavior unchanged at first +- [x] add tests that assert precedence with no GL Possible outcomes: -- evolve `RenderStateCompositionInput` -- add a new `LayeredRenderStateInput` -- add a thin adapter that feeds existing `RenderStateComposer` +- [x] add a new `LayeredRenderStateInput` +- [ ] add a thin adapter if a later migration needs compatibility with the previous input shape ### Step 3. Make Reset And Reload Policy Explicit @@ -366,9 +365,9 @@ Render-local resources such as temporal history, feedback buffers, readback cach Phase 5 can be considered complete once the project can say: -- [ ] persisted, committed-live, and transient automation layers are named in code or clear read models -- [ ] final render-value precedence is explicit and covered by tests -- [ ] `RenderStateComposer` or its replacement consumes a layered input contract +- [x] persisted, committed-live, and transient automation layers are named in code or clear read models +- [x] final render-value precedence is explicit and covered by tests +- [x] `RenderStateComposer` or its replacement consumes a layered input contract - [ ] reset/reload/preset behavior for transient overlays is centralized or clearly delegated - [ ] OSC overlay settle/commit behavior is explicit, including persistence policy - [ ] `RuntimeStore` remains durable-state focused and does not absorb transient automation policy diff --git a/tests/RuntimeLiveStateTests.cpp b/tests/RuntimeLiveStateTests.cpp index 540437b..37b72f6 100644 --- a/tests/RuntimeLiveStateTests.cpp +++ b/tests/RuntimeLiveStateTests.cpp @@ -359,12 +359,12 @@ void TestRenderStateComposerBuildsFrameState() update.targetValue = JsonValue(0.6); liveState.ApplyOscUpdates({ update }); - RenderStateCompositionInput input; + LayeredRenderStateInput input; std::vector baseLayerStates = { MakeLayerState() }; - input.baseLayerStates = &baseLayerStates; - input.liveState = &liveState; - input.allowLiveCommits = false; - input.liveSmoothing = 0.0; + input.committedLiveLayerStates = &baseLayerStates; + input.transientAutomationOverlay = &liveState; + input.allowTransientAutomationCommits = false; + input.transientAutomationSmoothing = 0.0; RenderStateComposer composer; RenderStateCompositionResult result = composer.BuildFrameState(input); @@ -382,6 +382,49 @@ void TestRenderStateComposerBuildsFrameState() "composer leaves base layer states unchanged"); } +void TestRenderStateComposerUsesCommittedLayerOverBaseLayer() +{ + std::vector basePersistedLayerStates = { MakeLayerState() }; + std::vector committedLiveLayerStates = { MakeLayerState() }; + committedLiveLayerStates[0].parameterValues["amount"].numberValues = { 0.4 }; + + LayeredRenderStateInput input; + input.basePersistedLayerStates = &basePersistedLayerStates; + input.committedLiveLayerStates = &committedLiveLayerStates; + + RenderStateComposer composer; + RenderStateCompositionResult result = composer.BuildFrameState(input); + + const auto valueIt = result.layerStates[0].parameterValues.find("amount"); + Expect(valueIt != result.layerStates[0].parameterValues.end() && + !valueIt->second.numberValues.empty() && + std::fabs(valueIt->second.numberValues[0] - 0.4) < 0.0001, + "committed live layer overrides base persisted layer"); + const auto baseValueIt = basePersistedLayerStates[0].parameterValues.find("amount"); + Expect(baseValueIt != basePersistedLayerStates[0].parameterValues.end() && + !baseValueIt->second.numberValues.empty() && + std::fabs(baseValueIt->second.numberValues[0] - 0.25) < 0.0001, + "committed override leaves base persisted layer unchanged"); +} + +void TestRenderStateComposerUsesBaseLayerWhenCommittedLayerMissing() +{ + std::vector basePersistedLayerStates = { MakeLayerState() }; + + LayeredRenderStateInput input; + input.basePersistedLayerStates = &basePersistedLayerStates; + + RenderStateComposer composer; + RenderStateCompositionResult result = composer.BuildFrameState(input); + + Expect(result.hasLayerStates, "composer can use base persisted layer states without committed layer states"); + const auto valueIt = result.layerStates[0].parameterValues.find("amount"); + Expect(valueIt != result.layerStates[0].parameterValues.end() && + !valueIt->second.numberValues.empty() && + std::fabs(valueIt->second.numberValues[0] - 0.25) < 0.0001, + "base persisted value is used when no committed live value exists"); +} + void TestRenderStateComposerQueuesCommitRequestsWhenEnabled() { RuntimeLiveState liveState; @@ -393,13 +436,13 @@ void TestRenderStateComposerQueuesCommitRequestsWhenEnabled() liveState.ApplyOscUpdates({ update }); std::vector baseLayerStates = { MakeLayerState() }; - RenderStateCompositionInput input; - input.baseLayerStates = &baseLayerStates; - input.liveState = &liveState; - input.allowLiveCommits = true; - input.collectLiveCommitRequests = true; - input.liveSmoothing = 0.0; - input.liveCommitDelay = std::chrono::milliseconds(0); + LayeredRenderStateInput input; + input.committedLiveLayerStates = &baseLayerStates; + input.transientAutomationOverlay = &liveState; + input.allowTransientAutomationCommits = true; + input.collectTransientAutomationCommitRequests = true; + input.transientAutomationSmoothing = 0.0; + input.transientAutomationCommitDelay = std::chrono::milliseconds(0); input.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(1); RenderStateComposer composer; @@ -421,13 +464,13 @@ void TestRenderStateComposerSuppressesCommitCollection() liveState.ApplyOscUpdates({ update }); std::vector baseLayerStates = { MakeLayerState() }; - RenderStateCompositionInput input; - input.baseLayerStates = &baseLayerStates; - input.liveState = &liveState; - input.allowLiveCommits = true; - input.collectLiveCommitRequests = false; - input.liveSmoothing = 0.0; - input.liveCommitDelay = std::chrono::milliseconds(0); + LayeredRenderStateInput input; + input.committedLiveLayerStates = &baseLayerStates; + input.transientAutomationOverlay = &liveState; + input.allowTransientAutomationCommits = true; + input.collectTransientAutomationCommitRequests = false; + input.transientAutomationSmoothing = 0.0; + input.transientAutomationCommitDelay = std::chrono::milliseconds(0); input.now = std::chrono::steady_clock::now() + std::chrono::milliseconds(1); RenderStateComposer composer; @@ -454,6 +497,8 @@ int main() TestRuntimeLiveStateSmoothingVectorSizeMismatchUsesTargetShape(); TestRuntimeLiveStateTriggerOverlayIncrementsAndClears(); TestRenderStateComposerBuildsFrameState(); + TestRenderStateComposerUsesCommittedLayerOverBaseLayer(); + TestRenderStateComposerUsesBaseLayerWhenCommittedLayerMissing(); TestRenderStateComposerQueuesCommitRequestsWhenEnabled(); TestRenderStateComposerSuppressesCommitCollection();