Phase 3 docs
This commit is contained in:
@@ -462,6 +462,10 @@ Suggested outcome:
|
||||
|
||||
After the event model exists, finish separating live committed state and service-facing coordination from the runtime facades.
|
||||
|
||||
Dedicated design note:
|
||||
|
||||
- [PHASE_3_LIVE_STATE_SERVICE_COORDINATION_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_3_LIVE_STATE_SERVICE_COORDINATION_DESIGN.md)
|
||||
|
||||
Recommended split:
|
||||
|
||||
- `RuntimeStore`
|
||||
|
||||
359
docs/PHASE_3_LIVE_STATE_SERVICE_COORDINATION_DESIGN.md
Normal file
359
docs/PHASE_3_LIVE_STATE_SERVICE_COORDINATION_DESIGN.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# Phase 3 Design: Live State And Service Coordination
|
||||
|
||||
This document expands Phase 3 of [ARCHITECTURE_RESILIENCE_REVIEW.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/ARCHITECTURE_RESILIENCE_REVIEW.md) into a concrete design target.
|
||||
|
||||
Phase 1 split runtime responsibilities into named subsystems. Phase 2 added the typed internal event model those subsystems can coordinate through. Phase 3 should now finish the service-facing and live-state cleanup needed before the app attempts sole-owner GL rendering.
|
||||
|
||||
## Status
|
||||
|
||||
- Phase 3 design package: proposed.
|
||||
- Phase 3 implementation: not started.
|
||||
- Current alignment: the repo has the right building blocks, but `OpenGLComposite::renderEffect()` still manually reconciles transient OSC overlays, completed OSC commits, committed/live snapshot selection, and render-state resolution on the render path.
|
||||
|
||||
Current footholds:
|
||||
|
||||
- `RuntimeStore` is split into durable state collaborators: `RuntimeConfigStore`, `LayerStackStore`, `ShaderPackageCatalog`, `RenderSnapshotBuilder`, presentation read models, and `HealthTelemetry`.
|
||||
- `RuntimeCoordinator` owns mutation validation/classification and publishes accepted/rejected/follow-up events.
|
||||
- `RuntimeSnapshotProvider` publishes render snapshots from `RenderSnapshotBuilder`.
|
||||
- `RenderEngine` owns render-local OSC overlay state and final render-layer resolution.
|
||||
- `ControlServices` owns OSC ingress, pending OSC updates, completed OSC commit notifications, and service start/stop.
|
||||
- `RuntimeEventDispatcher` now routes accepted mutations, reloads, snapshots, shader build events, backend observations, and health observations.
|
||||
|
||||
The current architecture is much better than the original `RuntimeHost` shape, but the render path still has too much coordination logic stitched through `OpenGLComposite`, `RuntimeServices`, `RuntimeCoordinator`, and `RenderEngine`.
|
||||
|
||||
## Why Phase 3 Exists
|
||||
|
||||
The resilience review says render-thread isolation should come after state access and control coordination are no longer centered on a large mutable runtime object. Phase 2 gives us the event substrate; Phase 3 should make the data flowing into render explicit enough that Phase 4 can make the render thread the sole GL owner without dragging service coordination and state reconciliation with it.
|
||||
|
||||
The main problems Phase 3 addresses:
|
||||
|
||||
- transient OSC overlay state and persisted committed state are still reconciled by hand in `OpenGLComposite::renderEffect()`
|
||||
- `RenderEngine` both stores transient OSC overlay state and resolves final layer states
|
||||
- `ControlServices` exposes service-side queues for pending OSC updates and completed OSC commits
|
||||
- `RuntimeStore` still performs synchronous persistence directly from many state mutation paths
|
||||
- `RuntimeUpdateController` still exists partly as compatibility glue between synchronous coordinator results and event-driven effects
|
||||
|
||||
## Goals
|
||||
|
||||
Phase 3 should establish:
|
||||
|
||||
- an explicit live-state model separating persisted state, committed runtime state, and transient automation overlay
|
||||
- service-facing event bridges for OSC overlay updates and overlay commit completions
|
||||
- a narrower `OpenGLComposite::renderEffect()` that renders a prepared read model instead of orchestrating runtime/service state
|
||||
- a clear owner for final render-layer state resolution before it reaches GL drawing
|
||||
- a contained persistence request model that prepares for the later background writer phase
|
||||
- tests for live-state composition, overlay settlement, and service-to-runtime event behavior without GL or DeckLink
|
||||
|
||||
## Non-Goals
|
||||
|
||||
Phase 3 should not require:
|
||||
|
||||
- a dedicated render thread
|
||||
- moving all GL calls off the current callback path
|
||||
- a background persistence writer implementation
|
||||
- a final DeckLink lifecycle state machine
|
||||
- replacing every direct synchronous command API
|
||||
- a final cue/preset/timeline system
|
||||
|
||||
Those are later phases. Phase 3 is about making state and service coordination clean enough for those later phases.
|
||||
|
||||
## Current Coordination Shape
|
||||
|
||||
`OpenGLComposite::renderEffect()` currently performs a lot of cross-subsystem coordination:
|
||||
|
||||
1. pumps `RuntimeUpdateController::ProcessRuntimeWork()`
|
||||
2. asks `RuntimeServices` to apply pending OSC updates
|
||||
3. asks `RuntimeServices` for completed OSC commits
|
||||
4. converts service read models into `RenderEngine::OscOverlayUpdate` and `OscOverlayCommitCompletion`
|
||||
5. applies those overlay changes to `RenderEngine`
|
||||
6. asks `RenderEngine` to resolve final render layer states
|
||||
7. queues OSC commit requests back into `RuntimeServices`
|
||||
8. asks `RenderEngine` to draw the layer stack
|
||||
|
||||
That works, but it keeps the current frame path responsible for service queue draining, live automation settlement, committed/live state selection, and final render-state composition. Phase 3 should turn that into explicit read models and event bridges.
|
||||
|
||||
## Target State Model
|
||||
|
||||
Phase 3 should formalize three state categories:
|
||||
|
||||
| State category | Owner | Lifetime | Render role |
|
||||
| --- | --- | --- | --- |
|
||||
| Persisted layer state | `LayerStackStore` behind `RuntimeStore` | saved durable state | base layer stack and saved parameter values |
|
||||
| Committed runtime state | `RuntimeCoordinator` / snapshot publication | accepted operator/UI/OSC commits | stable render snapshot selected for rendering |
|
||||
| Transient automation overlay | new live-state collaborator or narrowed render-side owner | high-rate OSC automation between commits | temporary per-route override blended into final values |
|
||||
|
||||
Render should eventually consume:
|
||||
|
||||
```text
|
||||
final render state = published snapshot + committed live selection + transient overlay
|
||||
```
|
||||
|
||||
The important change is not the exact formula name. The important change is that final render-state composition has one named owner and can be tested without GL.
|
||||
|
||||
## Proposed Collaborators
|
||||
|
||||
### `RuntimeLiveState`
|
||||
|
||||
New small runtime collaborator or equivalent read model builder.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- keep transient OSC overlay values keyed by route
|
||||
- track overlay generation and pending commit generation
|
||||
- apply overlay commit completions
|
||||
- decide when an overlay value has settled enough to request a commit
|
||||
- build a `LiveStateOverlaySnapshot` for final render-state composition
|
||||
|
||||
Non-responsibilities:
|
||||
|
||||
- persistent state mutation
|
||||
- shader package lookup
|
||||
- GL resources
|
||||
- OSC socket ownership
|
||||
|
||||
### `RenderStateComposer`
|
||||
|
||||
New pure or mostly pure collaborator.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- combine published render snapshots with live overlay state
|
||||
- apply smoothing/time-based automation policy
|
||||
- return final `RuntimeRenderState` values plus any commit requests
|
||||
- stay testable without OpenGL
|
||||
|
||||
Non-responsibilities:
|
||||
|
||||
- drawing
|
||||
- service queue draining
|
||||
- disk persistence
|
||||
- OSC packet parsing
|
||||
|
||||
### `RuntimeServiceEventBridge`
|
||||
|
||||
This may be a new class or a narrowing of `RuntimeUpdateController`/`RuntimeServices`.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- translate service-side OSC ingress into typed events or live-state commands
|
||||
- publish overlay applied/settled events where useful
|
||||
- route overlay commit requests to `RuntimeCoordinator`
|
||||
- keep `OpenGLComposite` out of service queue draining
|
||||
|
||||
Non-responsibilities:
|
||||
|
||||
- final GL rendering
|
||||
- persistent store mutation outside coordinator APIs
|
||||
|
||||
## Event Bridge Targets
|
||||
|
||||
| Current flow | Phase 3 bridge target | Notes |
|
||||
| --- | --- | --- |
|
||||
| pending OSC updates drained by `OpenGLComposite` | `OscValueReceived` -> live-state overlay update handler | Phase 2 already has the event type; Phase 3 decides whether transient overlay updates enter the app dispatcher or a source-local bridge. |
|
||||
| render asks for overlay commit requests | `OscOverlaySettled` or direct coordinator command plus event publication | Commit request creation should leave `renderEffect()` and live near the live-state owner. |
|
||||
| completed OSC commits drained by `OpenGLComposite` | `RuntimeMutationAccepted` / completion event -> live-state commit completion | Completed commit routing should be event-driven or owned by live-state service bridge. |
|
||||
| `RenderEngine::ResolveRenderLayerStates(...)` | `RenderStateComposer::BuildFrameState(...)` | Keep final state composition testable without GL. |
|
||||
| direct persistence writes from store mutations | `RuntimePersistenceRequested` as the durable write trigger | Background writer lands later; Phase 3 should make request boundaries clear. |
|
||||
| runtime-state broadcast side effects | `RuntimeStateBroadcastRequested` plus optional completed/failed observations | Keep broadcast delivery in services and presentation ownership in runtime presentation. |
|
||||
|
||||
## Runtime Store Scope In Phase 3
|
||||
|
||||
`RuntimeStore` is already much smaller than the original host, but Phase 3 should keep narrowing it toward durable state and read-model publishing.
|
||||
|
||||
Target responsibilities:
|
||||
|
||||
- initialize runtime config and persistent state
|
||||
- expose durable layer/package/config read models
|
||||
- own saved layer stack and preset serialization until the background writer phase
|
||||
- publish or support immutable render/presentation snapshots
|
||||
|
||||
Avoid adding:
|
||||
|
||||
- transient OSC overlay state
|
||||
- frame-local render composition decisions
|
||||
- service queue coordination
|
||||
- background worker policy
|
||||
|
||||
## Runtime Coordinator Scope In Phase 3
|
||||
|
||||
`RuntimeCoordinator` should remain the command/mutation policy owner.
|
||||
|
||||
Keep:
|
||||
|
||||
- validation/classification
|
||||
- accepted/rejected mutation publication
|
||||
- reload/build/persistence follow-up events
|
||||
- synchronous command results for UI/API callers that need immediate success or error
|
||||
|
||||
Narrow:
|
||||
|
||||
- any behavior that looks like render-frame state composition
|
||||
- any direct service queue interpretation
|
||||
- any persistence timing policy beyond publishing `RuntimePersistenceRequested`
|
||||
|
||||
## Render Engine Scope In Phase 3
|
||||
|
||||
`RenderEngine` should move closer to being a GL/render-local owner.
|
||||
|
||||
Keep:
|
||||
|
||||
- GL resources
|
||||
- shader programs
|
||||
- render passes
|
||||
- preview/output rendering
|
||||
- temporal history and feedback resources
|
||||
|
||||
Move or narrow:
|
||||
|
||||
- transient OSC overlay bookkeeping
|
||||
- final layer-state composition from snapshot plus overlay
|
||||
- creation of commit requests from smoothed overlay values
|
||||
|
||||
Some transient render-only state may remain in `RenderEngine` if it truly belongs to GL or temporal resources. But value composition should be separable from drawing.
|
||||
|
||||
## OpenGLComposite Scope In Phase 3
|
||||
|
||||
`OpenGLComposite` should remain the current composition root, but not the runtime-service coordinator.
|
||||
|
||||
Target:
|
||||
|
||||
- wire collaborators
|
||||
- own app-level lifecycle
|
||||
- initialize GL/backend/runtime services
|
||||
- call narrow render/update entrypoints
|
||||
|
||||
Avoid:
|
||||
|
||||
- draining OSC queues directly
|
||||
- converting service DTOs into render DTOs
|
||||
- deciding final layer-state composition
|
||||
- coordinating commit completion settlement
|
||||
|
||||
## Persistence Position
|
||||
|
||||
Phase 3 should not implement the background writer, but it should prepare for it.
|
||||
|
||||
Target behavior by Phase 3 exit:
|
||||
|
||||
- state mutations publish `RuntimePersistenceRequested`
|
||||
- persistence can be observed and tested as an event side effect
|
||||
- synchronous `SavePersistentState()` remains allowed as an implementation detail inside `RuntimeStore`
|
||||
- callers outside the store/coordinator should not infer disk writes from mutation categories
|
||||
|
||||
This keeps Phase 6 smaller: the background snapshot writer can subscribe to persistence requests and consume a stored-state snapshot rather than rediscovering mutation policy.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Step 1. Name The Live State Boundary
|
||||
|
||||
Introduce `RuntimeLiveState`, `RenderStateComposer`, or an equivalent pair of classes.
|
||||
|
||||
Start by moving pure data operations out of `RenderEngine::ResolveRenderLayerStates(...)` without changing behavior.
|
||||
|
||||
### Step 2. Move OSC Overlay Bookkeeping Behind The Boundary
|
||||
|
||||
Move these responsibilities out of the current frame orchestration:
|
||||
|
||||
- overlay updates by route
|
||||
- commit completion tracking
|
||||
- generation matching
|
||||
- settle/commit request creation
|
||||
|
||||
The first implementation can still be called synchronously from the current render path. The important part is that the behavior has a named owner and tests.
|
||||
|
||||
### Step 3. Bridge Service Queues To Events Or Live-State Commands
|
||||
|
||||
Replace `OpenGLComposite::renderEffect()` queue draining with a bridge that publishes or applies:
|
||||
|
||||
- `OscValueReceived`
|
||||
- `OscOverlayApplied`
|
||||
- `OscOverlaySettled`
|
||||
- overlay commit completion observations
|
||||
|
||||
This is where the remaining Phase 2 open question about transient OSC overlay event scope should be resolved for the current architecture.
|
||||
|
||||
### Step 4. Narrow `OpenGLComposite::renderEffect()`
|
||||
|
||||
Target shape:
|
||||
|
||||
```cpp
|
||||
void OpenGLComposite::renderEffect()
|
||||
{
|
||||
mRuntimeUpdateController->ProcessRuntimeWork();
|
||||
RenderFrameState frameState = mRenderFrameCoordinator->BuildFrameState(...);
|
||||
mRenderEngine->RenderLayerStack(frameState);
|
||||
}
|
||||
```
|
||||
|
||||
The exact names can change. The goal is that render effect no longer manually drains services, settles overlay commits, and resolves layer values.
|
||||
|
||||
### Step 5. Add Persistence Boundary Tests
|
||||
|
||||
Add behavior tests for:
|
||||
|
||||
- accepted persisted mutations publish `RuntimePersistenceRequested`
|
||||
- transient OSC commits do not force immediate persistence
|
||||
- preset load/save persistence requests remain explicit
|
||||
- rejected mutations do not publish persistence work
|
||||
|
||||
### Step 6. Update Docs And Phase 4 Readiness
|
||||
|
||||
Before calling Phase 3 complete, update:
|
||||
|
||||
- subsystem docs for new live-state/composer collaborators
|
||||
- architecture review checklist
|
||||
- Phase 4 assumptions about render thread input state
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Phase 3 tests should avoid GL, DeckLink, and sockets.
|
||||
|
||||
Recommended tests:
|
||||
|
||||
- final layer-state composition applies snapshot values when no overlay exists
|
||||
- transient overlay overrides the matching parameter by route
|
||||
- smoothing moves toward target values over time
|
||||
- overlay settle creates one commit request per route/generation
|
||||
- completed commits clear pending overlay commit state
|
||||
- stale commit completions are ignored by generation
|
||||
- accepted mutations publish persistence requests where expected
|
||||
- rejected mutations do not publish persistence or render follow-ups
|
||||
- `OpenGLComposite` no longer needs to drain service result queues for runtime effects
|
||||
|
||||
Existing useful homes:
|
||||
|
||||
- `RuntimeSubsystemTests` for pure state/composer behavior
|
||||
- `RuntimeEventTypeTests` for event bridge behavior
|
||||
- a new `RuntimeLiveStateTests.cpp` target if the live-state code grows enough
|
||||
|
||||
## Phase 3 Exit Criteria
|
||||
|
||||
Phase 3 can be considered complete once the project can say:
|
||||
|
||||
- [ ] final render-state composition has a named, testable owner outside `OpenGLComposite`
|
||||
- [ ] transient OSC overlay state has a named owner and tests
|
||||
- [ ] overlay commit requests and completions no longer require `OpenGLComposite` to drain service queues directly
|
||||
- [ ] `RenderEngine` is closer to GL/render resource ownership and less responsible for value composition
|
||||
- [ ] `RuntimeStore` remains durable-state focused and does not gain live overlay responsibilities
|
||||
- [ ] persistence requests are explicit event outcomes for persisted mutations
|
||||
- [ ] Phase 4 can define a render-thread input contract around immutable or near-immutable frame state
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should transient OSC overlay values enter the app-level event dispatcher, or should they use a dedicated source-local latest-value bridge until live-state layering is finalized?
|
||||
- Should the new live-state owner live under `runtime/`, `gl/`, or a new `renderstate/` boundary?
|
||||
- Should smoothing policy be owned by live state, render-state composition, or render settings?
|
||||
- Should overlay commit completion be represented as a new typed event, or derived from existing accepted mutation events with route/generation metadata?
|
||||
- How much of persistence should remain synchronous until Phase 6?
|
||||
|
||||
## Short Version
|
||||
|
||||
Phase 3 should make the app's live state boring and explicit.
|
||||
|
||||
- persisted state stays in the store
|
||||
- accepted command policy stays in the coordinator
|
||||
- transient automation gets a named owner
|
||||
- final render-state composition becomes testable without GL
|
||||
- `OpenGLComposite` stops manually reconciling service queues and layer values
|
||||
|
||||
Once that is true, Phase 4 can make the render thread the sole GL owner without also having to invent a clean state model at the same time.
|
||||
Reference in New Issue
Block a user