Finished phase 1
This commit is contained in:
@@ -57,11 +57,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Debug
|
- name: Build Debug
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: cmake --build --preset build-debug
|
run: cmake --build --preset build-debug --parallel
|
||||||
|
|
||||||
- name: Run Native Tests And Shader Validation
|
- name: Run Native Tests And Shader Validation
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: cmake --build --preset build-debug --target RUN_TESTS
|
run: cmake --build --preset build-debug --target RUN_TESTS --parallel
|
||||||
|
|
||||||
ui-ubuntu:
|
ui-ubuntu:
|
||||||
name: React UI Build
|
name: React UI Build
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Release
|
- name: Build Release
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: cmake --build --preset build-release
|
run: cmake --build --preset build-release --parallel
|
||||||
|
|
||||||
- name: Install Runtime Package
|
- name: Install Runtime Package
|
||||||
shell: powershell
|
shell: powershell
|
||||||
|
|||||||
@@ -242,6 +242,31 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests)
|
add_test(NAME RuntimeParameterUtilsTests COMMAND RuntimeParameterUtilsTests)
|
||||||
|
|
||||||
|
add_executable(RuntimeSubsystemTests
|
||||||
|
"${APP_DIR}/runtime/store/LayerStackStore.cpp"
|
||||||
|
"${APP_DIR}/runtime/store/ShaderPackageCatalog.cpp"
|
||||||
|
"${APP_DIR}/runtime/presentation/RuntimeStateJson.cpp"
|
||||||
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
|
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
|
||||||
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RuntimeSubsystemTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(RuntimeSubsystemTests PRIVATE
|
||||||
|
"${APP_DIR}"
|
||||||
|
"${APP_DIR}/runtime"
|
||||||
|
"${APP_DIR}/runtime/presentation"
|
||||||
|
"${APP_DIR}/runtime/store"
|
||||||
|
"${APP_DIR}/runtime/support"
|
||||||
|
"${APP_DIR}/shader"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(RuntimeSubsystemTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME RuntimeSubsystemTests COMMAND RuntimeSubsystemTests)
|
||||||
|
|
||||||
add_executable(Std140BufferTests
|
add_executable(Std140BufferTests
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/Std140BufferTests.cpp"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Configure and build the native app:
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cmake --preset vs2022-x64-debug
|
cmake --preset vs2022-x64-debug
|
||||||
cmake --build --preset build-debug
|
cmake --build --preset build-debug --parallel
|
||||||
```
|
```
|
||||||
|
|
||||||
Build the React control UI:
|
Build the React control UI:
|
||||||
@@ -80,7 +80,7 @@ npm ci
|
|||||||
npm run build
|
npm run build
|
||||||
cd ..
|
cd ..
|
||||||
cmake --preset vs2022-x64-release
|
cmake --preset vs2022-x64-release
|
||||||
cmake --build --preset build-release
|
cmake --build --preset build-release --parallel
|
||||||
cmake --install build/vs2022-x64-release --config Release --prefix dist/VideoShader
|
cmake --install build/vs2022-x64-release --config Release --prefix dist/VideoShader
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ Compress-Archive -Path dist/VideoShader/* -DestinationPath dist/VideoShader.zip
|
|||||||
Run native tests:
|
Run native tests:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cmake --build --preset build-debug --target RUN_TESTS
|
cmake --build --preset build-debug --target RUN_TESTS --parallel
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the UI production build check:
|
Run the UI production build check:
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ Phase checklist:
|
|||||||
|
|
||||||
Checklist note:
|
Checklist note:
|
||||||
|
|
||||||
- The checked Phase 1 item means the subsystem vocabulary, dependency direction, state categories, and design package are in place.
|
- The checked Phase 1 item means the subsystem vocabulary, dependency direction, state categories, design package, and runtime implementation foothold are in place.
|
||||||
- It does not mean the target boundaries are fully extracted in code. The current implementation has Phase 1 compatibility seams, while the full ownership split continues through later phases.
|
- It does not mean the whole app is fully extracted. Eventing, sole-owner render threading, live-state layering, background persistence, backend lifecycle, and richer telemetry continue through later phases.
|
||||||
|
|
||||||
## Timing Review
|
## Timing Review
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ Recommended direction:
|
|||||||
|
|
||||||
The current design works better now, but it still relies on hand-managed reconciliation between:
|
The current design works better now, but it still relies on hand-managed reconciliation between:
|
||||||
|
|
||||||
- persisted parameter state in `RuntimeHost`
|
- persisted/committed parameter state in `RuntimeStore`
|
||||||
- transient OSC overlay state in `RenderEngine`
|
- transient OSC overlay state in `RenderEngine`
|
||||||
|
|
||||||
Relevant code:
|
Relevant code:
|
||||||
@@ -293,7 +293,7 @@ Add lightweight tracing for:
|
|||||||
- preroll depth versus spare-buffer depth
|
- preroll depth versus spare-buffer depth
|
||||||
- preview present cost and skipped-preview count
|
- preview present cost and skipped-preview count
|
||||||
- control queue depth
|
- control queue depth
|
||||||
- `RuntimeHost` lock contention
|
- runtime state lock contention
|
||||||
|
|
||||||
That would make future tuning and failure diagnosis much easier.
|
That would make future tuning and failure diagnosis much easier.
|
||||||
|
|
||||||
@@ -364,8 +364,8 @@ Before changing major internals, formalize the target responsibilities for each
|
|||||||
Status:
|
Status:
|
||||||
|
|
||||||
- Design deliverable: complete.
|
- Design deliverable: complete.
|
||||||
- Compatibility seams in code: partially complete and expanding.
|
- Runtime implementation foothold: complete.
|
||||||
- Target boundary extraction: not complete across the whole app; remaining work is tracked by later phases, especially the event model, render ownership, and persistence work.
|
- Target boundary extraction: not complete across the whole app; remaining work is tracked by later phases, especially the event model, render ownership, live-state layering, backend lifecycle, telemetry, and persistence work.
|
||||||
|
|
||||||
Target split:
|
Target split:
|
||||||
|
|
||||||
@@ -415,7 +415,7 @@ Suggested deliverables:
|
|||||||
|
|
||||||
Current implementation note:
|
Current implementation note:
|
||||||
|
|
||||||
The repo now has concrete classes for the Phase 1 subsystem names and several call paths already route through them. These classes should be treated as migration boundaries, not proof that the target architecture is fully extracted.
|
The repo now has concrete runtime classes, folders, read models, and subsystem tests for the Phase 1 names. These classes are the runtime foothold for later phases; app-wide extraction still continues around eventing, render ownership, backend lifecycle, persistence, and telemetry.
|
||||||
|
|
||||||
### Phase 2. Introduce an internal event model
|
### Phase 2. Introduce an internal event model
|
||||||
|
|
||||||
@@ -503,7 +503,7 @@ Other threads should only:
|
|||||||
|
|
||||||
Why this phase comes here:
|
Why this phase comes here:
|
||||||
|
|
||||||
- it is much safer once state access and control coordination are no longer centered on `RuntimeHost`
|
- it is much safer once state access and control coordination are no longer centered on one shared runtime object
|
||||||
- it avoids coupling the render-thread refactor to storage and service refactors at the same time
|
- it avoids coupling the render-thread refactor to storage and service refactors at the same time
|
||||||
|
|
||||||
Expected benefits:
|
Expected benefits:
|
||||||
@@ -552,7 +552,7 @@ Target behavior:
|
|||||||
Why this phase comes after state splitting:
|
Why this phase comes after state splitting:
|
||||||
|
|
||||||
- otherwise persistence logic will need to be rewritten twice
|
- otherwise persistence logic will need to be rewritten twice
|
||||||
- it should operate on the new `RuntimeStore` model, not on the current mixed-responsibility object
|
- it should operate on the new `RuntimeStore` model, not on a mixed-responsibility runtime object
|
||||||
|
|
||||||
Expected benefits:
|
Expected benefits:
|
||||||
|
|
||||||
@@ -640,7 +640,7 @@ If this is approached as a serious architecture program rather than opportunisti
|
|||||||
This order tries to avoid doing foundational work twice.
|
This order tries to avoid doing foundational work twice.
|
||||||
|
|
||||||
- The event model comes before major subsystem extraction so coordination patterns stabilize early.
|
- The event model comes before major subsystem extraction so coordination patterns stabilize early.
|
||||||
- `RuntimeHost` is split before render isolation so the render thread does not inherit the current monolithic state model.
|
- runtime state ownership is split before render isolation so the render thread does not inherit a monolithic state model.
|
||||||
- Live state layering is formalized only after render ownership is clearer.
|
- Live state layering is formalized only after render ownership is clearer.
|
||||||
- Persistence is moved later so it can target the final state model rather than the current one.
|
- Persistence is moved later so it can target the final state model rather than the current one.
|
||||||
- Telemetry is intentionally late so it instruments the architecture that survives the refactor.
|
- Telemetry is intentionally late so it instruments the architecture that survives the refactor.
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ The main goal of Phase 1 is not to immediately rewrite the app. It is to establi
|
|||||||
Phase 1 has two different meanings in this repo, and they should not be collapsed:
|
Phase 1 has two different meanings in this repo, and they should not be collapsed:
|
||||||
|
|
||||||
- Phase 1 design package: complete.
|
- Phase 1 design package: complete.
|
||||||
- Phase 1 runtime target extraction in code: largely complete.
|
- Phase 1 runtime implementation foothold: complete.
|
||||||
|
|
||||||
The completed design package includes the agreed subsystem names, responsibilities, dependency rules, state categories, and current-to-target migration map. The runtime code now has concrete subsystem folders and collaborators for those boundaries. That is different from saying every target boundary is fully extracted across the whole app: later roadmap phases are still responsible for the event model, sole-owner render thread, explicit live-state layering, background persistence, backend state machine, and fuller telemetry.
|
The completed design package includes the agreed subsystem names, responsibilities, dependency rules, state categories, and current-to-target migration map. The runtime code now has concrete subsystem folders, collaborators, read models, and tests for those boundaries, and the compiled runtime path no longer depends on `RuntimeHost`. That is different from saying every target boundary is fully extracted across the whole app: later roadmap phases are still responsible for the event model, sole-owner render thread, explicit live-state layering, background persistence, backend state machine, and fuller telemetry.
|
||||||
|
|
||||||
## Why Phase 1 Exists
|
## Why Phase 1 Exists
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ This document is the parent note for the Phase 1 subsystem package. The bundle i
|
|||||||
|
|
||||||
## Current Implementation Foothold
|
## Current Implementation Foothold
|
||||||
|
|
||||||
The codebase now has an initial Phase 1 compatibility split in place:
|
The codebase now has a Phase 1 runtime implementation foothold in place:
|
||||||
|
|
||||||
- `RuntimeStore`
|
- `RuntimeStore`
|
||||||
- [RuntimeStore.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/store/RuntimeStore.h)
|
- [RuntimeStore.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/store/RuntimeStore.h)
|
||||||
@@ -130,7 +130,7 @@ The codebase now has an initial Phase 1 compatibility split in place:
|
|||||||
- [VideoBackend.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h)
|
- [VideoBackend.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h)
|
||||||
- [VideoBackend.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp)
|
- [VideoBackend.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp)
|
||||||
|
|
||||||
These are still compatibility seams, not a completed subsystem extraction. Some responsibilities have moved behind the new boundaries, while other paths still delegate through compatibility helpers, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes. Their purpose is to give later work real code boundaries that can be expanded without first inventing the names:
|
The runtime seams are now concrete code boundaries. Some app-level flows still delegate through compatibility helpers, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes, but runtime responsibilities have moved behind named collaborators:
|
||||||
|
|
||||||
- UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeCoordinator`
|
- UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeCoordinator`
|
||||||
- runtime startup now initializes path resolution and config loading through `RuntimeConfigStore`, with shader package scan and lookup delegated to `ShaderPackageCatalog`
|
- runtime startup now initializes path resolution and config loading through `RuntimeConfigStore`, with shader package scan and lookup delegated to `ShaderPackageCatalog`
|
||||||
@@ -159,13 +159,17 @@ These are still compatibility seams, not a completed subsystem extraction. Some
|
|||||||
- `OpenGLComposite` now owns a `RenderEngine` seam for renderer, pipeline, render-pass, and shader-program responsibilities
|
- `OpenGLComposite` now owns a `RenderEngine` seam for renderer, pipeline, render-pass, and shader-program responsibilities
|
||||||
- `OpenGLComposite` now owns a `VideoBackend` seam for device/session ownership and callback wiring
|
- `OpenGLComposite` now owns a `VideoBackend` seam for device/session ownership and callback wiring
|
||||||
- `OpenGLVideoIOBridge` now acts as an explicit compatibility adapter between `VideoBackend` and `RenderEngine`, instead of `OpenGLComposite` directly owning both sides
|
- `OpenGLVideoIOBridge` now acts as an explicit compatibility adapter between `VideoBackend` and `RenderEngine`, instead of `OpenGLComposite` directly owning both sides
|
||||||
|
- `RuntimeSubsystemTests` now cover the new runtime seams around layer-stack storage, preset round-trips, mutation classification, and runtime-state JSON serialization
|
||||||
|
|
||||||
That means the next extraction work can focus on moving responsibility behind these seams instead of first inventing them.
|
That means Phase 2 can focus on eventing and coordination mechanics rather than inventing the runtime boundary vocabulary.
|
||||||
|
|
||||||
Remaining extraction work includes:
|
Later-phase extraction work includes:
|
||||||
|
|
||||||
- moving persistence to an asynchronous writer in a later phase
|
- moving persistence to an asynchronous writer in a later phase
|
||||||
- replacing polling/shared-object coordination with the planned internal event model
|
- replacing polling/shared-object coordination with the planned internal event model
|
||||||
|
- making the render thread the sole GL owner
|
||||||
|
- formalizing committed-live versus transient-overlay layering
|
||||||
|
- making backend lifecycle and telemetry richer and more explicit
|
||||||
|
|
||||||
## Subsystem Responsibilities
|
## Subsystem Responsibilities
|
||||||
|
|
||||||
@@ -586,7 +590,8 @@ Should eventually split across:
|
|||||||
|
|
||||||
Likely examples:
|
Likely examples:
|
||||||
|
|
||||||
- config loading/saving -> `RuntimeStore`
|
- config loading/path resolution -> `RuntimeConfigStore`
|
||||||
|
- persistent state saving -> `RuntimeStore`
|
||||||
- layer stack mutation validation -> `RuntimeCoordinator`
|
- layer stack mutation validation -> `RuntimeCoordinator`
|
||||||
- render state building/versioning -> `RenderSnapshotBuilder`
|
- render state building/versioning -> `RenderSnapshotBuilder`
|
||||||
- render snapshot publication/cache -> `RuntimeSnapshotProvider`
|
- render snapshot publication/cache -> `RuntimeSnapshotProvider`
|
||||||
@@ -682,7 +687,7 @@ Phase 1 can reasonably be considered complete once the project has:
|
|||||||
- a current-to-target responsibility map for runtime services, `OpenGLComposite`, and backend/render bridge code
|
- a current-to-target responsibility map for runtime services, `OpenGLComposite`, and backend/render bridge code
|
||||||
- a decision that later phases will build against this target rather than inventing new boundaries ad hoc
|
- a decision that later phases will build against this target rather than inventing new boundaries ad hoc
|
||||||
|
|
||||||
By that definition, the Phase 1 design deliverable is complete. The implementation should still be described as partially extracted until the compatibility backing objects and cross-subsystem shims are removed.
|
By that definition, Phase 1 is complete for runtime: the design package is complete, `RuntimeHost` is retired from the compiled runtime path, runtime seams are represented in code, and runtime subsystem tests cover the new boundaries. App-wide ownership work continues in later phases.
|
||||||
|
|
||||||
## Open Questions For Later Phases
|
## Open Questions For Later Phases
|
||||||
|
|
||||||
|
|||||||
@@ -457,8 +457,8 @@ Likely keep under `ControlServices`:
|
|||||||
|
|
||||||
Should move out later:
|
Should move out later:
|
||||||
|
|
||||||
- direct `RuntimeHost` polling dependency
|
- legacy direct runtime polling dependency
|
||||||
- deferred OSC commit behavior as currently implemented through direct host mutation
|
- deferred OSC commit behavior that has since moved behind coordinator-facing outcomes
|
||||||
- any remaining direct state-broadcast decisions tied to runtime internals
|
- any remaining direct state-broadcast decisions tied to runtime internals
|
||||||
|
|
||||||
### Current `ControlServer`
|
### Current `ControlServer`
|
||||||
@@ -513,13 +513,13 @@ The goal is for transports to emit actions, even if temporary adapters still cal
|
|||||||
|
|
||||||
That should move toward a composition root or subsystem host arrangement where render is no longer the owner of control ingress.
|
That should move toward a composition root or subsystem host arrangement where render is no longer the owner of control ingress.
|
||||||
|
|
||||||
### Step 4. Remove Direct `RuntimeHost` Dependency
|
### Step 4. Remove Direct Runtime Mutation Dependency
|
||||||
|
|
||||||
Current polling and deferred OSC commit work directly against `RuntimeHost`:
|
Previous polling and deferred OSC commit work directly against runtime storage:
|
||||||
|
|
||||||
- [RuntimeServices.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp:194)
|
- [RuntimeServices.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp:194)
|
||||||
|
|
||||||
That should be replaced with coordinator-facing actions and later event-driven flows.
|
That has been routed through coordinator-facing actions; later phases should replace the remaining polling shape with event-driven flows.
|
||||||
|
|
||||||
### Step 5. Split Out Observation Delivery
|
### Step 5. Split Out Observation Delivery
|
||||||
|
|
||||||
@@ -535,7 +535,7 @@ Mitigation:
|
|||||||
|
|
||||||
- keep the boundary strict
|
- keep the boundary strict
|
||||||
- route mutations through coordinator interfaces
|
- route mutations through coordinator interfaces
|
||||||
- treat direct host calls as temporary compatibility only
|
- treat any direct runtime mutation calls as migration-only compatibility
|
||||||
|
|
||||||
### Risk 2. Service Queues Becoming Hidden State Authority
|
### Risk 2. Service Queues Becoming Hidden State Authority
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This document expands the `HealthTelemetry` subsystem introduced in [PHASE_1_SUB
|
|||||||
|
|
||||||
`HealthTelemetry` is the subsystem that owns operational visibility for the app. Its purpose is to gather health state, warnings, counters, logs, and timing observations from the other subsystems and publish them in a structured way without becoming a second control plane.
|
`HealthTelemetry` is the subsystem that owns operational visibility for the app. Its purpose is to gather health state, warnings, counters, logs, and timing observations from the other subsystems and publish them in a structured way without becoming a second control plane.
|
||||||
|
|
||||||
Today, those responsibilities are fragmented across `RuntimeHost` status setters, ad hoc `OutputDebugStringA` calls, callback-local warnings, and UI-facing runtime-state payloads. The result is that the app can often detect problems, but it does not yet have one clear place that answers:
|
Before the Phase 1 runtime split, those responsibilities were fragmented across `RuntimeHost` status setters, ad hoc `OutputDebugStringA` calls, callback-local warnings, and UI-facing runtime-state payloads. The result was that the app could often detect problems, but did not yet have one clear place that answered:
|
||||||
|
|
||||||
- what is healthy right now
|
- what is healthy right now
|
||||||
- what is degraded right now
|
- what is degraded right now
|
||||||
@@ -16,14 +16,14 @@ Today, those responsibilities are fragmented across `RuntimeHost` status setters
|
|||||||
|
|
||||||
## Why This Subsystem Exists
|
## Why This Subsystem Exists
|
||||||
|
|
||||||
The current code already contains meaningful health and timing signals, but they are spread through unrelated ownership domains:
|
The codebase already contains meaningful health and timing signals, but some are still spread through unrelated ownership domains:
|
||||||
|
|
||||||
- `RuntimeHost` stores signal and timing status:
|
- previous `RuntimeHost` status fields stored signal and timing status:
|
||||||
- `RuntimeHost.h`
|
- `RuntimeHost.h`
|
||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
- render and bridge code report timing by writing back into `RuntimeHost`:
|
- render and bridge code historically reported timing by writing back into `RuntimeHost`:
|
||||||
- [OpenGLRenderPipeline.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.cpp:50)
|
- [OpenGLRenderPipeline.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.cpp:50)
|
||||||
- [OpenGLVideoIOBridge.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp:49)
|
- [OpenGLVideoIOBridge.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp:49)
|
||||||
- backend warning paths still log directly:
|
- backend warning paths still log directly:
|
||||||
@@ -42,7 +42,7 @@ This creates several recurring problems:
|
|||||||
- logging is mostly text-first instead of structured-first
|
- logging is mostly text-first instead of structured-first
|
||||||
- recovery behavior is hard to audit because the app does not retain a coherent health snapshot
|
- recovery behavior is hard to audit because the app does not retain a coherent health snapshot
|
||||||
|
|
||||||
`HealthTelemetry` exists so later phases can move timing and health concerns out of `RuntimeHost`, out of callback-local logging, and into one subsystem whose only job is observation and reporting.
|
`HealthTelemetry` exists so timing and health concerns have one subsystem whose only job is observation and reporting, instead of drifting back into runtime storage, callback-local logging, or UI payload assembly.
|
||||||
|
|
||||||
## Design Goals
|
## Design Goals
|
||||||
|
|
||||||
@@ -373,9 +373,9 @@ Expected observations:
|
|||||||
|
|
||||||
The current codebase already contains several telemetry responsibilities that should migrate here.
|
The current codebase already contains several telemetry responsibilities that should migrate here.
|
||||||
|
|
||||||
### `RuntimeHost` Status Setters
|
### Previous `RuntimeHost` Status Setters
|
||||||
|
|
||||||
These are the clearest existing candidates:
|
These were the clearest initial migration candidates:
|
||||||
|
|
||||||
- `SetSignalStatus(...)`
|
- `SetSignalStatus(...)`
|
||||||
- `TrySetSignalStatus(...)`
|
- `TrySetSignalStatus(...)`
|
||||||
@@ -391,7 +391,7 @@ See:
|
|||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
|
|
||||||
In the target architecture, this kind of state should no longer sit on the same object that owns persistent layer truth.
|
In the target architecture, this kind of state should not sit on the same object that owns persistent layer truth.
|
||||||
|
|
||||||
### Render Timing Production
|
### Render Timing Production
|
||||||
|
|
||||||
@@ -403,7 +403,7 @@ That timing sample should conceptually become:
|
|||||||
|
|
||||||
- `RenderEngine -> HealthTelemetry::RecordTimingSample(...)`
|
- `RenderEngine -> HealthTelemetry::RecordTimingSample(...)`
|
||||||
|
|
||||||
not:
|
not the old pattern:
|
||||||
|
|
||||||
- `RenderEngine -> RuntimeHost::TrySetPerformanceStats(...)`
|
- `RenderEngine -> RuntimeHost::TrySetPerformanceStats(...)`
|
||||||
|
|
||||||
@@ -468,7 +468,7 @@ So the design should assume:
|
|||||||
- bounded memory
|
- bounded memory
|
||||||
- no long-held global mutex that callbacks and render both depend on
|
- no long-held global mutex that callbacks and render both depend on
|
||||||
|
|
||||||
Phase 1 does not require lock-free implementation, but it does require the architecture to avoid recreating the `RuntimeHost` problem where health writes share the same lock as durable state and render-facing concerns.
|
Phase 1 does not require lock-free implementation, but it does require the architecture to avoid recreating the old problem where health writes share the same lock as durable state and render-facing concerns.
|
||||||
|
|
||||||
Practical expectations:
|
Practical expectations:
|
||||||
|
|
||||||
@@ -494,13 +494,13 @@ Initial responsibilities:
|
|||||||
|
|
||||||
The first implementation can still be backed by simple in-memory structures.
|
The first implementation can still be backed by simple in-memory structures.
|
||||||
|
|
||||||
### Step 2: Move New Observations Off `RuntimeHost`
|
### Step 2: Keep New Observations Off Runtime Storage
|
||||||
|
|
||||||
Before removing old setters, route new health-style work into `HealthTelemetry` instead of adding more `RuntimeHost` status fields.
|
Route new health-style work into `HealthTelemetry` instead of adding more status fields to runtime storage.
|
||||||
|
|
||||||
This prevents the old status surface from growing during migration.
|
This prevents the old status surface from growing during migration.
|
||||||
|
|
||||||
### Step 3: Replace `RuntimeHost` Status Setters With Telemetry Producers
|
### Step 3: Replace Legacy Status Setters With Telemetry Producers
|
||||||
|
|
||||||
Refactor:
|
Refactor:
|
||||||
|
|
||||||
@@ -641,4 +641,4 @@ It should not:
|
|||||||
- coordinate recovery actions
|
- coordinate recovery actions
|
||||||
- become a replacement for the render or backend policy layers
|
- become a replacement for the render or backend policy layers
|
||||||
|
|
||||||
If this boundary holds, later phases can remove timing and warning state from `RuntimeHost` and move toward a much more diagnosable live system.
|
If this boundary holds, later phases can keep moving toward a much more diagnosable live system without putting timing and warning state back into runtime storage.
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ Parent documents:
|
|||||||
Status note:
|
Status note:
|
||||||
|
|
||||||
- The Phase 1 design package is complete.
|
- The Phase 1 design package is complete.
|
||||||
- The codebase has compatibility seams for the named subsystems.
|
- The runtime implementation foothold is complete: the named runtime subsystems exist in code, `RuntimeHost` is retired from the compiled runtime path, and subsystem tests cover the new seams.
|
||||||
- The target subsystem extraction is still in progress, so these notes describe the architecture the implementation is moving toward, not a claim that every boundary is already pure.
|
- The whole app is not fully extracted yet, so these notes still describe the architecture later phases should continue toward.
|
||||||
|
|
||||||
## Recommended Reading Order
|
## Recommended Reading Order
|
||||||
|
|
||||||
@@ -67,4 +67,4 @@ Phase 1 should leave the project with:
|
|||||||
|
|
||||||
Phase 1 does not need to settle every later implementation detail. The subsystem notes intentionally leave some questions open where later phases need room to choose concrete mechanics.
|
Phase 1 does not need to settle every later implementation detail. The subsystem notes intentionally leave some questions open where later phases need room to choose concrete mechanics.
|
||||||
|
|
||||||
As of the current codebase, those design questions are settled well enough for later work to build against them. Remaining implementation work should be tracked as extraction progress under later phases, especially eventing, the `RuntimeHost` split, render-thread ownership, persistence, backend lifecycle, and telemetry.
|
As of the current codebase, those design questions are settled well enough for later work to build against them. Remaining implementation work should be tracked under later phases, especially eventing, render-thread ownership, persistence, backend lifecycle, live-state layering, and telemetry.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document expands the `RenderEngine` portion of [PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md). It defines the target ownership, boundaries, and migration shape for the rendering subsystem so later phases can move GL work out of today's mixed orchestration paths without inventing new boundaries on the fly.
|
This document expands the `RenderEngine` portion of [PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md). It defines the target ownership, boundaries, and migration shape for the rendering subsystem so later phases can move GL work out of today's mixed orchestration paths without inventing new boundaries on the fly.
|
||||||
|
|
||||||
The intent here is not to force a one-step rewrite. It is to make the target render boundary explicit enough that later work on events, `RuntimeHost` splitting, and backend decoupling all land in the same place.
|
The intent here is not to force a one-step rewrite. It is to make the target render boundary explicit enough that later work on events, live-state layering, sole-owner GL threading, and backend decoupling all land in the same place.
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
@@ -400,11 +400,11 @@ As new features are added, keep:
|
|||||||
- render-local overlays
|
- render-local overlays
|
||||||
- preview state
|
- preview state
|
||||||
|
|
||||||
inside render-owned code paths instead of putting them back into `RuntimeHost` or service layers.
|
inside render-owned code paths instead of putting them back into runtime storage or service layers.
|
||||||
|
|
||||||
### Step 3. Isolate Snapshot Consumption
|
### Step 3. Isolate Snapshot Consumption
|
||||||
|
|
||||||
Introduce snapshot-facing APIs so render no longer depends on broad `RuntimeHost` state access for frame production.
|
Introduce snapshot-facing APIs so render no longer depends on broad runtime-state access for frame production.
|
||||||
|
|
||||||
### Step 4. Move Uploads Onto Render Ownership
|
### Step 4. Move Uploads Onto Render Ownership
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ This document defines the target design for the `RuntimeCoordinator` subsystem i
|
|||||||
|
|
||||||
## Why This Subsystem Exists
|
## Why This Subsystem Exists
|
||||||
|
|
||||||
Today the app's mutation path is split across several places:
|
Before the Phase 1 runtime split, the app's mutation path was split across several places:
|
||||||
|
|
||||||
- `RuntimeHost` performs validation, mutation, persistence, render-state invalidation, and some status updates:
|
- `RuntimeHost` performed validation, mutation, persistence, render-state invalidation, and some status updates:
|
||||||
- `RuntimeHost.h`
|
- `RuntimeHost.h`
|
||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
- `OpenGLComposite` currently acts like an orchestration shell and a mutation coordinator at the same time:
|
- `OpenGLComposite` currently acts like an orchestration shell and a mutation coordinator at the same time:
|
||||||
@@ -58,7 +58,7 @@ Examples:
|
|||||||
- whether a trigger should update committed state, transient state, or both
|
- whether a trigger should update committed state, transient state, or both
|
||||||
- whether a structural change should preserve compatible transient state such as feedback buffers
|
- whether a structural change should preserve compatible transient state such as feedback buffers
|
||||||
|
|
||||||
This is the main policy surface that is currently spread between `RuntimeHost` methods such as:
|
This is the policy surface that used to be spread between `RuntimeHost` methods such as:
|
||||||
|
|
||||||
- `AddLayer(...)`
|
- `AddLayer(...)`
|
||||||
- `SetLayerShader(...)`
|
- `SetLayerShader(...)`
|
||||||
@@ -246,7 +246,7 @@ Typical interaction:
|
|||||||
|
|
||||||
This is the coordinator's primary logical domain.
|
This is the coordinator's primary logical domain.
|
||||||
|
|
||||||
Even if the implementation initially stores committed live state inside `RuntimeHost` or later inside `RuntimeStore`, the coordinator should be considered the policy owner of:
|
Even while committed live state is physically stored inside `RuntimeStore`, the coordinator should be considered the policy owner of:
|
||||||
|
|
||||||
- current layer stack composition
|
- current layer stack composition
|
||||||
- current selected shaders
|
- current selected shaders
|
||||||
@@ -316,7 +316,7 @@ The coordinator likely needs collaborators conceptually equivalent to:
|
|||||||
- `IRuntimeStore`
|
- `IRuntimeStore`
|
||||||
- `IRuntimeSnapshotProvider`
|
- `IRuntimeSnapshotProvider`
|
||||||
- `IHealthTelemetry`
|
- `IHealthTelemetry`
|
||||||
- later, a compatibility shim for still-existing `RuntimeHost` behavior during migration
|
- compatibility adapters only where older call shapes still need to be supported during migration
|
||||||
|
|
||||||
### Mutation result shape
|
### Mutation result shape
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ Methods like:
|
|||||||
|
|
||||||
currently do this pattern:
|
currently do this pattern:
|
||||||
|
|
||||||
1. call `RuntimeHost` mutation
|
1. call a host/store mutation directly
|
||||||
2. decide whether to call `ReloadShader(...)`
|
2. decide whether to call `ReloadShader(...)`
|
||||||
3. call `broadcastRuntimeState()`
|
3. call `broadcastRuntimeState()`
|
||||||
|
|
||||||
@@ -365,9 +365,9 @@ See [OpenGLCompositeRuntimeControls.cpp](/c:/Users/Aiden/Documents/GitHub/video-
|
|||||||
|
|
||||||
That "call host, then decide reload/broadcast policy" logic is a direct candidate for migration into `RuntimeCoordinator`.
|
That "call host, then decide reload/broadcast policy" logic is a direct candidate for migration into `RuntimeCoordinator`.
|
||||||
|
|
||||||
### `RuntimeHost`
|
### Previous `RuntimeHost`
|
||||||
|
|
||||||
`RuntimeHost` currently combines:
|
`RuntimeHost` previously combined:
|
||||||
|
|
||||||
- mutation validation
|
- mutation validation
|
||||||
- state mutation
|
- state mutation
|
||||||
@@ -375,7 +375,7 @@ That "call host, then decide reload/broadcast policy" logic is a direct candidat
|
|||||||
- persistence writes
|
- persistence writes
|
||||||
- render-state dirty marking
|
- render-state dirty marking
|
||||||
|
|
||||||
Examples in `RuntimeHost.cpp`:
|
Examples from the old `RuntimeHost.cpp`:
|
||||||
|
|
||||||
- `AddLayer(...)`
|
- `AddLayer(...)`
|
||||||
- `SetLayerShader(...)`
|
- `SetLayerShader(...)`
|
||||||
@@ -452,14 +452,14 @@ The coordinator should be introduced incrementally.
|
|||||||
|
|
||||||
Introduce typed mutation request/result objects without changing most internals yet.
|
Introduce typed mutation request/result objects without changing most internals yet.
|
||||||
|
|
||||||
### Step 2. Wrap current `RuntimeHost` mutations behind coordinator entrypoints
|
### Step 2. Wrap direct runtime mutations behind coordinator entrypoints
|
||||||
|
|
||||||
The first implementation can still delegate heavily into `RuntimeHost`, but the call sites should stop deciding policy on their own.
|
The first implementation could still delegate heavily into existing runtime mutation paths, but the call sites should stop deciding policy on their own.
|
||||||
|
|
||||||
For example, instead of:
|
For example, instead of:
|
||||||
|
|
||||||
1. `OpenGLComposite::AddLayer()`
|
1. `OpenGLComposite::AddLayer()`
|
||||||
2. `RuntimeHost::AddLayer()`
|
2. direct layer-add mutation
|
||||||
3. `ReloadShader(true)`
|
3. `ReloadShader(true)`
|
||||||
4. `broadcastRuntimeState()`
|
4. `broadcastRuntimeState()`
|
||||||
|
|
||||||
@@ -470,7 +470,7 @@ the flow becomes:
|
|||||||
3. coordinator returns a result describing snapshot, reset, and persistence needs
|
3. coordinator returns a result describing snapshot, reset, and persistence needs
|
||||||
4. composition root dispatches those downstream effects
|
4. composition root dispatches those downstream effects
|
||||||
|
|
||||||
### Step 3. Move validation and classification out of `RuntimeHost`
|
### Step 3. Move validation and classification out of direct mutation helpers
|
||||||
|
|
||||||
Once coordinator entrypoints are stable, pull up:
|
Once coordinator entrypoints are stable, pull up:
|
||||||
|
|
||||||
@@ -480,9 +480,9 @@ Once coordinator entrypoints are stable, pull up:
|
|||||||
|
|
||||||
while leaving raw store operations in place.
|
while leaving raw store operations in place.
|
||||||
|
|
||||||
### Step 4. Narrow `RuntimeHost` into store and snapshot collaborators
|
### Step 4. Split storage and snapshot collaborators
|
||||||
|
|
||||||
Only after the coordinator is clearly owning policy should `RuntimeHost` be split into real target subsystems.
|
Only after the coordinator is clearly owning policy should storage and snapshot responsibilities be split into real target subsystems.
|
||||||
|
|
||||||
## Key Risks
|
## Key Risks
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ Mitigation:
|
|||||||
|
|
||||||
### Risk 2. Call sites bypass coordinator during migration
|
### Risk 2. Call sites bypass coordinator during migration
|
||||||
|
|
||||||
If new code continues calling `RuntimeHost` directly for convenience, the architecture will fork into two policy systems.
|
If new code bypasses `RuntimeCoordinator` for convenience, the architecture will fork into two policy systems.
|
||||||
|
|
||||||
Mitigation:
|
Mitigation:
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ The goal of `RuntimeSnapshotProvider` is to separate render-facing state publica
|
|||||||
|
|
||||||
`RuntimeSnapshotProvider` is the boundary between runtime-owned state and render-consumable state.
|
`RuntimeSnapshotProvider` is the boundary between runtime-owned state and render-consumable state.
|
||||||
|
|
||||||
It exists to solve three current problems:
|
It exists to solve three problems that Phase 1 pulled apart:
|
||||||
|
|
||||||
- render state is still built directly out of `RuntimeHost` under a shared mutex
|
- render state was built directly out of `RuntimeHost` under a shared mutex
|
||||||
- render reads and refreshes partially mutable cached layer state in more than one place
|
- render read and refreshed partially mutable cached layer state in more than one place
|
||||||
- state publication, state versioning, and dynamic frame-field refresh need explicit ownership
|
- state publication, state versioning, and dynamic frame-field refresh need explicit ownership
|
||||||
|
|
||||||
Today the closest current behavior lives in:
|
Before the Phase 1 runtime split, the closest behavior lived in:
|
||||||
|
|
||||||
- `RuntimeHost::GetLayerRenderStates(...)`
|
- `RuntimeHost::GetLayerRenderStates(...)`
|
||||||
- `RuntimeHost::TryGetLayerRenderStates(...)`
|
- `RuntimeHost::TryGetLayerRenderStates(...)`
|
||||||
@@ -340,7 +340,7 @@ This is especially important while migrating away from the current lock/fallback
|
|||||||
|
|
||||||
## Current Code Mapping
|
## Current Code Mapping
|
||||||
|
|
||||||
The current code suggests the following migration map.
|
The current code follows this migration map.
|
||||||
|
|
||||||
### Move into `RenderSnapshotBuilder`
|
### Move into `RenderSnapshotBuilder`
|
||||||
|
|
||||||
@@ -370,12 +370,12 @@ Current methods that should become compatibility shims and later disappear:
|
|||||||
|
|
||||||
The previous `OpenGLComposite` cache path:
|
The previous `OpenGLComposite` cache path:
|
||||||
|
|
||||||
- reads versions from `RuntimeHost`/store-owned counters
|
- read versions from `RuntimeHost`/store-owned counters
|
||||||
- conditionally calls `TryRefreshCachedLayerStates(...)`
|
- conditionally calls `TryRefreshCachedLayerStates(...)`
|
||||||
- conditionally rebuilds full layer state
|
- conditionally rebuilds full layer state
|
||||||
- then reapplies render-local OSC overlay state
|
- then reapplies render-local OSC overlay state
|
||||||
|
|
||||||
During migration, that should become:
|
The migrated runtime path is:
|
||||||
|
|
||||||
1. get latest published snapshot from provider
|
1. get latest published snapshot from provider
|
||||||
2. compare snapshot versions produced by `RenderSnapshotBuilder`
|
2. compare snapshot versions produced by `RenderSnapshotBuilder`
|
||||||
@@ -390,12 +390,12 @@ That is a much cleaner split than the current mixed lock/cache/fallback flow in
|
|||||||
### Step 1: Introduce provider types without changing behavior
|
### Step 1: Introduce provider types without changing behavior
|
||||||
|
|
||||||
- define `RuntimeRenderSnapshot`, `RuntimeRenderLayerSnapshot`, and `RuntimeRenderFrameContext`
|
- define `RuntimeRenderSnapshot`, `RuntimeRenderLayerSnapshot`, and `RuntimeRenderFrameContext`
|
||||||
- implement provider methods as thin wrappers over current `RuntimeHost` logic
|
- initially implement provider methods as thin wrappers over existing behavior
|
||||||
- keep `RuntimeHost` as the backing source temporarily
|
- completed: replace the temporary `RuntimeHost` backing source with `RenderSnapshotBuilder`
|
||||||
|
|
||||||
### Step 2: Route render reads through the provider
|
### Step 2: Route render reads through the provider
|
||||||
|
|
||||||
- replace direct `RuntimeHost` layer-state reads with provider snapshot reads
|
- replace direct host/store layer-state reads with provider snapshot reads
|
||||||
- preserve current version behavior first, even if internally bridged to existing counters
|
- preserve current version behavior first, even if internally bridged to existing counters
|
||||||
|
|
||||||
### Step 3: Separate structural publication from frame context
|
### Step 3: Separate structural publication from frame context
|
||||||
@@ -479,7 +479,7 @@ Recommended tests:
|
|||||||
|
|
||||||
`RuntimeSnapshotProvider` can be considered architecturally in place once:
|
`RuntimeSnapshotProvider` can be considered architecturally in place once:
|
||||||
|
|
||||||
- render no longer reads `RuntimeStore` or `RuntimeHost` render state directly
|
- render no longer reads `RuntimeStore` or legacy host render state directly
|
||||||
- render consumes published snapshot handles rather than rebuilding layer vectors from host state
|
- render consumes published snapshot handles rather than rebuilding layer vectors from host state
|
||||||
- dynamic frame fields are supplied separately from structural snapshot publication
|
- dynamic frame fields are supplied separately from structural snapshot publication
|
||||||
- snapshot version domains are explicit and observable
|
- snapshot version domains are explicit and observable
|
||||||
@@ -497,4 +497,4 @@ Its contract is:
|
|||||||
- keep frame-local timing separate
|
- keep frame-local timing separate
|
||||||
- give render a cheap, lock-light read path
|
- give render a cheap, lock-light read path
|
||||||
|
|
||||||
If that boundary is held, later phases can split `RuntimeHost`, isolate render timing, and decouple playout without inventing a second render-state authority.
|
If that boundary is held, later phases can isolate render timing and decouple playout without inventing a second render-state authority.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document expands the `RuntimeStore` portion of [PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md) into a subsystem-specific design note.
|
This document expands the `RuntimeStore` portion of [PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md) into a subsystem-specific design note.
|
||||||
|
|
||||||
The purpose of `RuntimeStore` is to give the Phase 1 target architecture one clear home for durable runtime data. In the current codebase, that responsibility is spread through `RuntimeHost`, where persistence, mutation entrypoints, render-state building, shader metadata access, and status reporting all share the same object and lock domain. `RuntimeStore` is the design boundary that separates "what the app knows and saves" from "how the app decides to mutate it" and "how rendering consumes it."
|
The purpose of `RuntimeStore` is to give the Phase 1 target architecture one clear home for durable runtime data. Before the Phase 1 runtime split, that responsibility was spread through `RuntimeHost`, where persistence, mutation entrypoints, render-state building, shader metadata access, and status reporting all shared the same object and lock domain. `RuntimeStore` is the design boundary that separates "what the app knows and saves" from "how the app decides to mutate it" and "how rendering consumes it."
|
||||||
|
|
||||||
## Role In The Phase 1 Architecture
|
## Role In The Phase 1 Architecture
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ This implies:
|
|||||||
- that mutex should protect durable models only
|
- that mutex should protect durable models only
|
||||||
- render-facing consumers should eventually read via `RuntimeSnapshotProvider`, not by reaching into the store
|
- render-facing consumers should eventually read via `RuntimeSnapshotProvider`, not by reaching into the store
|
||||||
|
|
||||||
One of the main goals here is reducing the current situation where `RuntimeHost` lock scope effectively mixes:
|
One of the main goals here is avoiding the old situation where runtime lock scope effectively mixed:
|
||||||
|
|
||||||
- persistent state
|
- persistent state
|
||||||
- status reporting
|
- status reporting
|
||||||
@@ -296,9 +296,9 @@ The store may emit errors or return result objects, but it should not coordinate
|
|||||||
|
|
||||||
## Current Code Mapping
|
## Current Code Mapping
|
||||||
|
|
||||||
Today, `RuntimeHost` contains most of the responsibilities that should move into `RuntimeStore`.
|
Before the Phase 1 runtime split, `RuntimeHost` contained many responsibilities that needed to move into `RuntimeStore` or adjacent runtime collaborators.
|
||||||
|
|
||||||
Key current code paths:
|
Previous code paths:
|
||||||
|
|
||||||
- config load:
|
- config load:
|
||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
@@ -318,7 +318,7 @@ Key current code paths:
|
|||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
- `RuntimeHost.cpp`
|
- `RuntimeHost.cpp`
|
||||||
|
|
||||||
Durable-state mutation entrypoints that currently live on `RuntimeHost` but conceptually split between coordinator and store:
|
Durable-state mutation entrypoints that previously lived on `RuntimeHost` but conceptually split between coordinator and store:
|
||||||
|
|
||||||
- layer stack edits:
|
- layer stack edits:
|
||||||
- `AddLayer`
|
- `AddLayer`
|
||||||
@@ -336,7 +336,7 @@ The target split should be:
|
|||||||
- validation/policy/orchestration -> `RuntimeCoordinator`
|
- validation/policy/orchestration -> `RuntimeCoordinator`
|
||||||
- durable state write application -> `RuntimeStore`
|
- durable state write application -> `RuntimeStore`
|
||||||
|
|
||||||
Methods that should not move into `RuntimeStore` even though they currently live on `RuntimeHost`:
|
Methods that were intentionally not moved into `RuntimeStore` because they belong under other runtime subsystems:
|
||||||
|
|
||||||
- render-state building and caching:
|
- render-state building and caching:
|
||||||
- `GetLayerRenderStates`
|
- `GetLayerRenderStates`
|
||||||
@@ -361,7 +361,7 @@ Those belong under other target subsystems.
|
|||||||
- `RuntimeConfigStore`
|
- `RuntimeConfigStore`
|
||||||
- runtime host config load and resolved paths
|
- runtime host config load and resolved paths
|
||||||
|
|
||||||
The current codebase has begun this split: `RuntimeConfigStore` owns config parsing, path resolution, configured ports/formats, runtime roots, and shader compiler paths, while `RuntimeStore` keeps compatibility delegates for existing callers.
|
The current codebase has completed this part of the split: `RuntimeConfigStore` owns config parsing, path resolution, configured ports/formats, runtime roots, and shader compiler paths, while `RuntimeStore` exposes compatibility-shaped delegates for existing callers.
|
||||||
- `LayerStackStore`
|
- `LayerStackStore`
|
||||||
- durable layer stack and parameter values
|
- durable layer stack and parameter values
|
||||||
- layer CRUD/reorder and shader selection
|
- layer CRUD/reorder and shader selection
|
||||||
@@ -378,13 +378,13 @@ The current codebase has begun this split: `RuntimeConfigStore` owns config pars
|
|||||||
- `PersistenceWriter` helper
|
- `PersistenceWriter` helper
|
||||||
- synchronous at first, async/debounced later
|
- synchronous at first, async/debounced later
|
||||||
|
|
||||||
The current codebase has begun the layer split: `LayerStackStore` owns durable layer state, layer CRUD/reorder, parameter persistence, and stack preset value serialization/load. `RuntimeStore` still owns locking, file IO, and compatibility delegates for existing callers.
|
The current codebase has completed the layer split: `LayerStackStore` owns durable layer state, layer CRUD/reorder, parameter persistence, and stack preset value serialization/load. `RuntimeStore` keeps file IO and facade methods for existing callers.
|
||||||
|
|
||||||
The current codebase has begun the render snapshot split: `RenderSnapshotBuilder` owns render-state assembly, cached parameter refresh, dynamic frame-field refresh, and render snapshot versions. `RuntimeSnapshotProvider` depends on this builder rather than on `RuntimeStore` friendship.
|
The current codebase has completed the render snapshot split: `RenderSnapshotBuilder` owns render-state assembly, cached parameter refresh, dynamic frame-field refresh, and render snapshot versions. `RuntimeSnapshotProvider` depends on this builder rather than on `RuntimeStore` friendship.
|
||||||
|
|
||||||
The current codebase has also begun the presentation split: `RuntimeStatePresenter` owns top-level runtime-state JSON assembly, while `RuntimeStateJson` owns the layer-stack and parameter presentation shape used by runtime state clients.
|
The current codebase has also completed the presentation split: `RuntimeStatePresenter` owns top-level runtime-state JSON assembly, while `RuntimeStateJson` owns the layer-stack and parameter presentation shape used by runtime state clients.
|
||||||
|
|
||||||
The current codebase has also begun the package split: `ShaderPackageCatalog` owns package scanning and registry comparison, while `RuntimeStore` uses it to keep layer state valid and to build compatibility read models.
|
The current codebase has also completed the package split: `ShaderPackageCatalog` owns package scanning and registry comparison, while `RuntimeStore` uses it to keep layer state valid and to build compatibility read models.
|
||||||
|
|
||||||
These can still be presented through one subsystem façade during migration.
|
These can still be presented through one subsystem façade during migration.
|
||||||
|
|
||||||
@@ -413,7 +413,7 @@ The safest migration path is to extract responsibilities by interface, not by bi
|
|||||||
|
|
||||||
### Step 1: Introduce The `RuntimeStore` Name And Facade
|
### Step 1: Introduce The `RuntimeStore` Name And Facade
|
||||||
|
|
||||||
Create a façade interface that wraps the durable-data parts of `RuntimeHost`.
|
Create a facade interface for the durable-data parts that used to live in `RuntimeHost`.
|
||||||
|
|
||||||
Initial likely contents:
|
Initial likely contents:
|
||||||
|
|
||||||
@@ -422,7 +422,7 @@ Initial likely contents:
|
|||||||
- preset load/save access
|
- preset load/save access
|
||||||
- package catalog read access
|
- package catalog read access
|
||||||
|
|
||||||
This stage is now past the initial compatibility point: `RuntimeStore` owns its durable/session backing fields directly rather than wrapping a `RuntimeHost` object.
|
This stage is complete: `RuntimeStore` owns its durable/session backing fields directly rather than wrapping a `RuntimeHost` object.
|
||||||
|
|
||||||
### Step 2: Move Pure Persistence Helpers First
|
### Step 2: Move Pure Persistence Helpers First
|
||||||
|
|
||||||
@@ -587,4 +587,4 @@ It should not answer:
|
|||||||
- how hardware pacing should work
|
- how hardware pacing should work
|
||||||
- what health warnings should be emitted
|
- what health warnings should be emitted
|
||||||
|
|
||||||
If this boundary holds, later phases can safely split `RuntimeHost` without just recreating the same coupling under a different class name.
|
If this boundary holds, later phases can continue without recreating the old coupling under a different class name.
|
||||||
|
|||||||
@@ -572,11 +572,11 @@ The most important migration is:
|
|||||||
|
|
||||||
### Previous Runtime Status Updates
|
### Previous Runtime Status Updates
|
||||||
|
|
||||||
Frame pacing and signal status setters currently called from the bridge should ultimately be routed through:
|
Frame pacing and signal status setters that were historically called from the bridge should route through:
|
||||||
|
|
||||||
- `VideoBackend -> HealthTelemetry`
|
- `VideoBackend -> HealthTelemetry`
|
||||||
|
|
||||||
rather than:
|
rather than the old pattern:
|
||||||
|
|
||||||
- callback/bridge -> `RuntimeHost`
|
- callback/bridge -> `RuntimeHost`
|
||||||
|
|
||||||
|
|||||||
200
tests/RuntimeSubsystemTests.cpp
Normal file
200
tests/RuntimeSubsystemTests.cpp
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
#include "LayerStackStore.h"
|
||||||
|
#include "RuntimeStateJson.h"
|
||||||
|
#include "ShaderPackageCatalog.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int gFailures = 0;
|
||||||
|
|
||||||
|
void Expect(bool condition, const char* message)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::cerr << "FAIL: " << message << "\n";
|
||||||
|
++gFailures;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path MakeTestRoot()
|
||||||
|
{
|
||||||
|
const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
|
std::filesystem::path root = std::filesystem::temp_directory_path() / ("video-shader-runtime-subsystem-tests-" + std::to_string(stamp));
|
||||||
|
std::filesystem::create_directories(root);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteFile(const std::filesystem::path& path, const std::string& contents)
|
||||||
|
{
|
||||||
|
std::filesystem::create_directories(path.parent_path());
|
||||||
|
std::ofstream output(path, std::ios::binary);
|
||||||
|
output << contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteShaderPackage(const std::filesystem::path& root, const std::string& directoryName, const std::string& manifest)
|
||||||
|
{
|
||||||
|
const std::filesystem::path packageRoot = root / directoryName;
|
||||||
|
WriteFile(packageRoot / "shader.json", manifest);
|
||||||
|
WriteFile(packageRoot / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderPackageCatalog BuildCatalog(const std::filesystem::path& root)
|
||||||
|
{
|
||||||
|
ShaderPackageCatalog catalog;
|
||||||
|
std::string error;
|
||||||
|
Expect(catalog.Scan(root, 4, error), "shader package catalog scans test packages");
|
||||||
|
Expect(error.empty(), "catalog scan does not report an error");
|
||||||
|
return catalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestLayerDefaultsAndCrud()
|
||||||
|
{
|
||||||
|
const std::filesystem::path root = MakeTestRoot();
|
||||||
|
WriteShaderPackage(root, "alpha", R"({
|
||||||
|
"id": "alpha",
|
||||||
|
"name": "Alpha",
|
||||||
|
"parameters": [
|
||||||
|
{ "id": "gain", "label": "Gain", "type": "float", "default": 0.25, "min": 0, "max": 1 },
|
||||||
|
{ "id": "enabled", "label": "Enabled", "type": "bool", "default": true }
|
||||||
|
]
|
||||||
|
})");
|
||||||
|
WriteShaderPackage(root, "beta", R"({
|
||||||
|
"id": "beta",
|
||||||
|
"name": "Beta",
|
||||||
|
"parameters": [
|
||||||
|
{ "id": "mode", "label": "Mode", "type": "enum", "default": "soft", "options": [
|
||||||
|
{ "value": "soft", "label": "Soft" },
|
||||||
|
{ "value": "hard", "label": "Hard" }
|
||||||
|
] }
|
||||||
|
]
|
||||||
|
})");
|
||||||
|
|
||||||
|
ShaderPackageCatalog catalog = BuildCatalog(root);
|
||||||
|
LayerStackStore layers;
|
||||||
|
std::string error;
|
||||||
|
Expect(layers.CreateLayer(catalog, "alpha", error), "layer store creates a layer for a known shader");
|
||||||
|
Expect(layers.LayerCount() == 1, "created layer is stored");
|
||||||
|
Expect(!layers.CreateLayer(catalog, "missing", error), "layer store rejects unknown shaders");
|
||||||
|
|
||||||
|
LayerStackStore::StoredParameterSnapshot snapshot;
|
||||||
|
Expect(layers.TryGetParameterById(catalog, layers.Layers()[0].id, "gain", snapshot, error), "parameter lookup by id succeeds");
|
||||||
|
Expect(snapshot.currentValue.numberValues.size() == 1 && snapshot.currentValue.numberValues[0] == 0.25, "default float value is persisted");
|
||||||
|
|
||||||
|
ShaderParameterValue value;
|
||||||
|
value.numberValues = { 0.75 };
|
||||||
|
Expect(layers.SetParameterValue(layers.Layers()[0].id, "gain", value, error), "parameter value can be updated");
|
||||||
|
Expect(layers.TryGetParameterById(catalog, layers.Layers()[0].id, "gain", snapshot, error), "updated parameter can be read");
|
||||||
|
Expect(snapshot.currentValue.numberValues.size() == 1 && snapshot.currentValue.numberValues[0] == 0.75, "updated float value is retained");
|
||||||
|
|
||||||
|
Expect(layers.SetLayerShaderSelection(catalog, layers.Layers()[0].id, "beta", error), "layer shader selection can change");
|
||||||
|
Expect(layers.Layers()[0].shaderId == "beta", "new shader id is stored");
|
||||||
|
Expect(layers.TryGetParameterById(catalog, layers.Layers()[0].id, "mode", snapshot, error), "new shader defaults are applied");
|
||||||
|
Expect(snapshot.currentValue.enumValue == "soft", "enum default is applied after shader change");
|
||||||
|
|
||||||
|
std::filesystem::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestMoveClassificationAndPresetLoad()
|
||||||
|
{
|
||||||
|
const std::filesystem::path root = MakeTestRoot();
|
||||||
|
WriteShaderPackage(root, "alpha", R"({
|
||||||
|
"id": "alpha",
|
||||||
|
"name": "Alpha",
|
||||||
|
"parameters": [{ "id": "gain", "label": "Gain", "type": "float", "default": 0.5, "min": 0, "max": 1 }]
|
||||||
|
})");
|
||||||
|
WriteShaderPackage(root, "beta", R"({
|
||||||
|
"id": "beta",
|
||||||
|
"name": "Beta",
|
||||||
|
"parameters": []
|
||||||
|
})");
|
||||||
|
|
||||||
|
ShaderPackageCatalog catalog = BuildCatalog(root);
|
||||||
|
LayerStackStore layers;
|
||||||
|
std::string error;
|
||||||
|
Expect(layers.CreateLayer(catalog, "alpha", error), "first test layer is created");
|
||||||
|
Expect(layers.CreateLayer(catalog, "beta", error), "second test layer is created");
|
||||||
|
const std::string firstLayerId = layers.Layers()[0].id;
|
||||||
|
const std::string secondLayerId = layers.Layers()[1].id;
|
||||||
|
|
||||||
|
bool shouldMove = true;
|
||||||
|
Expect(layers.ResolveLayerMove(firstLayerId, -1, shouldMove, error), "top layer move up is classified");
|
||||||
|
Expect(!shouldMove, "top layer move up is a no-op");
|
||||||
|
Expect(layers.ResolveLayerMove(firstLayerId, 1, shouldMove, error), "top layer move down is classified");
|
||||||
|
Expect(shouldMove, "top layer move down should move");
|
||||||
|
Expect(layers.MoveLayer(firstLayerId, 1, error), "top layer moves down");
|
||||||
|
Expect(layers.Layers()[0].id == secondLayerId && layers.Layers()[1].id == firstLayerId, "layer order changed after move");
|
||||||
|
|
||||||
|
JsonValue preset = layers.BuildStackPresetValue(catalog, "Look One");
|
||||||
|
LayerStackStore loaded;
|
||||||
|
Expect(loaded.LoadStackPresetValue(catalog, preset, error), "stack preset value loads into a fresh layer store");
|
||||||
|
Expect(loaded.LayerCount() == 2, "loaded preset preserves layer count");
|
||||||
|
Expect(loaded.Layers()[0].shaderId == "beta" && loaded.Layers()[1].shaderId == "alpha", "loaded preset preserves shader order");
|
||||||
|
Expect(loaded.Layers()[0].id != secondLayerId, "loaded preset generates fresh layer ids");
|
||||||
|
|
||||||
|
std::filesystem::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestRuntimeStateJsonReadModelSerialization()
|
||||||
|
{
|
||||||
|
ShaderPackage package;
|
||||||
|
package.id = "alpha";
|
||||||
|
package.displayName = "Alpha";
|
||||||
|
package.feedback.enabled = true;
|
||||||
|
package.feedback.writePassId = "main";
|
||||||
|
package.temporal.enabled = true;
|
||||||
|
package.temporal.historySource = TemporalHistorySource::Source;
|
||||||
|
package.temporal.requestedHistoryLength = 3;
|
||||||
|
package.temporal.effectiveHistoryLength = 3;
|
||||||
|
|
||||||
|
ShaderParameterDefinition gain;
|
||||||
|
gain.id = "gain";
|
||||||
|
gain.label = "Gain";
|
||||||
|
gain.type = ShaderParameterType::Float;
|
||||||
|
gain.defaultNumbers = { 0.5 };
|
||||||
|
gain.minNumbers = { 0.0 };
|
||||||
|
gain.maxNumbers = { 1.0 };
|
||||||
|
package.parameters.push_back(gain);
|
||||||
|
|
||||||
|
LayerStackStore::LayerPersistentState layer;
|
||||||
|
layer.id = "layer-1";
|
||||||
|
layer.shaderId = "alpha";
|
||||||
|
ShaderParameterValue gainValue;
|
||||||
|
gainValue.numberValues = { 0.8 };
|
||||||
|
layer.parameterValues["gain"] = gainValue;
|
||||||
|
|
||||||
|
JsonValue layersJson = RuntimeStateJson::SerializeLayerStack({ layer }, { { "alpha", package } });
|
||||||
|
Expect(layersJson.isArray() && layersJson.asArray().size() == 1, "runtime state layer serialization emits one layer");
|
||||||
|
|
||||||
|
const JsonValue& layerJson = layersJson.asArray()[0];
|
||||||
|
Expect(layerJson.find("shaderName") && layerJson.find("shaderName")->asString() == "Alpha", "serialized layer includes shader display name");
|
||||||
|
Expect(layerJson.find("temporal") && layerJson.find("temporal")->isObject(), "serialized layer includes temporal metadata");
|
||||||
|
Expect(layerJson.find("feedback") && layerJson.find("feedback")->isObject(), "serialized layer includes feedback metadata");
|
||||||
|
|
||||||
|
const JsonValue* parameters = layerJson.find("parameters");
|
||||||
|
Expect(parameters && parameters->isArray() && parameters->asArray().size() == 1, "serialized layer includes parameter metadata");
|
||||||
|
const JsonValue* value = parameters->asArray()[0].find("value");
|
||||||
|
Expect(value && value->asNumber() == 0.8, "serialized parameter includes current value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
TestLayerDefaultsAndCrud();
|
||||||
|
TestMoveClassificationAndPresetLoad();
|
||||||
|
TestRuntimeStateJsonReadModelSerialization();
|
||||||
|
|
||||||
|
if (gFailures != 0)
|
||||||
|
{
|
||||||
|
std::cerr << gFailures << " RuntimeSubsystem test failure(s).\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "RuntimeSubsystem tests passed.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user