refactor(app): close ordered startup composition

This commit is contained in:
Erik 2026-07-22 19:20:20 +02:00
parent 54244d31f1
commit 530b4bd8f5
16 changed files with 537 additions and 215 deletions

View file

@ -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 IL—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 JL—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

View file

@ -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 IL—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 JL—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

View file

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

View file

@ -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.9L 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; JL 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

View file

@ -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
JL remain. The App gate passes 3,430 tests / 3 intentional skips, the complete complete; shutdown, canonical soak snapshots, and closeout checkpoints JL
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

View file

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

View file

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

View file

@ -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.1I.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.

View file

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

View file

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

View file

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

View file

@ -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);
} }

View file

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

View file

@ -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,12 +1121,9 @@ public sealed class GameWindow :
_chaseCameraInput, _chaseCameraInput,
_pointerPosition, _pointerPosition,
_renderDiagnosticLog), _renderDiagnosticLog),
this).Compose(platform); this).Compose(platformResult),
WorldRenderDiagnostics worldRenderDiagnostics = (platformResult, hostInputCamera) =>
hostInputCamera.WorldRenderDiagnostics; new ContentEffectsAudioCompositionPhase(
ContentEffectsAudioResult contentEffectsAudio =
new ContentEffectsAudioCompositionPhase(
new ContentEffectsAudioDependencies( new ContentEffectsAudioDependencies(
_datDir, _datDir,
_physicsDataCache, _physicsDataCache,
@ -1130,91 +1137,95 @@ 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; {
if (DevToolsEnabled) SettingsDevToolsOptionalDependencies? optionalDevTools = null;
{ if (DevToolsEnabled)
var devToolsWorldEntities = {
new DeferredCanonicalWorldEntityCountSource(); var devToolsWorldEntities =
var devToolsFrameDiagnostics = new DeferredCanonicalWorldEntityCountSource();
new DeferredRenderFrameDiagnosticsSource(); var devToolsFrameDiagnostics =
var devToolsPlayerModeCommands = new DeferredRenderFrameDiagnosticsSource();
new DeferredDevToolsPlayerModeCommands(); var devToolsPlayerModeCommands =
var devToolsFacts = new DevToolsRuntimeFacts( new DeferredDevToolsPlayerModeCommands();
_localPlayerMode, var devToolsFacts = new DevToolsRuntimeFacts(
_playerControllerSlot, _localPlayerMode,
hostInputCamera.CameraController, _playerControllerSlot,
devToolsWorldEntities,
_animatedEntities,
_debugVmRenderFacts,
_physicsEngine,
_worldSceneDebugState,
_renderRange,
hostInputCamera.CameraPointerInput,
_worldEnvironment,
Lighting,
contentEffectsAudio.ParticleSystem,
devToolsFrameDiagnostics);
IRuntimeKeyBindingTarget? keyBindingTarget =
hostInputCamera.InputDispatcher is { } settingsDispatcher
? new RuntimeKeyBindingTarget(settingsDispatcher)
: null;
optionalDevTools = new SettingsDevToolsOptionalDependencies(
devToolsFacts,
keyBindingTarget,
devToolsWorldEntities,
devToolsFrameDiagnostics,
devToolsPlayerModeCommands);
}
SettingsDevToolsResult settingsDevTools =
new SettingsDevToolsCompositionPhase(
new SettingsDevToolsDependencies(
_window!,
_runtimeSettings,
new RuntimeSettingsStartupTargets(
new SilkRuntimeDisplayWindowTarget(_window!),
_displayFramePacing,
hostInputCamera.CameraController, hostInputCamera.CameraController,
contentEffectsAudio.Audio?.Engine), devToolsWorldEntities,
_hostQuiescence, _animatedEntities,
Chat, _debugVmRenderFacts,
Combat, _physicsEngine,
LocalPlayer, _worldSceneDebugState,
optionalDevTools, _renderRange,
_runtimeDiagnosticCommands, hostInputCamera.CameraPointerInput,
_combatFeedback, _worldEnvironment,
_keyBindings, Lighting,
_frameProfiler, contentEffectsAudio.ParticleSystem,
_framebufferResize, devToolsFrameDiagnostics);
Console.WriteLine), IRuntimeKeyBindingTarget? keyBindingTarget =
this).Compose(platform, hostInputCamera, contentEffectsAudio); hostInputCamera.InputDispatcher is { } settingsDispatcher
Action<string>? compositionToast = settingsDevTools.DevTools is { } composedDevTools ? new RuntimeKeyBindingTarget(settingsDispatcher)
? text => composedDevTools.Debug.AddToast(text) : null;
: null; optionalDevTools = new SettingsDevToolsOptionalDependencies(
devToolsFacts,
keyBindingTarget,
devToolsWorldEntities,
devToolsFrameDiagnostics,
devToolsPlayerModeCommands);
}
const uint initialCenterLandblockId = 0xA9B4FFFFu; return new SettingsDevToolsCompositionPhase(
WorldRenderResult worldRender = new WorldRenderCompositionPhase( new SettingsDevToolsDependencies(
new WorldRenderDependencies( _window!,
_worldEnvironment, _runtimeSettings,
_renderResourceLifetime, new RuntimeSettingsStartupTargets(
_gpuFrameFlights!, new SilkRuntimeDisplayWindowTarget(_window!),
initialCenterLandblockId, _displayFramePacing,
Console.WriteLine), hostInputCamera.CameraController,
this).Compose(platform, contentEffectsAudio, settingsDevTools); contentEffectsAudio.Audio?.Engine),
string shadersDir = worldRender.Foundation.ShadersDirectory; _hostQuiescence,
Console.WriteLine( Chat,
$"loading world view centered on " + Combat,
$"0x{worldRender.TerrainBuild.InitialCenterLandblockId:X8}"); LocalPlayer,
optionalDevTools,
InteractionRetainedUiResult interactionUi = _runtimeDiagnosticCommands,
new InteractionRetainedUiCompositionPhase( _combatFeedback,
new InteractionRetainedUiDependencies( _keyBindings,
_frameProfiler,
_framebufferResize,
Console.WriteLine),
this).Compose(platformResult, hostInputCamera, contentEffectsAudio);
},
(platformResult, contentEffectsAudio, settingsDevTools) =>
{
const uint initialCenterLandblockId = 0xA9B4FFFFu;
WorldRenderResult worldRender = new WorldRenderCompositionPhase(
new WorldRenderDependencies(
_worldEnvironment,
_renderResourceLifetime,
_gpuFrameFlights!,
initialCenterLandblockId,
Console.WriteLine),
this).Compose(platformResult, contentEffectsAudio, settingsDevTools);
Console.WriteLine(
$"loading world view centered on " +
$"0x{worldRender.TerrainBuild.InitialCenterLandblockId:X8}");
return worldRender;
},
(platformResult, hostInputCamera, contentEffectsAudio, settingsDevTools, worldRender) =>
{
Action<string>? compositionToast = settingsDevTools.DevTools is { } devTools
? text => devTools.Debug.AddToast(text)
: null;
return new InteractionRetainedUiCompositionPhase(
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,
new LivePresentationDependencies( contentEffectsAudio,
settingsDevTools,
worldRender,
interactionUi) =>
{
Action<string>? compositionToast = settingsDevTools.DevTools is { } devTools
? text => devTools.Debug.AddToast(text)
: null;
return new LivePresentationCompositionPhase(
new LivePresentationDependencies(
_options, _options,
platform.Graphics, platformResult.Graphics,
_window!, _window!,
_datLock, _datLock,
_runtimeSettings, _runtimeSettings,
@ -1299,13 +1320,20 @@ 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,
new SessionPlayerCompositionPhase( contentEffectsAudio,
settingsDevTools,
worldRender,
interactionUi,
livePresentation) =>
new SessionPlayerCompositionPhase(
new SessionPlayerDependencies( new SessionPlayerDependencies(
_options, _options,
_window!, _window!,
@ -1371,58 +1399,65 @@ public sealed class GameWindow :
this).Compose( this).Compose(
hostInputCamera, hostInputCamera,
contentEffectsAudio, contentEffectsAudio,
settingsDevTools,
worldRender, worldRender,
interactionUi, interactionUi,
livePresentation); livePresentation),
(platformResult,
FrameRootResult frameRoots = new FrameRootCompositionPhase(
new FrameRootDependencies(
_options,
_gl!,
_window!,
_input!,
WorldTime,
Weather,
Lighting,
_worldEnvironment,
_physicsEngine,
_cellVisibility,
_localPlayerMode,
_localPlayerIdentity,
_chaseCameraInput,
_playerControllerSlot,
_liveWorldOrigin,
_particleVisibility,
_effectPoses,
_renderRange,
_runtimeSettings,
_displayFramePacing,
_worldSceneDebugState,
_retailAlphaQueue,
_frameProfiler,
_frameDiag,
_renderDiagnosticLog,
_debugVmRenderFacts,
_inputCapture,
_cameraInput,
_selection,
_animatedEntities,
_updateFrameClock,
Combat,
_frameGraphs,
Console.WriteLine),
this).Compose(
hostInputCamera, hostInputCamera,
contentEffectsAudio, contentEffectsAudio,
settingsDevTools, settingsDevTools,
worldRender, worldRender,
interactionUi, interactionUi,
livePresentation, livePresentation,
sessionPlayer); sessionPlayer) => new FrameRootCompositionPhase(
new FrameRootDependencies(
new SessionStartCompositionPhase( _options,
new SessionStartDependencies(_options, Console.WriteLine)) platformResult.Graphics,
.Start(frameRoots); _window!,
platformResult.Input,
WorldTime,
Weather,
Lighting,
_worldEnvironment,
_physicsEngine,
_cellVisibility,
_localPlayerMode,
_localPlayerIdentity,
_chaseCameraInput,
_playerControllerSlot,
_liveWorldOrigin,
_particleVisibility,
_effectPoses,
_renderRange,
_runtimeSettings,
_displayFramePacing,
_worldSceneDebugState,
_retailAlphaQueue,
_frameProfiler,
_frameDiag,
_renderDiagnosticLog,
_debugVmRenderFacts,
_inputCapture,
_cameraInput,
_selection,
_animatedEntities,
_updateFrameClock,
Combat,
_frameGraphs,
Console.WriteLine),
this).Compose(
platformResult,
hostInputCamera,
contentEffectsAudio,
settingsDevTools,
worldRender,
interactionUi,
livePresentation,
sessionPlayer),
frameRoots => new SessionStartCompositionPhase(
new SessionStartDependencies(_options, Console.WriteLine))
.Start(frameRoots));
} }
private void OnUpdate(double dt) private void OnUpdate(double dt)

View file

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

View file

@ -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*(?:\?\?=|=)"));
@ -174,7 +173,7 @@ public sealed class GameWindowSlice8BoundaryTests
source, source,
StringComparison.Ordinal); StringComparison.Ordinal);
Assert.Contains( Assert.Contains(
"new WorldRenderDependencies(\n _worldEnvironment,", "new WorldRenderDependencies(\n _worldEnvironment,",
load.Replace("\r\n", "\n", StringComparison.Ordinal), load.Replace("\r\n", "\n", StringComparison.Ordinal),
StringComparison.Ordinal); StringComparison.Ordinal);
string worldCompose = Slice( string worldCompose = Slice(
@ -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",