refactor(app): close ordered startup composition
This commit is contained in:
parent
54244d31f1
commit
530b4bd8f5
16 changed files with 537 additions and 215 deletions
12
AGENTS.md
12
AGENTS.md
|
|
@ -150,10 +150,14 @@ lifecycle/reconnect gate, and 395.2-second synchronized nine-stop soak pass.
|
||||||
Checkpoint H adds sole terrain/sky render-resource roots, one retained
|
Checkpoint H adds sole terrain/sky render-resource roots, one retained
|
||||||
Host/runtime lease, an atomic update/render frame-root pair, prepared portal
|
Host/runtime lease, an atomic update/render frame-root pair, prepared portal
|
||||||
fallback/transfer, and checked retryable GL construction/state boundaries.
|
fallback/transfer, and checked retryable GL construction/state boundaries.
|
||||||
`GameWindow` is 3,689 raw lines / 162 fields / 37 methods, down 12,034 lines
|
Checkpoint I now routes production through the same executable nine-phase
|
||||||
(76.5%) from baseline. All three final corrected-diff reviews are clean. Slice
|
pipeline as its failure oracle, carries exact platform/settings results through
|
||||||
8 checkpoints I–L—ordered composition, retryable shutdown, canonical soak
|
typed phase inputs, atomically publishes frame roots, and makes session start
|
||||||
snapshots, and final connected gates—remain active.
|
terminal. `GameWindow` is 1,945 raw lines, down 13,778 lines (87.6%) from
|
||||||
|
baseline. App Release passes 3,431 tests / 3 skips and the complete Release
|
||||||
|
suite passes 7,803 / 5 skips. Checkpoint I is complete; checkpoints J–L—focused
|
||||||
|
retryable shutdown, canonical soak snapshots, and final connected gates—remain
|
||||||
|
active.
|
||||||
See `docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md` and
|
See `docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md` and
|
||||||
`docs/architecture/code-structure.md`. **Carried:** #232, #153,
|
`docs/architecture/code-structure.md`. **Carried:** #232, #153,
|
||||||
#116, remaining
|
#116, remaining
|
||||||
|
|
|
||||||
12
CLAUDE.md
12
CLAUDE.md
|
|
@ -148,10 +148,14 @@ lifecycle/reconnect gate, and 395.2-second synchronized nine-stop soak pass.
|
||||||
Checkpoint H adds sole terrain/sky render-resource roots, one retained
|
Checkpoint H adds sole terrain/sky render-resource roots, one retained
|
||||||
Host/runtime lease, an atomic update/render frame-root pair, prepared portal
|
Host/runtime lease, an atomic update/render frame-root pair, prepared portal
|
||||||
fallback/transfer, and checked retryable GL construction/state boundaries.
|
fallback/transfer, and checked retryable GL construction/state boundaries.
|
||||||
`GameWindow` is 3,689 raw lines / 162 fields / 37 methods, down 12,034 lines
|
Checkpoint I now routes production through the same executable nine-phase
|
||||||
(76.5%) from baseline. All three final corrected-diff reviews are clean. Slice
|
pipeline as its failure oracle, carries exact platform/settings results through
|
||||||
8 checkpoints I–L—ordered composition, retryable shutdown, canonical soak
|
typed phase inputs, atomically publishes frame roots, and makes session start
|
||||||
snapshots, and final connected gates—remain active.
|
terminal. `GameWindow` is 1,945 raw lines, down 13,778 lines (87.6%) from
|
||||||
|
baseline. App Release passes 3,431 tests / 3 skips and the complete Release
|
||||||
|
suite passes 7,803 / 5 skips. Checkpoint I is complete; checkpoints J–L—focused
|
||||||
|
retryable shutdown, canonical soak snapshots, and final connected gates—remain
|
||||||
|
active.
|
||||||
See `docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md` and
|
See `docs/plans/2026-07-22-gamewindow-slice-8-composition-lifecycle.md` and
|
||||||
`docs/architecture/code-structure.md`. **Carried:** #232, #153,
|
`docs/architecture/code-structure.md`. **Carried:** #232, #153,
|
||||||
#116, remaining
|
#116, remaining
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,11 @@ What does NOT go here:
|
||||||
command targets, and sole gameplay input subscriber before frame publication.
|
command targets, and sole gameplay input subscriber before frame publication.
|
||||||
I.8b now owns the complete update/render construction, exact atomic frame
|
I.8b now owns the complete update/render construction, exact atomic frame
|
||||||
publication, and lifecycle resource snapshot source. I.8c makes session
|
publication, and lifecycle resource snapshot source. I.8c makes session
|
||||||
start the terminal `OnLoad` operation. `GameWindow.OnUpdate` and
|
start the terminal `OnLoad` operation. I.9 corrected the production root to
|
||||||
`GameWindow.OnRender` are typed orchestration handoffs and the class is
|
invoke the exact tested pipeline and made every consumed platform/settings
|
||||||
1,910 raw lines. I.9 plus the remaining
|
result explicit. `GameWindow.OnUpdate` and `GameWindow.OnRender` are typed
|
||||||
shutdown/closeout
|
orchestration handoffs and the class is 1,945 raw lines. Checkpoint I is
|
||||||
cleanup is active in
|
complete; the shutdown/lifetime cutover and closeout remain active in
|
||||||
[`docs/architecture/code-structure.md`](architecture/code-structure.md).
|
[`docs/architecture/code-structure.md`](architecture/code-structure.md).
|
||||||
This is the behavior-preserving prerequisite before new M4 feature bodies.
|
This is the behavior-preserving prerequisite before new M4 feature bodies.
|
||||||
- **Connected-gate fidelity:** `#232` adds canonical managed/GPU/cache-owner
|
- **Connected-gate fidelity:** `#232` adds canonical managed/GPU/cache-owner
|
||||||
|
|
|
||||||
|
|
@ -739,12 +739,14 @@ diagnostic command targets, and sole gameplay input subscriber into Phase 7
|
||||||
before frame publication. I.8b moves the complete update/render construction
|
before frame publication. I.8b moves the complete update/render construction
|
||||||
body into `FrameRootCompositionPhase`, publishes the pair through an exact
|
body into `FrameRootCompositionPhase`, publishes the pair through an exact
|
||||||
lease, and gives lifecycle resource sampling a focused source. `GameWindow` is
|
lease, and gives lifecycle resource sampling a focused source. `GameWindow` is
|
||||||
1,910 raw lines after I.8c makes live-session start the terminal `OnLoad`
|
1,945 raw lines after I.9 corrects production to invoke the same executable
|
||||||
operation. I.9–L remain active, with the complete Checkpoint-I audit next. The
|
pipeline as the failure oracle, carries exact platform/settings results through
|
||||||
App gate passes 3,430 tests / 3 intentional skips and the complete Release suite
|
the typed contract, and keeps live-session start terminal. Checkpoint I is
|
||||||
passes 7,802 tests / 5 intentional skips. The clean solution build retains only
|
complete; J–L remain active with lifetime/shutdown extraction next. The App gate
|
||||||
the 17 test-project warnings tracked by #228; the I.8c corrected-diff review is
|
passes 3,431 tests / 3 intentional skips and the complete Release suite passes
|
||||||
clean.
|
7,803 tests / 5 intentional skips. The clean solution build retains only the 17
|
||||||
|
test-project warnings tracked by #228; the complete Checkpoint-I behavior,
|
||||||
|
architecture, and adversarial corrected-diff passes are clean.
|
||||||
|
|
||||||
### 4.4 Exit criteria
|
### 4.4 Exit criteria
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,11 +104,12 @@ controllers, selection/radar, landblock publishers, and portal/sky/particle
|
||||||
resources have transactional phase ownership and exact-owner late bindings.
|
resources have transactional phase ownership and exact-owner late bindings.
|
||||||
I.7 adds the complete streaming/session/hydration/local-player/teleport phase,
|
I.7 adds the complete streaming/session/hydration/local-player/teleport phase,
|
||||||
including explicit reversible ownership for every late edge. `GameWindow` is
|
including explicit reversible ownership for every late edge. `GameWindow` is
|
||||||
now 1,910 raw lines after I.8c, which makes session start the terminal `OnLoad`
|
now 1,945 raw lines after I.9 makes production invoke the same typed pipeline
|
||||||
operation. I.9, shutdown, canonical soak snapshots, and closeout checkpoints
|
as the failure oracle and keeps session start terminal. Checkpoint I is
|
||||||
J–L remain. The App gate passes 3,430 tests / 3 intentional skips, the complete
|
complete; shutdown, canonical soak snapshots, and closeout checkpoints J–L
|
||||||
Release suite passes 7,802 tests / 5 skips, and the I.8c corrected-diff review
|
remain. The App gate passes 3,431 tests / 3 intentional skips, the complete
|
||||||
is clean.
|
Release suite passes 7,803 tests / 5 skips, and the complete Checkpoint-I
|
||||||
|
corrected-diff review is clean.
|
||||||
|
|
||||||
This is a behavior-preserving structural program. Severe regressions still get
|
This is a behavior-preserving structural program. Severe regressions still get
|
||||||
root-cause fixes in separate commits; ordinary feature work resumes with M4
|
root-cause fixes in separate commits; ordinary feature work resumes with M4
|
||||||
|
|
|
||||||
|
|
@ -63,11 +63,13 @@ named reversible late edges. I.8a adds the focused live-session runtime factory
|
||||||
and attaches command/input owners before frame publication. I.8b composes and
|
and attaches command/input owners before frame publication. I.8b composes and
|
||||||
atomically publishes the complete frame pair and extracts lifecycle resource
|
atomically publishes the complete frame pair and extracts lifecycle resource
|
||||||
sampling. I.8c makes session start the terminal `OnLoad` operation.
|
sampling. I.8c makes session start the terminal `OnLoad` operation.
|
||||||
`GameWindow` is 1,910 raw lines. I.9 and checkpoints J–L remain active. Issue
|
`GameWindow` is 1,945 raw lines after I.9 routes production through the exact
|
||||||
#232 tracks
|
tested pipeline with explicit prior-result dependencies. Checkpoint I is
|
||||||
|
complete and checkpoints J–L remain active. Issue #232 tracks
|
||||||
process-residency variance in the soak without loosening its leak threshold.
|
process-residency variance in the soak without loosening its leak threshold.
|
||||||
The App gate passes 3,430 tests / 3 skips, the complete Release suite passes
|
The App gate passes 3,431 tests / 3 skips, the complete Release suite passes
|
||||||
7,802 tests / 5 skips, and the I.8c corrected-diff review is clean.
|
7,803 tests / 5 skips, and the complete Checkpoint-I corrected-diff review is
|
||||||
|
clean.
|
||||||
|
|
||||||
Carried:
|
Carried:
|
||||||
#153 far-teleport residual, #116 slide-response, the remaining R6 ownership cleanup plus
|
#153 far-teleport residual, #116 slide-response, the remaining R6 ownership cleanup plus
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# GameWindow Slice 8 Checkpoint I — ordered production composition
|
# GameWindow Slice 8 Checkpoint I — ordered production composition
|
||||||
|
|
||||||
**Status:** Active 2026-07-22.
|
**Status:** Complete 2026-07-22.
|
||||||
**Parent:**
|
**Parent:**
|
||||||
[`2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](2026-07-22-gamewindow-slice-8-composition-lifecycle.md),
|
[`2026-07-22-gamewindow-slice-8-composition-lifecycle.md`](2026-07-22-gamewindow-slice-8-composition-lifecycle.md),
|
||||||
Checkpoint I.
|
Checkpoint I.
|
||||||
|
|
@ -299,9 +299,19 @@ ledger only after the complete pipeline is live.
|
||||||
the pair through an exact owned slot lease, and moves lifecycle resource
|
the pair through an exact owned slot lease, and moves lifecycle resource
|
||||||
sampling out of the window. I.8c makes the exact session start and its
|
sampling out of the window. I.8c makes the exact session start and its
|
||||||
existing diagnostics the terminal `OnLoad` operation. `GameWindow.cs` is
|
existing diagnostics the terminal `OnLoad` operation. `GameWindow.cs` is
|
||||||
1,910 raw lines. I.9 corrected-diff review and documentation closeout is the
|
1,910 raw lines.
|
||||||
active cut.
|
- I.9 found and corrected one structural defect: production manually repeated
|
||||||
Checkpoint I remains active until I.9 closes the complete nine-phase pipeline.
|
the phase order instead of invoking the executable pipeline used by failure
|
||||||
|
tests, and several later phases received real platform/settings results only
|
||||||
|
through surrounding captures. Production now enters the same fixed pipeline;
|
||||||
|
platform and settings are explicit typed phase inputs and concrete phases
|
||||||
|
reject mismatched result instances. Session start remains the terminal
|
||||||
|
operation. Three corrected-diff passes covering behavior order, architecture/
|
||||||
|
ownership, and adversarial failure/reentrancy are clean. `GameWindow.cs` is
|
||||||
|
1,945 raw lines. App Release passes 3,431 tests / 3 intentional skips; the
|
||||||
|
complete Release suite passes 7,803 tests / 5 intentional skips with only the
|
||||||
|
17 pre-existing test-project warnings tracked by #228. No retail behavior or
|
||||||
|
divergence changed. Checkpoint I is complete; Checkpoint J is active.
|
||||||
|
|
||||||
## 6. Automated acceptance
|
## 6. Automated acceptance
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ audit.
|
||||||
- [x] G — extract two-phase persisted settings/display/quality ownership.
|
- [x] G — extract two-phase persisted settings/display/quality ownership.
|
||||||
- [x] H — give terrain atlas, sky shader, retained `UiHost`, and both frame roots
|
- [x] H — give terrain atlas, sky shader, retained `UiHost`, and both frame roots
|
||||||
explicit single ownership and transfer seams.
|
explicit single ownership and transfer seams.
|
||||||
- [ ] I — group `OnLoad` into small ordered, fakeable composition phases with
|
- [x] I — group `OnLoad` into small ordered, fakeable composition phases with
|
||||||
transactional partial-acquisition rollback. I.1–I.8 are complete; I.9
|
transactional partial-acquisition rollback. Production and failure tests now
|
||||||
corrected-diff review and checkpoint closeout is active. Detailed plan:
|
invoke the same pipeline and carry exact typed prior results. Detailed plan:
|
||||||
[`2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`](2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md).
|
[`2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`](2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md).
|
||||||
- [ ] J — move the exact retryable shutdown manifest to a focused lifetime
|
- [ ] J — move the exact retryable shutdown manifest to a focused lifetime
|
||||||
owner and prove all partial-load/reentrant/retry paths.
|
owner and prove all partial-load/reentrant/retry paths.
|
||||||
|
|
@ -627,6 +627,16 @@ corrected-diff reviews are clean. The complete Release suite passes 7,606 tests
|
||||||
teleport transfer, and frame-root publication. Each concrete phase also
|
teleport transfer, and frame-root publication. Each concrete phase also
|
||||||
proves transactional rollback of its internal acquisition prefix.
|
proves transactional rollback of its internal acquisition prefix.
|
||||||
|
|
||||||
|
Result: all nine production phases now run through the same executable pipeline
|
||||||
|
as the prefix/failure oracle. Platform and settings results are explicit typed
|
||||||
|
inputs wherever consumed; phases reject mismatched instances, all long-lived
|
||||||
|
owners publish to focused lifetime slots, the frame pair publishes atomically,
|
||||||
|
and session start is terminal. `GameWindow.cs` is 1,945 raw lines. Three
|
||||||
|
corrected-diff passes are clean; App Release passes 3,431 tests / 3 skips and
|
||||||
|
the complete Release suite passes 7,803 tests / 5 skips with the existing 17
|
||||||
|
#228 warnings. No connected gate was required for the final structural
|
||||||
|
correction; Checkpoint L retains the connected lifecycle and soak gates.
|
||||||
|
|
||||||
### J — lifetime cutover and shutdown failure policy
|
### J — lifetime cutover and shutdown failure policy
|
||||||
|
|
||||||
- Move the teardown-only stage records and shutdown manifest out of GameWindow.
|
- Move the teardown-only stage records and shutdown manifest out of GameWindow.
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ internal sealed class FrameRootRuntimeBindings : IDisposable
|
||||||
|
|
||||||
internal sealed class FrameRootCompositionPhase
|
internal sealed class FrameRootCompositionPhase
|
||||||
: IFrameRootCompositionPhase<
|
: IFrameRootCompositionPhase<
|
||||||
|
GameWindowPlatformResult<GL, IInputContext>,
|
||||||
HostInputCameraResult,
|
HostInputCameraResult,
|
||||||
ContentEffectsAudioResult,
|
ContentEffectsAudioResult,
|
||||||
SettingsDevToolsResult,
|
SettingsDevToolsResult,
|
||||||
|
|
@ -153,6 +154,7 @@ internal sealed class FrameRootCompositionPhase
|
||||||
}
|
}
|
||||||
|
|
||||||
public FrameRootResult Compose(
|
public FrameRootResult Compose(
|
||||||
|
GameWindowPlatformResult<GL, IInputContext> platform,
|
||||||
HostInputCameraResult host,
|
HostInputCameraResult host,
|
||||||
ContentEffectsAudioResult content,
|
ContentEffectsAudioResult content,
|
||||||
SettingsDevToolsResult settings,
|
SettingsDevToolsResult settings,
|
||||||
|
|
@ -161,6 +163,7 @@ internal sealed class FrameRootCompositionPhase
|
||||||
LivePresentationResult live,
|
LivePresentationResult live,
|
||||||
SessionPlayerResult session)
|
SessionPlayerResult session)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(platform);
|
||||||
ArgumentNullException.ThrowIfNull(host);
|
ArgumentNullException.ThrowIfNull(host);
|
||||||
ArgumentNullException.ThrowIfNull(content);
|
ArgumentNullException.ThrowIfNull(content);
|
||||||
ArgumentNullException.ThrowIfNull(settings);
|
ArgumentNullException.ThrowIfNull(settings);
|
||||||
|
|
@ -168,6 +171,12 @@ internal sealed class FrameRootCompositionPhase
|
||||||
ArgumentNullException.ThrowIfNull(interaction);
|
ArgumentNullException.ThrowIfNull(interaction);
|
||||||
ArgumentNullException.ThrowIfNull(live);
|
ArgumentNullException.ThrowIfNull(live);
|
||||||
ArgumentNullException.ThrowIfNull(session);
|
ArgumentNullException.ThrowIfNull(session);
|
||||||
|
if (!ReferenceEquals(_dependencies.Gl, platform.Graphics)
|
||||||
|
|| !ReferenceEquals(_dependencies.Input, platform.Input))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Frame-root dependencies do not match the ordered platform result.");
|
||||||
|
}
|
||||||
|
|
||||||
var scope = new CompositionAcquisitionScope();
|
var scope = new CompositionAcquisitionScope();
|
||||||
FrameRootRuntimeBindings? bindings = null;
|
FrameRootRuntimeBindings? bindings = null;
|
||||||
|
|
|
||||||
|
|
@ -20,29 +20,37 @@ internal interface IWorldRenderCompositionPhase<in TPlatform, in TContent, in TS
|
||||||
TResult Compose(TPlatform platform, TContent content, TSettings settings);
|
TResult Compose(TPlatform platform, TContent content, TSettings settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface IInteractionUiCompositionPhase<in THost, in TContent, in TSettings, in TWorld, out TResult>
|
internal interface IInteractionUiCompositionPhase<in TPlatform, in THost, in TContent, in TSettings, in TWorld, out TResult>
|
||||||
{
|
{
|
||||||
TResult Compose(THost host, TContent content, TSettings settings, TWorld world);
|
TResult Compose(TPlatform platform, THost host, TContent content, TSettings settings, TWorld world);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface ILivePresentationCompositionPhase<in THost, in TContent, in TWorld, in TInteraction, out TResult>
|
internal interface ILivePresentationCompositionPhase<in TPlatform, in THost, in TContent, in TSettings, in TWorld, in TInteraction, out TResult>
|
||||||
{
|
{
|
||||||
TResult Compose(THost host, TContent content, TWorld world, TInteraction interaction);
|
TResult Compose(
|
||||||
|
TPlatform platform,
|
||||||
|
THost host,
|
||||||
|
TContent content,
|
||||||
|
TSettings settings,
|
||||||
|
TWorld world,
|
||||||
|
TInteraction interaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface ISessionPlayerCompositionPhase<in THost, in TContent, in TWorld, in TInteraction, in TLive, out TResult>
|
internal interface ISessionPlayerCompositionPhase<in THost, in TContent, in TSettings, in TWorld, in TInteraction, in TLive, out TResult>
|
||||||
{
|
{
|
||||||
TResult Compose(
|
TResult Compose(
|
||||||
THost host,
|
THost host,
|
||||||
TContent content,
|
TContent content,
|
||||||
|
TSettings settings,
|
||||||
TWorld world,
|
TWorld world,
|
||||||
TInteraction interaction,
|
TInteraction interaction,
|
||||||
TLive live);
|
TLive live);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface IFrameRootCompositionPhase<in THost, in TContent, in TSettings, in TWorld, in TInteraction, in TLive, in TSession, out TResult>
|
internal interface IFrameRootCompositionPhase<in TPlatform, in THost, in TContent, in TSettings, in TWorld, in TInteraction, in TLive, in TSession, out TResult>
|
||||||
{
|
{
|
||||||
TResult Compose(
|
TResult Compose(
|
||||||
|
TPlatform platform,
|
||||||
THost host,
|
THost host,
|
||||||
TContent content,
|
TContent content,
|
||||||
TSettings settings,
|
TSettings settings,
|
||||||
|
|
@ -57,6 +65,149 @@ internal interface ISessionStartCompositionPhase<in TFrame>
|
||||||
void Start(TFrame frame);
|
void Start(TFrame frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronous production entry point for the fixed composition topology.
|
||||||
|
/// The delegates exist only for the duration of <see cref="Run"/>; this lets
|
||||||
|
/// the native host remain the write-only publication shell without retaining
|
||||||
|
/// it in any phase owner.
|
||||||
|
/// </summary>
|
||||||
|
internal static class GameWindowCompositionPipeline
|
||||||
|
{
|
||||||
|
public static void Run<
|
||||||
|
TPlatform,
|
||||||
|
THost,
|
||||||
|
TContent,
|
||||||
|
TSettings,
|
||||||
|
TWorld,
|
||||||
|
TInteraction,
|
||||||
|
TLive,
|
||||||
|
TSession,
|
||||||
|
TFrame>(
|
||||||
|
TPlatform platform,
|
||||||
|
Func<TPlatform, THost> host,
|
||||||
|
Func<TPlatform, THost, TContent> content,
|
||||||
|
Func<TPlatform, THost, TContent, TSettings> settings,
|
||||||
|
Func<TPlatform, TContent, TSettings, TWorld> world,
|
||||||
|
Func<TPlatform, THost, TContent, TSettings, TWorld, TInteraction> interaction,
|
||||||
|
Func<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive> live,
|
||||||
|
Func<THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession> session,
|
||||||
|
Func<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> frame,
|
||||||
|
Action<TFrame> start)
|
||||||
|
{
|
||||||
|
new GameWindowCompositionPipeline<
|
||||||
|
TPlatform,
|
||||||
|
THost,
|
||||||
|
TContent,
|
||||||
|
TSettings,
|
||||||
|
TWorld,
|
||||||
|
TInteraction,
|
||||||
|
TLive,
|
||||||
|
TSession,
|
||||||
|
TFrame>(
|
||||||
|
new HostPhase<TPlatform, THost>(host),
|
||||||
|
new ContentPhase<TPlatform, THost, TContent>(content),
|
||||||
|
new SettingsPhase<TPlatform, THost, TContent, TSettings>(settings),
|
||||||
|
new WorldPhase<TPlatform, TContent, TSettings, TWorld>(world),
|
||||||
|
new InteractionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction>(interaction),
|
||||||
|
new LivePhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive>(live),
|
||||||
|
new SessionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession>(session),
|
||||||
|
new FramePhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TFrame>(frame),
|
||||||
|
new StartPhase<TFrame>(start))
|
||||||
|
.Run(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class HostPhase<TPlatform, TResult>(
|
||||||
|
Func<TPlatform, TResult> compose)
|
||||||
|
: IHostInputCameraCompositionPhase<TPlatform, TResult>
|
||||||
|
{
|
||||||
|
public TResult Compose(TPlatform platform) => compose(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ContentPhase<TPlatform, THost, TResult>(
|
||||||
|
Func<TPlatform, THost, TResult> compose)
|
||||||
|
: IContentEffectsAudioCompositionPhase<TPlatform, THost, TResult>
|
||||||
|
{
|
||||||
|
public TResult Compose(TPlatform platform, THost host) => compose(platform, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SettingsPhase<TPlatform, THost, TContent, TResult>(
|
||||||
|
Func<TPlatform, THost, TContent, TResult> compose)
|
||||||
|
: ISettingsDevToolsCompositionPhase<TPlatform, THost, TContent, TResult>
|
||||||
|
{
|
||||||
|
public TResult Compose(TPlatform platform, THost host, TContent content) =>
|
||||||
|
compose(platform, host, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class WorldPhase<TPlatform, TContent, TSettings, TResult>(
|
||||||
|
Func<TPlatform, TContent, TSettings, TResult> compose)
|
||||||
|
: IWorldRenderCompositionPhase<TPlatform, TContent, TSettings, TResult>
|
||||||
|
{
|
||||||
|
public TResult Compose(TPlatform platform, TContent content, TSettings settings) =>
|
||||||
|
compose(platform, content, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class InteractionPhase<TPlatform, THost, TContent, TSettings, TWorld, TResult>(
|
||||||
|
Func<TPlatform, THost, TContent, TSettings, TWorld, TResult> compose)
|
||||||
|
: IInteractionUiCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TResult>
|
||||||
|
{
|
||||||
|
public TResult Compose(
|
||||||
|
TPlatform platform,
|
||||||
|
THost host,
|
||||||
|
TContent content,
|
||||||
|
TSettings settings,
|
||||||
|
TWorld world) => compose(platform, host, content, settings, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class LivePhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TResult>(
|
||||||
|
Func<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TResult> compose)
|
||||||
|
: ILivePresentationCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TResult>
|
||||||
|
{
|
||||||
|
public TResult Compose(
|
||||||
|
TPlatform platform,
|
||||||
|
THost host,
|
||||||
|
TContent content,
|
||||||
|
TSettings settings,
|
||||||
|
TWorld world,
|
||||||
|
TInteraction interaction) =>
|
||||||
|
compose(platform, host, content, settings, world, interaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SessionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TResult>(
|
||||||
|
Func<THost, TContent, TSettings, TWorld, TInteraction, TLive, TResult> compose)
|
||||||
|
: ISessionPlayerCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TResult>
|
||||||
|
{
|
||||||
|
public TResult Compose(
|
||||||
|
THost host,
|
||||||
|
TContent content,
|
||||||
|
TSettings settings,
|
||||||
|
TWorld world,
|
||||||
|
TInteraction interaction,
|
||||||
|
TLive live) => compose(host, content, settings, world, interaction, live);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FramePhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TResult>(
|
||||||
|
Func<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TResult> compose)
|
||||||
|
: IFrameRootCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TResult>
|
||||||
|
{
|
||||||
|
public TResult Compose(
|
||||||
|
TPlatform platform,
|
||||||
|
THost host,
|
||||||
|
TContent content,
|
||||||
|
TSettings settings,
|
||||||
|
TWorld world,
|
||||||
|
TInteraction interaction,
|
||||||
|
TLive live,
|
||||||
|
TSession session) =>
|
||||||
|
compose(platform, host, content, settings, world, interaction, live, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class StartPhase<TFrame>(Action<TFrame> start)
|
||||||
|
: ISessionStartCompositionPhase<TFrame>
|
||||||
|
{
|
||||||
|
public void Start(TFrame frame) => start(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fixed startup topology. It intentionally retains no cumulative context or
|
/// Fixed startup topology. It intentionally retains no cumulative context or
|
||||||
/// final runtime graph; immutable phase results exist only as locals during
|
/// final runtime graph; immutable phase results exist only as locals during
|
||||||
|
|
@ -77,10 +228,10 @@ internal sealed class GameWindowCompositionPipeline<
|
||||||
private readonly IContentEffectsAudioCompositionPhase<TPlatform, THost, TContent> _content;
|
private readonly IContentEffectsAudioCompositionPhase<TPlatform, THost, TContent> _content;
|
||||||
private readonly ISettingsDevToolsCompositionPhase<TPlatform, THost, TContent, TSettings> _settings;
|
private readonly ISettingsDevToolsCompositionPhase<TPlatform, THost, TContent, TSettings> _settings;
|
||||||
private readonly IWorldRenderCompositionPhase<TPlatform, TContent, TSettings, TWorld> _world;
|
private readonly IWorldRenderCompositionPhase<TPlatform, TContent, TSettings, TWorld> _world;
|
||||||
private readonly IInteractionUiCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction> _interaction;
|
private readonly IInteractionUiCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction> _interaction;
|
||||||
private readonly ILivePresentationCompositionPhase<THost, TContent, TWorld, TInteraction, TLive> _live;
|
private readonly ILivePresentationCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive> _live;
|
||||||
private readonly ISessionPlayerCompositionPhase<THost, TContent, TWorld, TInteraction, TLive, TSession> _session;
|
private readonly ISessionPlayerCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession> _session;
|
||||||
private readonly IFrameRootCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> _frame;
|
private readonly IFrameRootCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> _frame;
|
||||||
private readonly ISessionStartCompositionPhase<TFrame> _start;
|
private readonly ISessionStartCompositionPhase<TFrame> _start;
|
||||||
|
|
||||||
public GameWindowCompositionPipeline(
|
public GameWindowCompositionPipeline(
|
||||||
|
|
@ -88,10 +239,10 @@ internal sealed class GameWindowCompositionPipeline<
|
||||||
IContentEffectsAudioCompositionPhase<TPlatform, THost, TContent> content,
|
IContentEffectsAudioCompositionPhase<TPlatform, THost, TContent> content,
|
||||||
ISettingsDevToolsCompositionPhase<TPlatform, THost, TContent, TSettings> settings,
|
ISettingsDevToolsCompositionPhase<TPlatform, THost, TContent, TSettings> settings,
|
||||||
IWorldRenderCompositionPhase<TPlatform, TContent, TSettings, TWorld> world,
|
IWorldRenderCompositionPhase<TPlatform, TContent, TSettings, TWorld> world,
|
||||||
IInteractionUiCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction> interaction,
|
IInteractionUiCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction> interaction,
|
||||||
ILivePresentationCompositionPhase<THost, TContent, TWorld, TInteraction, TLive> live,
|
ILivePresentationCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive> live,
|
||||||
ISessionPlayerCompositionPhase<THost, TContent, TWorld, TInteraction, TLive, TSession> session,
|
ISessionPlayerCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession> session,
|
||||||
IFrameRootCompositionPhase<THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> frame,
|
IFrameRootCompositionPhase<TPlatform, THost, TContent, TSettings, TWorld, TInteraction, TLive, TSession, TFrame> frame,
|
||||||
ISessionStartCompositionPhase<TFrame> start)
|
ISessionStartCompositionPhase<TFrame> start)
|
||||||
{
|
{
|
||||||
_host = host ?? throw new ArgumentNullException(nameof(host));
|
_host = host ?? throw new ArgumentNullException(nameof(host));
|
||||||
|
|
@ -111,10 +262,11 @@ internal sealed class GameWindowCompositionPipeline<
|
||||||
TContent content = _content.Compose(platform, host);
|
TContent content = _content.Compose(platform, host);
|
||||||
TSettings settings = _settings.Compose(platform, host, content);
|
TSettings settings = _settings.Compose(platform, host, content);
|
||||||
TWorld world = _world.Compose(platform, content, settings);
|
TWorld world = _world.Compose(platform, content, settings);
|
||||||
TInteraction interaction = _interaction.Compose(host, content, settings, world);
|
TInteraction interaction = _interaction.Compose(platform, host, content, settings, world);
|
||||||
TLive live = _live.Compose(host, content, world, interaction);
|
TLive live = _live.Compose(platform, host, content, settings, world, interaction);
|
||||||
TSession session = _session.Compose(host, content, world, interaction, live);
|
TSession session = _session.Compose(host, content, settings, world, interaction, live);
|
||||||
TFrame frame = _frame.Compose(
|
TFrame frame = _frame.Compose(
|
||||||
|
platform,
|
||||||
host,
|
host,
|
||||||
content,
|
content,
|
||||||
settings,
|
settings,
|
||||||
|
|
|
||||||
|
|
@ -693,6 +693,7 @@ internal sealed class RetailInteractionRetainedUiCompositionFactory
|
||||||
|
|
||||||
internal sealed class InteractionRetainedUiCompositionPhase
|
internal sealed class InteractionRetainedUiCompositionPhase
|
||||||
: IInteractionUiCompositionPhase<
|
: IInteractionUiCompositionPhase<
|
||||||
|
GameWindowPlatformResult<GL, IInputContext>,
|
||||||
HostInputCameraResult,
|
HostInputCameraResult,
|
||||||
ContentEffectsAudioResult,
|
ContentEffectsAudioResult,
|
||||||
SettingsDevToolsResult,
|
SettingsDevToolsResult,
|
||||||
|
|
@ -805,16 +806,20 @@ internal sealed class InteractionRetainedUiCompositionPhase
|
||||||
}
|
}
|
||||||
|
|
||||||
public InteractionRetainedUiResult Compose(
|
public InteractionRetainedUiResult Compose(
|
||||||
|
GameWindowPlatformResult<GL, IInputContext> platform,
|
||||||
HostInputCameraResult host,
|
HostInputCameraResult host,
|
||||||
ContentEffectsAudioResult content,
|
ContentEffectsAudioResult content,
|
||||||
SettingsDevToolsResult settings,
|
SettingsDevToolsResult settings,
|
||||||
WorldRenderResult world)
|
WorldRenderResult world)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(platform);
|
||||||
ArgumentNullException.ThrowIfNull(host);
|
ArgumentNullException.ThrowIfNull(host);
|
||||||
ArgumentNullException.ThrowIfNull(content);
|
ArgumentNullException.ThrowIfNull(content);
|
||||||
ArgumentNullException.ThrowIfNull(settings);
|
ArgumentNullException.ThrowIfNull(settings);
|
||||||
ArgumentNullException.ThrowIfNull(world);
|
ArgumentNullException.ThrowIfNull(world);
|
||||||
if (!ReferenceEquals(_dependencies.InputDispatcher, host.InputDispatcher)
|
if (!ReferenceEquals(_dependencies.Gl, platform.Graphics)
|
||||||
|
|| !ReferenceEquals(_dependencies.Input, platform.Input)
|
||||||
|
|| !ReferenceEquals(_dependencies.InputDispatcher, host.InputDispatcher)
|
||||||
|| !ReferenceEquals(_dependencies.Dats, content.Dats)
|
|| !ReferenceEquals(_dependencies.Dats, content.Dats)
|
||||||
|| !ReferenceEquals(_dependencies.MagicCatalog, content.MagicCatalog)
|
|| !ReferenceEquals(_dependencies.MagicCatalog, content.MagicCatalog)
|
||||||
|| !ReferenceEquals(_dependencies.TextureCache, world.Foundation.TextureCache)
|
|| !ReferenceEquals(_dependencies.TextureCache, world.Foundation.TextureCache)
|
||||||
|
|
|
||||||
|
|
@ -131,8 +131,10 @@ internal enum LivePresentationCompositionPoint
|
||||||
|
|
||||||
internal sealed class LivePresentationCompositionPhase
|
internal sealed class LivePresentationCompositionPhase
|
||||||
: ILivePresentationCompositionPhase<
|
: ILivePresentationCompositionPhase<
|
||||||
|
GameWindowPlatformResult<GL, Silk.NET.Input.IInputContext>,
|
||||||
HostInputCameraResult,
|
HostInputCameraResult,
|
||||||
ContentEffectsAudioResult,
|
ContentEffectsAudioResult,
|
||||||
|
SettingsDevToolsResult,
|
||||||
WorldRenderResult,
|
WorldRenderResult,
|
||||||
InteractionRetainedUiResult,
|
InteractionRetainedUiResult,
|
||||||
LivePresentationResult>
|
LivePresentationResult>
|
||||||
|
|
@ -154,15 +156,24 @@ internal sealed class LivePresentationCompositionPhase
|
||||||
}
|
}
|
||||||
|
|
||||||
public LivePresentationResult Compose(
|
public LivePresentationResult Compose(
|
||||||
|
GameWindowPlatformResult<GL, Silk.NET.Input.IInputContext> platform,
|
||||||
HostInputCameraResult host,
|
HostInputCameraResult host,
|
||||||
ContentEffectsAudioResult content,
|
ContentEffectsAudioResult content,
|
||||||
|
SettingsDevToolsResult settings,
|
||||||
WorldRenderResult world,
|
WorldRenderResult world,
|
||||||
InteractionRetainedUiResult interaction)
|
InteractionRetainedUiResult interaction)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(platform);
|
||||||
ArgumentNullException.ThrowIfNull(host);
|
ArgumentNullException.ThrowIfNull(host);
|
||||||
ArgumentNullException.ThrowIfNull(content);
|
ArgumentNullException.ThrowIfNull(content);
|
||||||
|
ArgumentNullException.ThrowIfNull(settings);
|
||||||
ArgumentNullException.ThrowIfNull(world);
|
ArgumentNullException.ThrowIfNull(world);
|
||||||
ArgumentNullException.ThrowIfNull(interaction);
|
ArgumentNullException.ThrowIfNull(interaction);
|
||||||
|
if (!ReferenceEquals(_dependencies.Gl, platform.Graphics))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Live-presentation dependencies do not match the ordered platform result.");
|
||||||
|
}
|
||||||
return ComposeCore(host, content, world, interaction);
|
return ComposeCore(host, content, world, interaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
: ISessionPlayerCompositionPhase<
|
: ISessionPlayerCompositionPhase<
|
||||||
HostInputCameraResult,
|
HostInputCameraResult,
|
||||||
ContentEffectsAudioResult,
|
ContentEffectsAudioResult,
|
||||||
|
SettingsDevToolsResult,
|
||||||
WorldRenderResult,
|
WorldRenderResult,
|
||||||
InteractionRetainedUiResult,
|
InteractionRetainedUiResult,
|
||||||
LivePresentationResult,
|
LivePresentationResult,
|
||||||
|
|
@ -172,15 +173,22 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
public SessionPlayerResult Compose(
|
public SessionPlayerResult Compose(
|
||||||
HostInputCameraResult host,
|
HostInputCameraResult host,
|
||||||
ContentEffectsAudioResult content,
|
ContentEffectsAudioResult content,
|
||||||
|
SettingsDevToolsResult settings,
|
||||||
WorldRenderResult world,
|
WorldRenderResult world,
|
||||||
InteractionRetainedUiResult interaction,
|
InteractionRetainedUiResult interaction,
|
||||||
LivePresentationResult live)
|
LivePresentationResult live)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(host);
|
ArgumentNullException.ThrowIfNull(host);
|
||||||
ArgumentNullException.ThrowIfNull(content);
|
ArgumentNullException.ThrowIfNull(content);
|
||||||
|
ArgumentNullException.ThrowIfNull(settings);
|
||||||
ArgumentNullException.ThrowIfNull(world);
|
ArgumentNullException.ThrowIfNull(world);
|
||||||
ArgumentNullException.ThrowIfNull(interaction);
|
ArgumentNullException.ThrowIfNull(interaction);
|
||||||
ArgumentNullException.ThrowIfNull(live);
|
ArgumentNullException.ThrowIfNull(live);
|
||||||
|
if (!ReferenceEquals(_dependencies.SettingsDevTools, settings))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Session/player dependencies do not match the ordered settings result.");
|
||||||
|
}
|
||||||
|
|
||||||
var scope = new CompositionAcquisitionScope();
|
var scope = new CompositionAcquisitionScope();
|
||||||
SessionPlayerRuntimeBindings? bindings = null;
|
SessionPlayerRuntimeBindings? bindings = null;
|
||||||
|
|
|
||||||
|
|
@ -1097,8 +1097,18 @@ public sealed class GameWindow :
|
||||||
|
|
||||||
GameWindowPlatformResult<GL, IInputContext> platform = AcquirePlatform();
|
GameWindowPlatformResult<GL, IInputContext> platform = AcquirePlatform();
|
||||||
|
|
||||||
HostInputCameraResult hostInputCamera =
|
GameWindowCompositionPipeline.Run<
|
||||||
new HostInputCameraCompositionPhase(
|
GameWindowPlatformResult<GL, IInputContext>,
|
||||||
|
HostInputCameraResult,
|
||||||
|
ContentEffectsAudioResult,
|
||||||
|
SettingsDevToolsResult,
|
||||||
|
WorldRenderResult,
|
||||||
|
InteractionRetainedUiResult,
|
||||||
|
LivePresentationResult,
|
||||||
|
SessionPlayerResult,
|
||||||
|
FrameRootResult>(
|
||||||
|
platform,
|
||||||
|
platformResult => new HostInputCameraCompositionPhase(
|
||||||
new HostInputCameraDependencies(
|
new HostInputCameraDependencies(
|
||||||
_framebufferResize,
|
_framebufferResize,
|
||||||
_window!.FramebufferSize,
|
_window!.FramebufferSize,
|
||||||
|
|
@ -1111,11 +1121,8 @@ public sealed class GameWindow :
|
||||||
_chaseCameraInput,
|
_chaseCameraInput,
|
||||||
_pointerPosition,
|
_pointerPosition,
|
||||||
_renderDiagnosticLog),
|
_renderDiagnosticLog),
|
||||||
this).Compose(platform);
|
this).Compose(platformResult),
|
||||||
WorldRenderDiagnostics worldRenderDiagnostics =
|
(platformResult, hostInputCamera) =>
|
||||||
hostInputCamera.WorldRenderDiagnostics;
|
|
||||||
|
|
||||||
ContentEffectsAudioResult contentEffectsAudio =
|
|
||||||
new ContentEffectsAudioCompositionPhase(
|
new ContentEffectsAudioCompositionPhase(
|
||||||
new ContentEffectsAudioDependencies(
|
new ContentEffectsAudioDependencies(
|
||||||
_datDir,
|
_datDir,
|
||||||
|
|
@ -1130,8 +1137,9 @@ public sealed class GameWindow :
|
||||||
_options.NoAudio,
|
_options.NoAudio,
|
||||||
Console.WriteLine,
|
Console.WriteLine,
|
||||||
Console.Error.WriteLine),
|
Console.Error.WriteLine),
|
||||||
this).Compose(platform, hostInputCamera);
|
this).Compose(platformResult, hostInputCamera),
|
||||||
|
(platformResult, hostInputCamera, contentEffectsAudio) =>
|
||||||
|
{
|
||||||
SettingsDevToolsOptionalDependencies? optionalDevTools = null;
|
SettingsDevToolsOptionalDependencies? optionalDevTools = null;
|
||||||
if (DevToolsEnabled)
|
if (DevToolsEnabled)
|
||||||
{
|
{
|
||||||
|
|
@ -1167,8 +1175,8 @@ public sealed class GameWindow :
|
||||||
devToolsFrameDiagnostics,
|
devToolsFrameDiagnostics,
|
||||||
devToolsPlayerModeCommands);
|
devToolsPlayerModeCommands);
|
||||||
}
|
}
|
||||||
SettingsDevToolsResult settingsDevTools =
|
|
||||||
new SettingsDevToolsCompositionPhase(
|
return new SettingsDevToolsCompositionPhase(
|
||||||
new SettingsDevToolsDependencies(
|
new SettingsDevToolsDependencies(
|
||||||
_window!,
|
_window!,
|
||||||
_runtimeSettings,
|
_runtimeSettings,
|
||||||
|
|
@ -1188,11 +1196,10 @@ public sealed class GameWindow :
|
||||||
_frameProfiler,
|
_frameProfiler,
|
||||||
_framebufferResize,
|
_framebufferResize,
|
||||||
Console.WriteLine),
|
Console.WriteLine),
|
||||||
this).Compose(platform, hostInputCamera, contentEffectsAudio);
|
this).Compose(platformResult, hostInputCamera, contentEffectsAudio);
|
||||||
Action<string>? compositionToast = settingsDevTools.DevTools is { } composedDevTools
|
},
|
||||||
? text => composedDevTools.Debug.AddToast(text)
|
(platformResult, contentEffectsAudio, settingsDevTools) =>
|
||||||
: null;
|
{
|
||||||
|
|
||||||
const uint initialCenterLandblockId = 0xA9B4FFFFu;
|
const uint initialCenterLandblockId = 0xA9B4FFFFu;
|
||||||
WorldRenderResult worldRender = new WorldRenderCompositionPhase(
|
WorldRenderResult worldRender = new WorldRenderCompositionPhase(
|
||||||
new WorldRenderDependencies(
|
new WorldRenderDependencies(
|
||||||
|
|
@ -1201,20 +1208,24 @@ public sealed class GameWindow :
|
||||||
_gpuFrameFlights!,
|
_gpuFrameFlights!,
|
||||||
initialCenterLandblockId,
|
initialCenterLandblockId,
|
||||||
Console.WriteLine),
|
Console.WriteLine),
|
||||||
this).Compose(platform, contentEffectsAudio, settingsDevTools);
|
this).Compose(platformResult, contentEffectsAudio, settingsDevTools);
|
||||||
string shadersDir = worldRender.Foundation.ShadersDirectory;
|
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"loading world view centered on " +
|
$"loading world view centered on " +
|
||||||
$"0x{worldRender.TerrainBuild.InitialCenterLandblockId:X8}");
|
$"0x{worldRender.TerrainBuild.InitialCenterLandblockId:X8}");
|
||||||
|
return worldRender;
|
||||||
InteractionRetainedUiResult interactionUi =
|
},
|
||||||
new InteractionRetainedUiCompositionPhase(
|
(platformResult, hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender) =>
|
||||||
|
{
|
||||||
|
Action<string>? compositionToast = settingsDevTools.DevTools is { } devTools
|
||||||
|
? text => devTools.Debug.AddToast(text)
|
||||||
|
: null;
|
||||||
|
return new InteractionRetainedUiCompositionPhase(
|
||||||
new InteractionRetainedUiDependencies(
|
new InteractionRetainedUiDependencies(
|
||||||
_options,
|
_options,
|
||||||
platform.Graphics,
|
platformResult.Graphics,
|
||||||
_window!,
|
_window!,
|
||||||
platform.Input,
|
platformResult.Input,
|
||||||
shadersDir,
|
worldRender.Foundation.ShadersDirectory,
|
||||||
contentEffectsAudio.Dats,
|
contentEffectsAudio.Dats,
|
||||||
_datLock,
|
_datLock,
|
||||||
worldRender.Foundation.TextureCache,
|
worldRender.Foundation.TextureCache,
|
||||||
|
|
@ -1252,16 +1263,26 @@ public sealed class GameWindow :
|
||||||
Console.WriteLine),
|
Console.WriteLine),
|
||||||
_retailUiLease,
|
_retailUiLease,
|
||||||
this).Compose(
|
this).Compose(
|
||||||
|
platformResult,
|
||||||
hostInputCamera,
|
hostInputCamera,
|
||||||
contentEffectsAudio,
|
contentEffectsAudio,
|
||||||
settingsDevTools,
|
settingsDevTools,
|
||||||
worldRender);
|
worldRender);
|
||||||
|
},
|
||||||
LivePresentationResult livePresentation =
|
(platformResult,
|
||||||
new LivePresentationCompositionPhase(
|
hostInputCamera,
|
||||||
|
contentEffectsAudio,
|
||||||
|
settingsDevTools,
|
||||||
|
worldRender,
|
||||||
|
interactionUi) =>
|
||||||
|
{
|
||||||
|
Action<string>? compositionToast = settingsDevTools.DevTools is { } devTools
|
||||||
|
? text => devTools.Debug.AddToast(text)
|
||||||
|
: null;
|
||||||
|
return new LivePresentationCompositionPhase(
|
||||||
new LivePresentationDependencies(
|
new LivePresentationDependencies(
|
||||||
_options,
|
_options,
|
||||||
platform.Graphics,
|
platformResult.Graphics,
|
||||||
_window!,
|
_window!,
|
||||||
_datLock,
|
_datLock,
|
||||||
_runtimeSettings,
|
_runtimeSettings,
|
||||||
|
|
@ -1299,12 +1320,19 @@ public sealed class GameWindow :
|
||||||
_uiFrameDiagnostics,
|
_uiFrameDiagnostics,
|
||||||
compositionToast),
|
compositionToast),
|
||||||
this).Compose(
|
this).Compose(
|
||||||
|
platformResult,
|
||||||
hostInputCamera,
|
hostInputCamera,
|
||||||
contentEffectsAudio,
|
contentEffectsAudio,
|
||||||
|
settingsDevTools,
|
||||||
worldRender,
|
worldRender,
|
||||||
interactionUi);
|
interactionUi);
|
||||||
|
},
|
||||||
SessionPlayerResult sessionPlayer =
|
(hostInputCamera,
|
||||||
|
contentEffectsAudio,
|
||||||
|
settingsDevTools,
|
||||||
|
worldRender,
|
||||||
|
interactionUi,
|
||||||
|
livePresentation) =>
|
||||||
new SessionPlayerCompositionPhase(
|
new SessionPlayerCompositionPhase(
|
||||||
new SessionPlayerDependencies(
|
new SessionPlayerDependencies(
|
||||||
_options,
|
_options,
|
||||||
|
|
@ -1371,16 +1399,23 @@ public sealed class GameWindow :
|
||||||
this).Compose(
|
this).Compose(
|
||||||
hostInputCamera,
|
hostInputCamera,
|
||||||
contentEffectsAudio,
|
contentEffectsAudio,
|
||||||
|
settingsDevTools,
|
||||||
worldRender,
|
worldRender,
|
||||||
interactionUi,
|
interactionUi,
|
||||||
livePresentation);
|
livePresentation),
|
||||||
|
(platformResult,
|
||||||
FrameRootResult frameRoots = new FrameRootCompositionPhase(
|
hostInputCamera,
|
||||||
|
contentEffectsAudio,
|
||||||
|
settingsDevTools,
|
||||||
|
worldRender,
|
||||||
|
interactionUi,
|
||||||
|
livePresentation,
|
||||||
|
sessionPlayer) => new FrameRootCompositionPhase(
|
||||||
new FrameRootDependencies(
|
new FrameRootDependencies(
|
||||||
_options,
|
_options,
|
||||||
_gl!,
|
platformResult.Graphics,
|
||||||
_window!,
|
_window!,
|
||||||
_input!,
|
platformResult.Input,
|
||||||
WorldTime,
|
WorldTime,
|
||||||
Weather,
|
Weather,
|
||||||
Lighting,
|
Lighting,
|
||||||
|
|
@ -1412,17 +1447,17 @@ public sealed class GameWindow :
|
||||||
_frameGraphs,
|
_frameGraphs,
|
||||||
Console.WriteLine),
|
Console.WriteLine),
|
||||||
this).Compose(
|
this).Compose(
|
||||||
|
platformResult,
|
||||||
hostInputCamera,
|
hostInputCamera,
|
||||||
contentEffectsAudio,
|
contentEffectsAudio,
|
||||||
settingsDevTools,
|
settingsDevTools,
|
||||||
worldRender,
|
worldRender,
|
||||||
interactionUi,
|
interactionUi,
|
||||||
livePresentation,
|
livePresentation,
|
||||||
sessionPlayer);
|
sessionPlayer),
|
||||||
|
frameRoots => new SessionStartCompositionPhase(
|
||||||
new SessionStartCompositionPhase(
|
|
||||||
new SessionStartDependencies(_options, Console.WriteLine))
|
new SessionStartDependencies(_options, Console.WriteLine))
|
||||||
.Start(frameRoots);
|
.Start(frameRoots));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUpdate(double dt)
|
private void OnUpdate(double dt)
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,65 @@ public sealed class GameWindowCompositionPipelineTests
|
||||||
Assert.Equal("phase-9", phases.Calls[^1]);
|
Assert.Equal("phase-9", phases.Calls[^1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProductionDelegateEntryUsesTheSamePipelineAndExactResults()
|
||||||
|
{
|
||||||
|
var calls = new List<int>();
|
||||||
|
var values = Enumerable.Range(0, 9).Select(value => new Token(value)).ToArray();
|
||||||
|
|
||||||
|
GameWindowCompositionPipeline.Run<
|
||||||
|
Token,
|
||||||
|
Token,
|
||||||
|
Token,
|
||||||
|
Token,
|
||||||
|
Token,
|
||||||
|
Token,
|
||||||
|
Token,
|
||||||
|
Token,
|
||||||
|
Token>(
|
||||||
|
values[0],
|
||||||
|
platform => Pass(1, values[1], platform),
|
||||||
|
(platform, host) => Pass(2, values[2], platform, host),
|
||||||
|
(platform, host, content) => Pass(3, values[3], platform, host, content),
|
||||||
|
(platform, content, settings) => Pass(4, values[4], platform, content, settings),
|
||||||
|
(platform, host, content, settings, world) =>
|
||||||
|
Pass(5, values[5], platform, host, content, settings, world),
|
||||||
|
(platform, host, content, settings, world, interaction) =>
|
||||||
|
Pass(6, values[6], platform, host, content, settings, world, interaction),
|
||||||
|
(host, content, settings, world, interaction, live) =>
|
||||||
|
Pass(7, values[7], host, content, settings, world, interaction, live),
|
||||||
|
(platform, host, content, settings, world, interaction, live, session) =>
|
||||||
|
Pass(8, values[8], platform, host, content, settings, world, interaction, live, session),
|
||||||
|
frame =>
|
||||||
|
{
|
||||||
|
Assert.Same(values[8], frame);
|
||||||
|
calls.Add(9);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(Enumerable.Range(1, 9), calls);
|
||||||
|
|
||||||
|
Token Pass(int phase, Token result, params Token[] actual)
|
||||||
|
{
|
||||||
|
Token[] expected = phase switch
|
||||||
|
{
|
||||||
|
1 => [values[0]],
|
||||||
|
2 => [values[0], values[1]],
|
||||||
|
3 => [values[0], values[1], values[2]],
|
||||||
|
4 => [values[0], values[2], values[3]],
|
||||||
|
5 => [values[0], values[1], values[2], values[3], values[4]],
|
||||||
|
6 => [values[0], values[1], values[2], values[3], values[4], values[5]],
|
||||||
|
7 => [values[1], values[2], values[3], values[4], values[5], values[6]],
|
||||||
|
8 => [values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7]],
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(phase)),
|
||||||
|
};
|
||||||
|
Assert.Equal(expected.Length, actual.Length);
|
||||||
|
for (int index = 0; index < expected.Length; index++)
|
||||||
|
Assert.Same(expected[index], actual[index]);
|
||||||
|
calls.Add(phase);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed record Token(int Phase);
|
private sealed record Token(int Phase);
|
||||||
|
|
||||||
private sealed class RecordingPhases(int failingPhase) :
|
private sealed class RecordingPhases(int failingPhase) :
|
||||||
|
|
@ -49,10 +108,10 @@ public sealed class GameWindowCompositionPipelineTests
|
||||||
IContentEffectsAudioCompositionPhase<Token, Token, Token>,
|
IContentEffectsAudioCompositionPhase<Token, Token, Token>,
|
||||||
ISettingsDevToolsCompositionPhase<Token, Token, Token, Token>,
|
ISettingsDevToolsCompositionPhase<Token, Token, Token, Token>,
|
||||||
IWorldRenderCompositionPhase<Token, Token, Token, Token>,
|
IWorldRenderCompositionPhase<Token, Token, Token, Token>,
|
||||||
IInteractionUiCompositionPhase<Token, Token, Token, Token, Token>,
|
IInteractionUiCompositionPhase<Token, Token, Token, Token, Token, Token>,
|
||||||
ILivePresentationCompositionPhase<Token, Token, Token, Token, Token>,
|
ILivePresentationCompositionPhase<Token, Token, Token, Token, Token, Token, Token>,
|
||||||
ISessionPlayerCompositionPhase<Token, Token, Token, Token, Token, Token>,
|
ISessionPlayerCompositionPhase<Token, Token, Token, Token, Token, Token, Token>,
|
||||||
IFrameRootCompositionPhase<Token, Token, Token, Token, Token, Token, Token, Token>,
|
IFrameRootCompositionPhase<Token, Token, Token, Token, Token, Token, Token, Token, Token>,
|
||||||
ISessionStartCompositionPhase<Token>
|
ISessionStartCompositionPhase<Token>
|
||||||
{
|
{
|
||||||
public Token Platform { get; } = new(0);
|
public Token Platform { get; } = new(0);
|
||||||
|
|
@ -111,12 +170,14 @@ public sealed class GameWindowCompositionPipelineTests
|
||||||
return _world;
|
return _world;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token IInteractionUiCompositionPhase<Token, Token, Token, Token, Token>.Compose(
|
Token IInteractionUiCompositionPhase<Token, Token, Token, Token, Token, Token>.Compose(
|
||||||
|
Token platform,
|
||||||
Token host,
|
Token host,
|
||||||
Token content,
|
Token content,
|
||||||
Token settings,
|
Token settings,
|
||||||
Token world)
|
Token world)
|
||||||
{
|
{
|
||||||
|
Assert.Same(Platform, platform);
|
||||||
Assert.Same(_host, host);
|
Assert.Same(_host, host);
|
||||||
Assert.Same(_content, content);
|
Assert.Same(_content, content);
|
||||||
Assert.Same(_settings, settings);
|
Assert.Same(_settings, settings);
|
||||||
|
|
@ -125,29 +186,35 @@ public sealed class GameWindowCompositionPipelineTests
|
||||||
return _interaction;
|
return _interaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token ILivePresentationCompositionPhase<Token, Token, Token, Token, Token>.Compose(
|
Token ILivePresentationCompositionPhase<Token, Token, Token, Token, Token, Token, Token>.Compose(
|
||||||
|
Token platform,
|
||||||
Token host,
|
Token host,
|
||||||
Token content,
|
Token content,
|
||||||
|
Token settings,
|
||||||
Token world,
|
Token world,
|
||||||
Token interaction)
|
Token interaction)
|
||||||
{
|
{
|
||||||
|
Assert.Same(Platform, platform);
|
||||||
Assert.Same(_host, host);
|
Assert.Same(_host, host);
|
||||||
Assert.Same(_content, content);
|
Assert.Same(_content, content);
|
||||||
|
Assert.Same(_settings, settings);
|
||||||
Assert.Same(_world, world);
|
Assert.Same(_world, world);
|
||||||
Assert.Same(_interaction, interaction);
|
Assert.Same(_interaction, interaction);
|
||||||
Record(6);
|
Record(6);
|
||||||
return _live;
|
return _live;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token ISessionPlayerCompositionPhase<Token, Token, Token, Token, Token, Token>.Compose(
|
Token ISessionPlayerCompositionPhase<Token, Token, Token, Token, Token, Token, Token>.Compose(
|
||||||
Token host,
|
Token host,
|
||||||
Token content,
|
Token content,
|
||||||
|
Token settings,
|
||||||
Token world,
|
Token world,
|
||||||
Token interaction,
|
Token interaction,
|
||||||
Token live)
|
Token live)
|
||||||
{
|
{
|
||||||
Assert.Same(_host, host);
|
Assert.Same(_host, host);
|
||||||
Assert.Same(_content, content);
|
Assert.Same(_content, content);
|
||||||
|
Assert.Same(_settings, settings);
|
||||||
Assert.Same(_world, world);
|
Assert.Same(_world, world);
|
||||||
Assert.Same(_interaction, interaction);
|
Assert.Same(_interaction, interaction);
|
||||||
Assert.Same(_live, live);
|
Assert.Same(_live, live);
|
||||||
|
|
@ -155,7 +222,8 @@ public sealed class GameWindowCompositionPipelineTests
|
||||||
return _session;
|
return _session;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token IFrameRootCompositionPhase<Token, Token, Token, Token, Token, Token, Token, Token>.Compose(
|
Token IFrameRootCompositionPhase<Token, Token, Token, Token, Token, Token, Token, Token, Token>.Compose(
|
||||||
|
Token platform,
|
||||||
Token host,
|
Token host,
|
||||||
Token content,
|
Token content,
|
||||||
Token settings,
|
Token settings,
|
||||||
|
|
@ -164,6 +232,7 @@ public sealed class GameWindowCompositionPipelineTests
|
||||||
Token live,
|
Token live,
|
||||||
Token session)
|
Token session)
|
||||||
{
|
{
|
||||||
|
Assert.Same(Platform, platform);
|
||||||
Assert.Same(_host, host);
|
Assert.Same(_host, host);
|
||||||
Assert.Same(_content, content);
|
Assert.Same(_content, content);
|
||||||
Assert.Same(_settings, settings);
|
Assert.Same(_settings, settings);
|
||||||
|
|
|
||||||
|
|
@ -52,22 +52,21 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
AssertAppearsInOrder(
|
AssertAppearsInOrder(
|
||||||
body,
|
body,
|
||||||
"GameWindowPlatformResult<GL, IInputContext> platform = AcquirePlatform();",
|
"GameWindowPlatformResult<GL, IInputContext> platform = AcquirePlatform();",
|
||||||
|
"GameWindowCompositionPipeline.Run<",
|
||||||
"new HostInputCameraCompositionPhase(",
|
"new HostInputCameraCompositionPhase(",
|
||||||
"this).Compose(platform);",
|
"this).Compose(platformResult),",
|
||||||
"new ContentEffectsAudioCompositionPhase(",
|
"new ContentEffectsAudioCompositionPhase(",
|
||||||
"this).Compose(platform, hostInputCamera);",
|
"this).Compose(platformResult, hostInputCamera),",
|
||||||
"new SettingsDevToolsCompositionPhase(",
|
"new SettingsDevToolsCompositionPhase(",
|
||||||
"this).Compose(platform, hostInputCamera, contentEffectsAudio);",
|
"this).Compose(platformResult, hostInputCamera, contentEffectsAudio);",
|
||||||
"InteractionRetainedUiResult interactionUi =",
|
|
||||||
"new InteractionRetainedUiCompositionPhase(",
|
"new InteractionRetainedUiCompositionPhase(",
|
||||||
"this).Compose(",
|
"this).Compose(",
|
||||||
"LivePresentationResult livePresentation =",
|
|
||||||
"new LivePresentationCompositionPhase(",
|
"new LivePresentationCompositionPhase(",
|
||||||
"this).Compose(",
|
"this).Compose(",
|
||||||
"new SessionPlayerCompositionPhase(",
|
"new SessionPlayerCompositionPhase(",
|
||||||
"FrameRootResult frameRoots = new FrameRootCompositionPhase(",
|
"new FrameRootCompositionPhase(",
|
||||||
"new SessionStartCompositionPhase(",
|
"new SessionStartCompositionPhase(",
|
||||||
".Start(frameRoots);");
|
".Start(frameRoots));");
|
||||||
|
|
||||||
string framePhase = File.ReadAllText(Path.Combine(
|
string framePhase = File.ReadAllText(Path.Combine(
|
||||||
FindRepoRoot(),
|
FindRepoRoot(),
|
||||||
|
|
@ -98,7 +97,7 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"_publication.PublishSessionPlayer(result);");
|
"_publication.PublishSessionPlayer(result);");
|
||||||
Assert.Equal(1, CountOccurrences(
|
Assert.Equal(1, CountOccurrences(
|
||||||
body,
|
body,
|
||||||
".Start(frameRoots);"));
|
".Start(frameRoots));"));
|
||||||
string phaseOne = Slice(
|
string phaseOne = Slice(
|
||||||
File.ReadAllText(Path.Combine(
|
File.ReadAllText(Path.Combine(
|
||||||
FindRepoRoot(),
|
FindRepoRoot(),
|
||||||
|
|
@ -131,9 +130,9 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"case LiveSessionStartStatus.MissingCredentials:",
|
"case LiveSessionStartStatus.MissingCredentials:",
|
||||||
"case LiveSessionStartStatus.Failed:");
|
"case LiveSessionStartStatus.Failed:");
|
||||||
int start = body.IndexOf(
|
int start = body.IndexOf(
|
||||||
".Start(frameRoots);",
|
".Start(frameRoots));",
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
string postStart = body[(start + ".Start(frameRoots);".Length)..];
|
string postStart = body[(start + ".Start(frameRoots));".Length)..];
|
||||||
Assert.Empty(System.Text.RegularExpressions.Regex.Matches(
|
Assert.Empty(System.Text.RegularExpressions.Regex.Matches(
|
||||||
postStart,
|
postStart,
|
||||||
@"(?m)^\s+_[A-Za-z]\w*\s*(?:\?\?=|=)"));
|
@"(?m)^\s+_[A-Za-z]\w*\s*(?:\?\?=|=)"));
|
||||||
|
|
@ -350,13 +349,14 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"Window.Create(options)");
|
"Window.Create(options)");
|
||||||
AssertAppearsInOrder(
|
AssertAppearsInOrder(
|
||||||
load,
|
load,
|
||||||
|
"GameWindowCompositionPipeline.Run<",
|
||||||
"new SettingsDevToolsCompositionPhase(",
|
"new SettingsDevToolsCompositionPhase(",
|
||||||
"this).Compose(platform, hostInputCamera, contentEffectsAudio);",
|
"this).Compose(platformResult, hostInputCamera, contentEffectsAudio);",
|
||||||
"new WorldRenderCompositionPhase(",
|
"new WorldRenderCompositionPhase(",
|
||||||
"new SessionPlayerCompositionPhase(",
|
"new SessionPlayerCompositionPhase(",
|
||||||
"new FrameRootCompositionPhase(",
|
"new FrameRootCompositionPhase(",
|
||||||
"new SessionStartCompositionPhase(",
|
"new SessionStartCompositionPhase(",
|
||||||
".Start(frameRoots);");
|
".Start(frameRoots));");
|
||||||
string sessionPhase = File.ReadAllText(Path.Combine(
|
string sessionPhase = File.ReadAllText(Path.Combine(
|
||||||
FindRepoRoot(),
|
FindRepoRoot(),
|
||||||
"src",
|
"src",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue