Phase 5 step 2
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Successful in 2m54s

This commit is contained in:
Aiden
2026-05-11 18:56:39 +10:00
parent 77590f4a62
commit 7740fe209c
5 changed files with 103 additions and 56 deletions

View File

@@ -100,13 +100,13 @@ std::vector<RuntimeRenderState> RenderFrameStateResolver::ComposeLayerStates(
double smoothing,
std::vector<RuntimeLiveOscCommitRequest>* 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);

View File

@@ -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<RuntimeRenderState>* 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;
}

View File

@@ -5,14 +5,15 @@
#include <chrono>
#include <vector>
struct RenderStateCompositionInput
struct LayeredRenderStateInput
{
const std::vector<RuntimeRenderState>* 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<RuntimeRenderState>* basePersistedLayerStates = nullptr;
const std::vector<RuntimeRenderState>* 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;
};

View File

@@ -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

View File

@@ -359,12 +359,12 @@ void TestRenderStateComposerBuildsFrameState()
update.targetValue = JsonValue(0.6);
liveState.ApplyOscUpdates({ update });
RenderStateCompositionInput input;
LayeredRenderStateInput input;
std::vector<RuntimeRenderState> 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<RuntimeRenderState> basePersistedLayerStates = { MakeLayerState() };
std::vector<RuntimeRenderState> 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<RuntimeRenderState> 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<RuntimeRenderState> 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<RuntimeRenderState> 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();