refactor(app): make session start terminal
Move live-session startup and its existing diagnostics into the typed Phase-9 owner. GameWindow.OnLoad now ends immediately after that phase, so no callback binding, publication, or allocation can occur after the session becomes active. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
3628aeb520
commit
54244d31f1
11 changed files with 139 additions and 39 deletions
|
|
@ -35,9 +35,10 @@ What does NOT go here:
|
||||||
every late edge explicitly. I.8a now owns the focused session factory,
|
every late edge explicitly. I.8a now owns the focused session factory,
|
||||||
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. `GameWindow.OnUpdate`
|
publication, and lifecycle resource snapshot source. I.8c makes session
|
||||||
and `GameWindow.OnRender` are typed orchestration handoffs and the class is
|
start the terminal `OnLoad` operation. `GameWindow.OnUpdate` and
|
||||||
1,919 raw lines. I.8c–I.9 plus the remaining
|
`GameWindow.OnRender` are typed orchestration handoffs and the class is
|
||||||
|
1,910 raw lines. I.9 plus the remaining
|
||||||
shutdown/closeout
|
shutdown/closeout
|
||||||
cleanup is active in
|
cleanup is active in
|
||||||
[`docs/architecture/code-structure.md`](architecture/code-structure.md).
|
[`docs/architecture/code-structure.md`](architecture/code-structure.md).
|
||||||
|
|
|
||||||
|
|
@ -739,10 +739,11 @@ 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,919 raw lines. I.8c–L remain active, with terminal session start next. The
|
1,910 raw lines after I.8c makes live-session start the terminal `OnLoad`
|
||||||
App gate passes 3,429 tests / 3 intentional skips and the complete Release suite
|
operation. I.9–L remain active, with the complete Checkpoint-I audit next. The
|
||||||
passes 7,801 tests / 5 intentional skips. The clean solution build retains only
|
App gate passes 3,430 tests / 3 intentional skips and the complete Release suite
|
||||||
the 17 test-project warnings tracked by #228; the I.8b corrected-diff review is
|
passes 7,802 tests / 5 intentional skips. The clean solution build retains only
|
||||||
|
the 17 test-project warnings tracked by #228; the I.8c corrected-diff review is
|
||||||
clean.
|
clean.
|
||||||
|
|
||||||
### 4.4 Exit criteria
|
### 4.4 Exit criteria
|
||||||
|
|
|
||||||
|
|
@ -104,11 +104,11 @@ 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,919 raw lines after I.8b, which also composes and atomically publishes the
|
now 1,910 raw lines after I.8c, which makes session start the terminal `OnLoad`
|
||||||
complete update/render pair and extracts lifecycle resource sampling. I.8c–I.9,
|
operation. I.9, shutdown, canonical soak snapshots, and closeout checkpoints
|
||||||
shutdown, canonical soak snapshots, and closeout checkpoints J–L remain. The
|
J–L remain. The App gate passes 3,430 tests / 3 intentional skips, the complete
|
||||||
App gate passes 3,429 tests / 3 intentional skips, the complete Release suite
|
Release suite passes 7,802 tests / 5 skips, and the I.8c corrected-diff review
|
||||||
passes 7,801 tests / 5 skips, and the I.8b corrected-diff review is clean.
|
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
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,12 @@ session, hydration, local-player, combat, and teleport startup graph, including
|
||||||
named reversible late edges. I.8a adds the focused live-session runtime factory
|
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. `GameWindow` is 1,919 raw lines. I.8c–I.9 and checkpoints J–L remain
|
sampling. I.8c makes session start the terminal `OnLoad` operation.
|
||||||
active. Issue #232 tracks
|
`GameWindow` is 1,910 raw lines. I.9 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,429 tests / 3 skips, the complete Release suite passes
|
The App gate passes 3,430 tests / 3 skips, the complete Release suite passes
|
||||||
7,801 tests / 5 skips, and the I.8b corrected-diff review is clean.
|
7,802 tests / 5 skips, and the I.8c 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
|
||||||
|
|
|
||||||
|
|
@ -297,8 +297,10 @@ ledger only after the complete pipeline is live.
|
||||||
input subscriber now belong to Phase 7 and publish before any frame root.
|
input subscriber now belong to Phase 7 and publish before any frame root.
|
||||||
I.8b composes the unchanged update/render graph in a focused phase, publishes
|
I.8b composes the unchanged update/render graph in a focused phase, publishes
|
||||||
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. `GameWindow.cs` is 1,919 raw lines. I.8c terminal
|
sampling out of the window. I.8c makes the exact session start and its
|
||||||
session start is the active cut.
|
existing diagnostics the terminal `OnLoad` operation. `GameWindow.cs` is
|
||||||
|
1,910 raw lines. I.9 corrected-diff review and documentation closeout is the
|
||||||
|
active cut.
|
||||||
Checkpoint I remains active until I.9 closes the complete nine-phase pipeline.
|
Checkpoint I remains active until I.9 closes the complete nine-phase pipeline.
|
||||||
|
|
||||||
## 6. Automated acceptance
|
## 6. Automated acceptance
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# GameWindow Slice 8 — Checkpoint I.8 Frame Roots and Session Start
|
# GameWindow Slice 8 — Checkpoint I.8 Frame Roots and Session Start
|
||||||
|
|
||||||
**Status:** Active — I.8a–I.8b complete; I.8c terminal start next
|
**Status:** Complete 2026-07-22
|
||||||
|
|
||||||
**Parent:** `2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`
|
**Parent:** `2026-07-22-gamewindow-slice-8-checkpoint-i-ordered-composition.md`
|
||||||
|
|
||||||
|
|
@ -144,6 +144,12 @@ shutdown. Replacement is rejected atomically.
|
||||||
|
|
||||||
## 4. I.8c — terminal start and window cutover
|
## 4. I.8c — terminal start and window cutover
|
||||||
|
|
||||||
|
**Completed 2026-07-22.** `SessionStartCompositionPhase` invokes the exact
|
||||||
|
Phase-7 host through the Phase-8 result and owns the existing missing-credential
|
||||||
|
and failed-start diagnostics. The phase call is the final statement in
|
||||||
|
`GameWindow.OnLoad`; no binding, publication, allocation, or diagnostics setup
|
||||||
|
follows it.
|
||||||
|
|
||||||
Add `SessionStartCompositionPhase` implementing
|
Add `SessionStartCompositionPhase` implementing
|
||||||
`ISessionStartCompositionPhase<FrameRootResult>`. It invokes only
|
`ISessionStartCompositionPhase<FrameRootResult>`. It invokes only
|
||||||
`LiveSessionHost.Start(RuntimeOptions)` and preserves the existing diagnostics
|
`LiveSessionHost.Start(RuntimeOptions)` and preserves the existing diagnostics
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ audit.
|
||||||
- [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
|
- [ ] I — group `OnLoad` into small ordered, fakeable composition phases with
|
||||||
transactional partial-acquisition rollback. I.1–I.8b are complete; I.8c
|
transactional partial-acquisition rollback. I.1–I.8 are complete; I.9
|
||||||
terminal session start is active. Detailed plan:
|
corrected-diff review and checkpoint closeout is active. 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.
|
||||||
|
|
|
||||||
47
src/AcDream.App/Composition/SessionStartComposition.cs
Normal file
47
src/AcDream.App/Composition/SessionStartComposition.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
using AcDream.App.Net;
|
||||||
|
|
||||||
|
namespace AcDream.App.Composition;
|
||||||
|
|
||||||
|
internal sealed record SessionStartDependencies(
|
||||||
|
RuntimeOptions Options,
|
||||||
|
Action<string> Log);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Terminal startup phase. Every callback, command target, and frame root is
|
||||||
|
/// already published when this owner asks the canonical session host to start.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class SessionStartCompositionPhase
|
||||||
|
: ISessionStartCompositionPhase<FrameRootResult>
|
||||||
|
{
|
||||||
|
private readonly SessionStartDependencies _dependencies;
|
||||||
|
|
||||||
|
public SessionStartCompositionPhase(SessionStartDependencies dependencies) =>
|
||||||
|
_dependencies = dependencies
|
||||||
|
?? throw new ArgumentNullException(nameof(dependencies));
|
||||||
|
|
||||||
|
public void Start(FrameRootResult frame)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(frame);
|
||||||
|
LiveSessionStartResult result =
|
||||||
|
frame.SessionHost.Start(_dependencies.Options);
|
||||||
|
Report(result, _dependencies.Log);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Report(
|
||||||
|
LiveSessionStartResult result,
|
||||||
|
Action<string> log)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(result);
|
||||||
|
ArgumentNullException.ThrowIfNull(log);
|
||||||
|
switch (result.Status)
|
||||||
|
{
|
||||||
|
case LiveSessionStartStatus.MissingCredentials:
|
||||||
|
log(
|
||||||
|
"live: ACDREAM_LIVE set but TEST_USER/TEST_PASS missing; skipping");
|
||||||
|
break;
|
||||||
|
case LiveSessionStartStatus.Failed:
|
||||||
|
log($"live: session failed: {result.Error}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1420,18 +1420,9 @@ public sealed class GameWindow :
|
||||||
livePresentation,
|
livePresentation,
|
||||||
sessionPlayer);
|
sessionPlayer);
|
||||||
|
|
||||||
AcDream.App.Net.LiveSessionStartResult liveStart =
|
new SessionStartCompositionPhase(
|
||||||
frameRoots.SessionHost.Start(_options);
|
new SessionStartDependencies(_options, Console.WriteLine))
|
||||||
switch (liveStart.Status)
|
.Start(frameRoots);
|
||||||
{
|
|
||||||
case AcDream.App.Net.LiveSessionStartStatus.MissingCredentials:
|
|
||||||
Console.WriteLine(
|
|
||||||
"live: ACDREAM_LIVE set but TEST_USER/TEST_PASS missing; skipping");
|
|
||||||
break;
|
|
||||||
case AcDream.App.Net.LiveSessionStartStatus.Failed:
|
|
||||||
Console.WriteLine($"live: session failed: {liveStart.Error}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUpdate(double dt)
|
private void OnUpdate(double dt)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
using AcDream.App.Composition;
|
||||||
|
using AcDream.App.Net;
|
||||||
|
|
||||||
|
namespace AcDream.App.Tests.Composition;
|
||||||
|
|
||||||
|
public sealed class SessionStartCompositionTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DiagnosticsPreserveMissingCredentialAndFailureMessagesOnly()
|
||||||
|
{
|
||||||
|
var messages = new List<string>();
|
||||||
|
|
||||||
|
SessionStartCompositionPhase.Report(
|
||||||
|
new LiveSessionStartResult(
|
||||||
|
LiveSessionStartStatus.MissingCredentials),
|
||||||
|
messages.Add);
|
||||||
|
SessionStartCompositionPhase.Report(
|
||||||
|
new LiveSessionStartResult(
|
||||||
|
LiveSessionStartStatus.Failed,
|
||||||
|
Error: new InvalidOperationException("boom")),
|
||||||
|
messages.Add);
|
||||||
|
SessionStartCompositionPhase.Report(
|
||||||
|
new LiveSessionStartResult(LiveSessionStartStatus.Disabled),
|
||||||
|
messages.Add);
|
||||||
|
SessionStartCompositionPhase.Report(
|
||||||
|
new LiveSessionStartResult(LiveSessionStartStatus.Connected),
|
||||||
|
messages.Add);
|
||||||
|
|
||||||
|
Assert.Equal(2, messages.Count);
|
||||||
|
Assert.Equal(
|
||||||
|
"live: ACDREAM_LIVE set but TEST_USER/TEST_PASS missing; skipping",
|
||||||
|
messages[0]);
|
||||||
|
Assert.StartsWith(
|
||||||
|
"live: session failed: System.InvalidOperationException: boom",
|
||||||
|
messages[1],
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -66,7 +66,8 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"this).Compose(",
|
"this).Compose(",
|
||||||
"new SessionPlayerCompositionPhase(",
|
"new SessionPlayerCompositionPhase(",
|
||||||
"FrameRootResult frameRoots = new FrameRootCompositionPhase(",
|
"FrameRootResult frameRoots = new FrameRootCompositionPhase(",
|
||||||
"frameRoots.SessionHost.Start(_options)");
|
"new SessionStartCompositionPhase(",
|
||||||
|
".Start(frameRoots);");
|
||||||
|
|
||||||
string framePhase = File.ReadAllText(Path.Combine(
|
string framePhase = File.ReadAllText(Path.Combine(
|
||||||
FindRepoRoot(),
|
FindRepoRoot(),
|
||||||
|
|
@ -97,7 +98,7 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"_publication.PublishSessionPlayer(result);");
|
"_publication.PublishSessionPlayer(result);");
|
||||||
Assert.Equal(1, CountOccurrences(
|
Assert.Equal(1, CountOccurrences(
|
||||||
body,
|
body,
|
||||||
"frameRoots.SessionHost.Start(_options)"));
|
".Start(frameRoots);"));
|
||||||
string phaseOne = Slice(
|
string phaseOne = Slice(
|
||||||
File.ReadAllText(Path.Combine(
|
File.ReadAllText(Path.Combine(
|
||||||
FindRepoRoot(),
|
FindRepoRoot(),
|
||||||
|
|
@ -117,11 +118,22 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"if (firstKeyboard is not null && firstMouse is not null)",
|
"if (firstKeyboard is not null && firstMouse is not null)",
|
||||||
phaseOne,
|
phaseOne,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
|
string startPhase = File.ReadAllText(Path.Combine(
|
||||||
|
FindRepoRoot(),
|
||||||
|
"src",
|
||||||
|
"AcDream.App",
|
||||||
|
"Composition",
|
||||||
|
"SessionStartComposition.cs"));
|
||||||
|
AssertAppearsInOrder(
|
||||||
|
startPhase,
|
||||||
|
"frame.SessionHost.Start(_dependencies.Options)",
|
||||||
|
"switch (result.Status)",
|
||||||
|
"case LiveSessionStartStatus.MissingCredentials:",
|
||||||
|
"case LiveSessionStartStatus.Failed:");
|
||||||
int start = body.IndexOf(
|
int start = body.IndexOf(
|
||||||
"frameRoots.SessionHost.Start(_options)",
|
".Start(frameRoots);",
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
string postStart = body[start..];
|
string postStart = body[(start + ".Start(frameRoots);".Length)..];
|
||||||
Assert.Contains("switch (liveStart.Status)", postStart, StringComparison.Ordinal);
|
|
||||||
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*(?:\?\?=|=)"));
|
||||||
|
|
@ -343,7 +355,8 @@ public sealed class GameWindowSlice8BoundaryTests
|
||||||
"new WorldRenderCompositionPhase(",
|
"new WorldRenderCompositionPhase(",
|
||||||
"new SessionPlayerCompositionPhase(",
|
"new SessionPlayerCompositionPhase(",
|
||||||
"new FrameRootCompositionPhase(",
|
"new FrameRootCompositionPhase(",
|
||||||
"frameRoots.SessionHost.Start(_options)");
|
"new SessionStartCompositionPhase(",
|
||||||
|
".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