step 3
This commit is contained in:
@@ -155,6 +155,40 @@ void ControlServices::ClearOscState()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ControlServices::ClearOscStateForLayerKey(const std::string& layerKey)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mPendingOscMutex);
|
||||||
|
for (auto it = mPendingOscUpdates.begin(); it != mPendingOscUpdates.end();)
|
||||||
|
{
|
||||||
|
if (it->second.layerKey == layerKey)
|
||||||
|
it = mPendingOscUpdates.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
|
||||||
|
for (auto it = mPendingOscCommits.begin(); it != mPendingOscCommits.end();)
|
||||||
|
{
|
||||||
|
if (it->second.layerKey == layerKey)
|
||||||
|
it = mPendingOscCommits.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
|
||||||
|
for (auto it = mCompletedOscCommits.begin(); it != mCompletedOscCommits.end();)
|
||||||
|
{
|
||||||
|
if (it->routeKey.rfind(layerKey + "\n", 0) == 0)
|
||||||
|
it = mCompletedOscCommits.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ControlServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
void ControlServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
||||||
{
|
{
|
||||||
completedCommits.clear();
|
completedCommits.clear();
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public:
|
|||||||
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
|
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
|
||||||
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
|
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
|
||||||
void ClearOscState();
|
void ClearOscState();
|
||||||
|
void ClearOscStateForLayerKey(const std::string& layerKey);
|
||||||
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ void RuntimeServices::ClearOscState()
|
|||||||
mControlServices->ClearOscState();
|
mControlServices->ClearOscState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RuntimeServices::ClearOscStateForLayerKey(const std::string& layerKey)
|
||||||
|
{
|
||||||
|
if (mControlServices)
|
||||||
|
mControlServices->ClearOscStateForLayerKey(layerKey);
|
||||||
|
}
|
||||||
|
|
||||||
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
|
||||||
{
|
{
|
||||||
if (!mControlServices)
|
if (!mControlServices)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public:
|
|||||||
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
|
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
|
||||||
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
|
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
|
||||||
void ClearOscState();
|
void ClearOscState();
|
||||||
|
void ClearOscStateForLayerKey(const std::string& layerKey);
|
||||||
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -415,7 +415,16 @@ void RenderEngine::ProcessScreenshotCaptureCommandsOnRenderThread()
|
|||||||
|
|
||||||
void RenderEngine::ClearOscOverlayState()
|
void RenderEngine::ClearOscOverlayState()
|
||||||
{
|
{
|
||||||
|
InvokeOnRenderThread([this]() {
|
||||||
mRuntimeLiveState.Clear();
|
mRuntimeLiveState.Clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::ClearOscOverlayStateForLayerKey(const std::string& layerKey)
|
||||||
|
{
|
||||||
|
InvokeOnRenderThread([this, layerKey]() {
|
||||||
|
mRuntimeLiveState.ClearForLayerKey(layerKey);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderEngine::UpdateOscOverlayState(
|
void RenderEngine::UpdateOscOverlayState(
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ public:
|
|||||||
void ResetShaderFeedbackState();
|
void ResetShaderFeedbackState();
|
||||||
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
|
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
|
||||||
void ClearOscOverlayState();
|
void ClearOscOverlayState();
|
||||||
|
void ClearOscOverlayStateForLayerKey(const std::string& layerKey);
|
||||||
void UpdateOscOverlayState(
|
void UpdateOscOverlayState(
|
||||||
const std::vector<OscOverlayUpdate>& updates,
|
const std::vector<OscOverlayUpdate>& updates,
|
||||||
const std::vector<OscOverlayCommitCompletion>& completedCommits);
|
const std::vector<OscOverlayCommitCompletion>& completedCommits);
|
||||||
|
|||||||
@@ -85,10 +85,19 @@ bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordin
|
|||||||
|
|
||||||
mRuntimeCoordinator.ApplyCommittedStateMode(result.committedStateMode);
|
mRuntimeCoordinator.ApplyCommittedStateMode(result.committedStateMode);
|
||||||
|
|
||||||
if (result.clearTransientOscState)
|
switch (result.transientOscInvalidation)
|
||||||
{
|
{
|
||||||
|
case RuntimeCoordinatorTransientOscInvalidation::All:
|
||||||
mRenderEngine.ClearOscOverlayState();
|
mRenderEngine.ClearOscOverlayState();
|
||||||
mRuntimeServices.ClearOscState();
|
mRuntimeServices.ClearOscState();
|
||||||
|
break;
|
||||||
|
case RuntimeCoordinatorTransientOscInvalidation::Layer:
|
||||||
|
mRenderEngine.ClearOscOverlayStateForLayerKey(result.transientOscLayerKey);
|
||||||
|
mRuntimeServices.ClearOscStateForLayerKey(result.transientOscLayerKey);
|
||||||
|
break;
|
||||||
|
case RuntimeCoordinatorTransientOscInvalidation::None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
|
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& laye
|
|||||||
}
|
}
|
||||||
|
|
||||||
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true, true);
|
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true, true);
|
||||||
|
if (result.accepted)
|
||||||
|
{
|
||||||
|
result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
|
||||||
|
result.transientOscLayerKey = layerId;
|
||||||
|
}
|
||||||
PublishCoordinatorResult("RemoveLayer", result);
|
PublishCoordinatorResult("RemoveLayer", result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -205,7 +210,8 @@ RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::str
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.clearTransientOscState = true;
|
result.transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::Layer;
|
||||||
|
result.transientOscLayerKey = layerId;
|
||||||
result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
|
result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
|
||||||
PublishCoordinatorResult("ResetLayerParameters", result);
|
PublishCoordinatorResult("ResetLayerParameters", result);
|
||||||
return result;
|
return result;
|
||||||
@@ -538,7 +544,7 @@ void RuntimeCoordinator::PublishCoordinatorResult(const std::string& action, con
|
|||||||
mutation.runtimeStateBroadcastRequired = result.runtimeStateBroadcastRequired;
|
mutation.runtimeStateBroadcastRequired = result.runtimeStateBroadcastRequired;
|
||||||
mutation.shaderBuildRequested = result.shaderBuildRequested;
|
mutation.shaderBuildRequested = result.shaderBuildRequested;
|
||||||
mutation.persistenceRequested = result.persistenceRequested;
|
mutation.persistenceRequested = result.persistenceRequested;
|
||||||
mutation.clearTransientOscState = result.clearTransientOscState;
|
mutation.clearTransientOscState = result.transientOscInvalidation != RuntimeCoordinatorTransientOscInvalidation::None;
|
||||||
mutation.renderResetScope = ToRuntimeEventRenderResetScope(result.renderResetScope);
|
mutation.renderResetScope = ToRuntimeEventRenderResetScope(result.renderResetScope);
|
||||||
mutation.errorMessage = result.errorMessage;
|
mutation.errorMessage = result.errorMessage;
|
||||||
mRuntimeEventDispatcher.PublishPayload(mutation, "RuntimeCoordinator");
|
mRuntimeEventDispatcher.PublishPayload(mutation, "RuntimeCoordinator");
|
||||||
|
|||||||
@@ -25,18 +25,26 @@ enum class RuntimeCoordinatorRenderResetScope
|
|||||||
TemporalHistoryAndFeedback
|
TemporalHistoryAndFeedback
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class RuntimeCoordinatorTransientOscInvalidation
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Layer,
|
||||||
|
All
|
||||||
|
};
|
||||||
|
|
||||||
struct RuntimeCoordinatorResult
|
struct RuntimeCoordinatorResult
|
||||||
{
|
{
|
||||||
bool accepted = false;
|
bool accepted = false;
|
||||||
bool runtimeStateBroadcastRequired = false;
|
bool runtimeStateBroadcastRequired = false;
|
||||||
bool shaderBuildRequested = false;
|
bool shaderBuildRequested = false;
|
||||||
bool persistenceRequested = false;
|
bool persistenceRequested = false;
|
||||||
bool clearTransientOscState = false;
|
|
||||||
bool compileStatusChanged = false;
|
bool compileStatusChanged = false;
|
||||||
bool compileStatusSucceeded = false;
|
bool compileStatusSucceeded = false;
|
||||||
bool clearReloadRequest = false;
|
bool clearReloadRequest = false;
|
||||||
RuntimeCoordinatorCommittedStateMode committedStateMode = RuntimeCoordinatorCommittedStateMode::Unchanged;
|
RuntimeCoordinatorCommittedStateMode committedStateMode = RuntimeCoordinatorCommittedStateMode::Unchanged;
|
||||||
RuntimeCoordinatorRenderResetScope renderResetScope = RuntimeCoordinatorRenderResetScope::None;
|
RuntimeCoordinatorRenderResetScope renderResetScope = RuntimeCoordinatorRenderResetScope::None;
|
||||||
|
RuntimeCoordinatorTransientOscInvalidation transientOscInvalidation = RuntimeCoordinatorTransientOscInvalidation::None;
|
||||||
|
std::string transientOscLayerKey;
|
||||||
std::string compileStatusMessage;
|
std::string compileStatusMessage;
|
||||||
std::string errorMessage;
|
std::string errorMessage;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const
|
|||||||
|
|
||||||
return JsonValue();
|
return JsonValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeLiveState::Clear()
|
void RuntimeLiveState::Clear()
|
||||||
@@ -81,6 +82,47 @@ void RuntimeLiveState::Clear()
|
|||||||
mOscOverlayStates.clear();
|
mOscOverlayStates.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RuntimeLiveState::ClearForLayerKey(const std::string& layerKey)
|
||||||
|
{
|
||||||
|
for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
|
||||||
|
{
|
||||||
|
if (OverlayMatchesLayerKey(it->second, layerKey))
|
||||||
|
it = mOscOverlayStates.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLiveState::OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey)
|
||||||
|
{
|
||||||
|
return MatchesOscControlKey(overlay.layerKey, layerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLiveState::TryResolveOverlayTarget(
|
||||||
|
const OscOverlayState& overlay,
|
||||||
|
const std::vector<RuntimeRenderState>& states,
|
||||||
|
std::vector<RuntimeRenderState>::const_iterator& stateIt,
|
||||||
|
std::vector<ShaderParameterDefinition>::const_iterator& definitionIt)
|
||||||
|
{
|
||||||
|
stateIt = std::find_if(states.begin(), states.end(),
|
||||||
|
[&overlay](const RuntimeRenderState& state)
|
||||||
|
{
|
||||||
|
return MatchesOscControlKey(state.layerId, overlay.layerKey) ||
|
||||||
|
MatchesOscControlKey(state.shaderId, overlay.layerKey) ||
|
||||||
|
MatchesOscControlKey(state.shaderName, overlay.layerKey);
|
||||||
|
});
|
||||||
|
if (stateIt == states.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(),
|
||||||
|
[&overlay](const ShaderParameterDefinition& definition)
|
||||||
|
{
|
||||||
|
return MatchesOscControlKey(definition.id, overlay.parameterKey) ||
|
||||||
|
MatchesOscControlKey(definition.label, overlay.parameterKey);
|
||||||
|
});
|
||||||
|
return definitionIt != stateIt->parameterDefinitions.end();
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t RuntimeLiveState::OverlayCount() const
|
std::size_t RuntimeLiveState::OverlayCount() const
|
||||||
{
|
{
|
||||||
return mOscOverlayStates.size();
|
return mOscOverlayStates.size();
|
||||||
@@ -131,6 +173,27 @@ void RuntimeLiveState::ApplyOscCommitCompletions(const std::vector<RuntimeLiveOs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RuntimeLiveState::PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states)
|
||||||
|
{
|
||||||
|
for (auto it = mOscOverlayStates.begin(); it != mOscOverlayStates.end();)
|
||||||
|
{
|
||||||
|
std::vector<RuntimeRenderState>::const_iterator stateIt;
|
||||||
|
std::vector<ShaderParameterDefinition>::const_iterator definitionIt;
|
||||||
|
if (TryResolveOverlayTarget(it->second, states, stateIt, definitionIt))
|
||||||
|
{
|
||||||
|
ShaderParameterValue targetValue;
|
||||||
|
std::string normalizeError;
|
||||||
|
if (NormalizeAndValidateParameterValue(*definitionIt, it->second.targetValue, targetValue, normalizeError))
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it = mOscOverlayStates.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RuntimeLiveState::ApplyToLayerStates(
|
void RuntimeLiveState::ApplyToLayerStates(
|
||||||
std::vector<RuntimeRenderState>& states,
|
std::vector<RuntimeRenderState>& states,
|
||||||
const RuntimeLiveStateApplyOptions& options,
|
const RuntimeLiveStateApplyOptions& options,
|
||||||
@@ -139,6 +202,10 @@ void RuntimeLiveState::ApplyToLayerStates(
|
|||||||
if (states.empty() || mOscOverlayStates.empty())
|
if (states.empty() || mOscOverlayStates.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
PruneIncompatibleOverlays(states);
|
||||||
|
if (mOscOverlayStates.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
const auto now = options.now;
|
const auto now = options.now;
|
||||||
const double clampedSmoothing = ClampOscAlpha(options.smoothing);
|
const double clampedSmoothing = ClampOscAlpha(options.smoothing);
|
||||||
std::vector<std::string> overlayKeysToRemove;
|
std::vector<std::string> overlayKeysToRemove;
|
||||||
|
|||||||
@@ -44,9 +44,11 @@ class RuntimeLiveState
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void Clear();
|
void Clear();
|
||||||
|
void ClearForLayerKey(const std::string& layerKey);
|
||||||
std::size_t OverlayCount() const;
|
std::size_t OverlayCount() const;
|
||||||
void ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates);
|
void ApplyOscUpdates(const std::vector<RuntimeLiveOscUpdate>& updates);
|
||||||
void ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits);
|
void ApplyOscCommitCompletions(const std::vector<RuntimeLiveOscCommitCompletion>& completedCommits);
|
||||||
|
void PruneIncompatibleOverlays(const std::vector<RuntimeRenderState>& states);
|
||||||
void ApplyToLayerStates(
|
void ApplyToLayerStates(
|
||||||
std::vector<RuntimeRenderState>& states,
|
std::vector<RuntimeRenderState>& states,
|
||||||
const RuntimeLiveStateApplyOptions& options,
|
const RuntimeLiveStateApplyOptions& options,
|
||||||
@@ -67,5 +69,12 @@ private:
|
|||||||
bool commitQueued = false;
|
bool commitQueued = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool OverlayMatchesLayerKey(const OscOverlayState& overlay, const std::string& layerKey);
|
||||||
|
static bool TryResolveOverlayTarget(
|
||||||
|
const OscOverlayState& overlay,
|
||||||
|
const std::vector<RuntimeRenderState>& states,
|
||||||
|
std::vector<RuntimeRenderState>::const_iterator& stateIt,
|
||||||
|
std::vector<ShaderParameterDefinition>::const_iterator& definitionIt);
|
||||||
|
|
||||||
std::map<std::string, OscOverlayState> mOscOverlayStates;
|
std::map<std::string, OscOverlayState> mOscOverlayStates;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ Phase 1 named the subsystems. Phase 2 added the typed event substrate. Phase 3 m
|
|||||||
## Status
|
## Status
|
||||||
|
|
||||||
- Phase 5 design package: proposed.
|
- Phase 5 design package: proposed.
|
||||||
- Phase 5 implementation: Step 2 started.
|
- Phase 5 implementation: Step 3 complete.
|
||||||
- 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 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, `RenderStateComposer` consumes a `LayeredRenderStateInput` whose fields make base persisted, committed live, and transient automation inputs explicit, and `RuntimeLiveState` owns transient-overlay invalidation against current layer/parameter compatibility. Committed runtime values are still physically stored through `RuntimeStore`/`LayerStackStore`.
|
||||||
|
|
||||||
Current live-state footholds:
|
Current live-state footholds:
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ Current live-state footholds:
|
|||||||
- `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.
|
- `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.
|
- `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.
|
- `RuntimeStateLayerModel` names the Phase 5 state categories and classifies current fields as base persisted, committed live, transient automation, render-local, or health/config state.
|
||||||
|
- `RuntimeCoordinator` can request layer-scoped transient OSC invalidation, while `RuntimeLiveState` prunes overlays that no longer map to the current render-facing layer/parameter definitions.
|
||||||
|
|
||||||
## Why Phase 5 Exists
|
## Why Phase 5 Exists
|
||||||
|
|
||||||
@@ -265,14 +266,21 @@ Move reset/reload transient-state decisions into one policy point.
|
|||||||
|
|
||||||
Initial target:
|
Initial target:
|
||||||
|
|
||||||
- layer removal clears matching transient overlays
|
- [x] layer removal clears matching transient overlays
|
||||||
- shader change clears incompatible overlays
|
- [x] shader change clears incompatible overlays
|
||||||
- preset load clears incompatible overlays
|
- [x] preset load clears incompatible overlays
|
||||||
- shader reload can preserve compatible overlays when requested
|
- [x] shader reload can preserve compatible overlays when requested
|
||||||
- temporal/feedback resets stay render-local and separate from parameter overlays
|
- [x] temporal/feedback resets stay render-local and separate from parameter overlays
|
||||||
|
|
||||||
This is where Phase 5 should prevent "clear everything" and "preserve everything" from being scattered through unrelated code.
|
This is where Phase 5 should prevent "clear everything" and "preserve everything" from being scattered through unrelated code.
|
||||||
|
|
||||||
|
Current implementation:
|
||||||
|
|
||||||
|
- `RuntimeCoordinatorResult` carries a named `RuntimeCoordinatorTransientOscInvalidation` request rather than a raw clear-all flag.
|
||||||
|
- `RuntimeUpdateController` applies layer-scoped invalidation to both render-owned overlay state and queued OSC service state.
|
||||||
|
- `RuntimeLiveState::PruneIncompatibleOverlays(...)` is the central compatibility policy for current render-facing layer/parameter definitions.
|
||||||
|
- `RuntimeLiveState::ApplyToLayerStates(...)` prunes incompatible overlays before applying transient values, so shader changes, preset loads, and layer removals stop carrying stale overlays once the current frame state no longer maps them.
|
||||||
|
|
||||||
### Step 4. Clarify OSC Commit Semantics
|
### Step 4. Clarify OSC Commit Semantics
|
||||||
|
|
||||||
Make the transient-to-committed path explicit.
|
Make the transient-to-committed path explicit.
|
||||||
@@ -368,7 +376,7 @@ Phase 5 can be considered complete once the project can say:
|
|||||||
- [x] persisted, committed-live, and transient automation layers are named in code or clear read models
|
- [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] final render-value precedence is explicit and covered by tests
|
||||||
- [x] `RenderStateComposer` or its replacement consumes a layered input contract
|
- [x] `RenderStateComposer` or its replacement consumes a layered input contract
|
||||||
- [ ] reset/reload/preset behavior for transient overlays is centralized or clearly delegated
|
- [x] reset/reload/preset behavior for transient overlays is centralized or clearly delegated
|
||||||
- [ ] OSC overlay settle/commit behavior is explicit, including persistence policy
|
- [ ] OSC overlay settle/commit behavior is explicit, including persistence policy
|
||||||
- [ ] `RuntimeStore` remains durable-state focused and does not absorb transient automation policy
|
- [ ] `RuntimeStore` remains durable-state focused and does not absorb transient automation policy
|
||||||
- [ ] render-local temporal/feedback state remains separate from live parameter layering
|
- [ ] render-local temporal/feedback state remains separate from live parameter layering
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ 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.
|
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.
|
||||||
|
|
||||||
|
`RuntimeLiveState` now owns transient automation invalidation for render-facing compatibility. It can clear overlays for a target layer/control key and prunes overlays that no longer resolve to the current layer and parameter definitions before applying them to a frame. This keeps shader reload, preset load, and layer removal behavior local to the live-state/composition boundary instead of scattering it through GL drawing code.
|
||||||
|
|
||||||
### 5. Shader Build Application
|
### 5. Shader Build Application
|
||||||
|
|
||||||
Compilation itself may eventually move into a separate build service, but once shader build outputs exist, `RenderEngine` owns:
|
Compilation itself may eventually move into a separate build service, but once shader build outputs exist, `RenderEngine` owns:
|
||||||
|
|||||||
@@ -446,6 +446,8 @@ The coordinator should define explicit reset scopes such as:
|
|||||||
|
|
||||||
That allows later phases to stop encoding reset behavior implicitly in UI handlers or render rebuild code.
|
That allows later phases to stop encoding reset behavior implicitly in UI handlers or render rebuild code.
|
||||||
|
|
||||||
|
Phase 5 has made this more concrete for OSC overlays: coordinator results now carry a named transient OSC invalidation request, with layer-scoped invalidation used for layer removal and manual parameter reset. The render/live-state owner still decides compatibility details, but callers no longer infer transient reset behavior from a generic boolean.
|
||||||
|
|
||||||
## Migration Plan From Current Code
|
## Migration Plan From Current Code
|
||||||
|
|
||||||
The coordinator should be introduced incrementally.
|
The coordinator should be introduced incrementally.
|
||||||
|
|||||||
@@ -349,6 +349,100 @@ void TestRuntimeLiveStateTriggerOverlayIncrementsAndClears()
|
|||||||
Expect(liveState.OverlayCount() == 0, "trigger overlay clears after apply");
|
Expect(liveState.OverlayCount() == 0, "trigger overlay clears after apply");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestRuntimeLiveStateClearsOverlaysForLayerKey()
|
||||||
|
{
|
||||||
|
RuntimeLiveState liveState;
|
||||||
|
|
||||||
|
RuntimeLiveOscUpdate first;
|
||||||
|
first.routeKey = "layer-one\namount";
|
||||||
|
first.layerKey = "layer-one";
|
||||||
|
first.parameterKey = "amount";
|
||||||
|
first.targetValue = JsonValue(0.5);
|
||||||
|
|
||||||
|
RuntimeLiveOscUpdate second = first;
|
||||||
|
second.routeKey = "layer-two\namount";
|
||||||
|
second.layerKey = "layer-two";
|
||||||
|
second.targetValue = JsonValue(0.75);
|
||||||
|
|
||||||
|
liveState.ApplyOscUpdates({ first, second });
|
||||||
|
liveState.ClearForLayerKey("layer-one");
|
||||||
|
|
||||||
|
Expect(liveState.OverlayCount() == 1, "layer-scoped invalidation only removes matching overlays");
|
||||||
|
|
||||||
|
std::vector<RuntimeRenderState> states = { MakeLayerState() };
|
||||||
|
states[0].layerId = "layer-two";
|
||||||
|
RuntimeLiveStateApplyOptions options;
|
||||||
|
options.smoothing = 0.0;
|
||||||
|
liveState.ApplyToLayerStates(states, options, nullptr);
|
||||||
|
|
||||||
|
const auto valueIt = states[0].parameterValues.find("amount");
|
||||||
|
Expect(valueIt != states[0].parameterValues.end() &&
|
||||||
|
!valueIt->second.numberValues.empty() &&
|
||||||
|
std::fabs(valueIt->second.numberValues[0] - 0.75) < 0.0001,
|
||||||
|
"unmatched layer invalidation preserves unrelated overlay");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestRuntimeLiveStatePrunesRemovedLayerOverlay()
|
||||||
|
{
|
||||||
|
RuntimeLiveState liveState;
|
||||||
|
|
||||||
|
RuntimeLiveOscUpdate update;
|
||||||
|
update.routeKey = "removed-layer\namount";
|
||||||
|
update.layerKey = "removed-layer";
|
||||||
|
update.parameterKey = "amount";
|
||||||
|
update.targetValue = JsonValue(0.5);
|
||||||
|
liveState.ApplyOscUpdates({ update });
|
||||||
|
|
||||||
|
std::vector<RuntimeRenderState> states = { MakeLayerState() };
|
||||||
|
liveState.PruneIncompatibleOverlays(states);
|
||||||
|
|
||||||
|
Expect(liveState.OverlayCount() == 0, "invalidation policy removes overlays for missing layers");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestRuntimeLiveStatePrunesIncompatibleParameterOverlay()
|
||||||
|
{
|
||||||
|
RuntimeLiveState liveState;
|
||||||
|
|
||||||
|
RuntimeLiveOscUpdate update;
|
||||||
|
update.routeKey = "layer-one\namount";
|
||||||
|
update.layerKey = "layer-one";
|
||||||
|
update.parameterKey = "amount";
|
||||||
|
update.targetValue = JsonValue(0.5);
|
||||||
|
liveState.ApplyOscUpdates({ update });
|
||||||
|
|
||||||
|
std::vector<RuntimeRenderState> states = { MakeLayerStateWithDefinitions({ FloatDefinition("other", "Other") }) };
|
||||||
|
liveState.PruneIncompatibleOverlays(states);
|
||||||
|
|
||||||
|
Expect(liveState.OverlayCount() == 0, "invalidation policy removes overlays for missing parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestRuntimeLiveStatePreservesCompatibleOverlayAfterShaderReload()
|
||||||
|
{
|
||||||
|
RuntimeLiveState liveState;
|
||||||
|
|
||||||
|
RuntimeLiveOscUpdate update;
|
||||||
|
update.routeKey = "layer-one\namount";
|
||||||
|
update.layerKey = "layer-one";
|
||||||
|
update.parameterKey = "Amount";
|
||||||
|
update.targetValue = JsonValue(0.5);
|
||||||
|
liveState.ApplyOscUpdates({ update });
|
||||||
|
|
||||||
|
std::vector<RuntimeRenderState> states = { MakeLayerStateWithDefinitions({ FloatDefinition("amount-renamed", "Amount") }) };
|
||||||
|
liveState.PruneIncompatibleOverlays(states);
|
||||||
|
|
||||||
|
Expect(liveState.OverlayCount() == 1, "invalidation policy preserves overlays that still map by control key");
|
||||||
|
|
||||||
|
RuntimeLiveStateApplyOptions options;
|
||||||
|
options.smoothing = 0.0;
|
||||||
|
liveState.ApplyToLayerStates(states, options, nullptr);
|
||||||
|
|
||||||
|
const auto valueIt = states[0].parameterValues.find("amount-renamed");
|
||||||
|
Expect(valueIt != states[0].parameterValues.end() &&
|
||||||
|
!valueIt->second.numberValues.empty() &&
|
||||||
|
std::fabs(valueIt->second.numberValues[0] - 0.5) < 0.0001,
|
||||||
|
"compatible overlay applies to the reloaded parameter id");
|
||||||
|
}
|
||||||
|
|
||||||
void TestRenderStateComposerBuildsFrameState()
|
void TestRenderStateComposerBuildsFrameState()
|
||||||
{
|
{
|
||||||
RuntimeLiveState liveState;
|
RuntimeLiveState liveState;
|
||||||
@@ -496,6 +590,10 @@ int main()
|
|||||||
TestRuntimeLiveStateSmoothingPartiallyConverges();
|
TestRuntimeLiveStateSmoothingPartiallyConverges();
|
||||||
TestRuntimeLiveStateSmoothingVectorSizeMismatchUsesTargetShape();
|
TestRuntimeLiveStateSmoothingVectorSizeMismatchUsesTargetShape();
|
||||||
TestRuntimeLiveStateTriggerOverlayIncrementsAndClears();
|
TestRuntimeLiveStateTriggerOverlayIncrementsAndClears();
|
||||||
|
TestRuntimeLiveStateClearsOverlaysForLayerKey();
|
||||||
|
TestRuntimeLiveStatePrunesRemovedLayerOverlay();
|
||||||
|
TestRuntimeLiveStatePrunesIncompatibleParameterOverlay();
|
||||||
|
TestRuntimeLiveStatePreservesCompatibleOverlayAfterShaderReload();
|
||||||
TestRenderStateComposerBuildsFrameState();
|
TestRenderStateComposerBuildsFrameState();
|
||||||
TestRenderStateComposerUsesCommittedLayerOverBaseLayer();
|
TestRenderStateComposerUsesCommittedLayerOverBaseLayer();
|
||||||
TestRenderStateComposerUsesBaseLayerWhenCommittedLayerMissing();
|
TestRenderStateComposerUsesBaseLayerWhenCommittedLayerMissing();
|
||||||
|
|||||||
@@ -278,6 +278,14 @@ void TestRuntimeCoordinatorPersistenceEvents()
|
|||||||
expectAcceptedPersistence(coordinator.UpdateLayerParameter(alphaLayerId, "gain", JsonValue(0.75)), "UpdateLayerParameter",
|
expectAcceptedPersistence(coordinator.UpdateLayerParameter(alphaLayerId, "gain", JsonValue(0.75)), "UpdateLayerParameter",
|
||||||
"parameter changes are accepted");
|
"parameter changes are accepted");
|
||||||
|
|
||||||
|
RuntimeCoordinatorResult resetResult = coordinator.ResetLayerParameters(alphaLayerId);
|
||||||
|
std::vector<RuntimeEvent> resetEvents = dispatchAndClear();
|
||||||
|
Expect(resetResult.accepted, "parameter reset is accepted");
|
||||||
|
Expect(resetResult.transientOscInvalidation == RuntimeCoordinatorTransientOscInvalidation::Layer,
|
||||||
|
"parameter reset requests layer-scoped transient OSC invalidation");
|
||||||
|
Expect(resetResult.transientOscLayerKey == alphaLayerId, "parameter reset invalidates the target layer overlays");
|
||||||
|
Expect(countEvents(resetEvents, RuntimeEventType::RuntimeMutationAccepted) == 1, "parameter reset publishes accepted fact");
|
||||||
|
|
||||||
expectAcceptedPersistence(coordinator.AddLayer("beta"), "AddLayer", "stack edits are accepted");
|
expectAcceptedPersistence(coordinator.AddLayer("beta"), "AddLayer", "stack edits are accepted");
|
||||||
layers = store.CopyLayerStates();
|
layers = store.CopyLayerStates();
|
||||||
Expect(layers.size() == 2, "stack edit creates a second layer");
|
Expect(layers.size() == 2, "stack edit creates a second layer");
|
||||||
|
|||||||
Reference in New Issue
Block a user