refactor(update): cut over the frame orchestrator

Reduce GameWindow.OnUpdate to one typed orchestration call and remove transitive window callbacks from teardown, liveness, teleport placement, live presentation, auto-entry, and entity packet routing. Preserve the frozen retail object/network order while making the production owner graph explicit and testable.
This commit is contained in:
Erik 2026-07-22 03:31:38 +02:00
parent 947c61d2d7
commit e91f310279
18 changed files with 864 additions and 348 deletions

View file

@ -1,4 +1,5 @@
using System.Numerics;
using AcDream.App.Input;
using AcDream.App.Rendering;
using AcDream.App.Streaming;
using AcDream.App.World;
@ -1594,9 +1595,14 @@ public sealed class LiveEntityHydrationControllerTests
public readonly RecordingOrigin Origin;
public readonly RecordingNetworkSink Network = new();
public readonly RecordingTeardownCoordinator Teardown = new();
public readonly RecordingTimestampPublisher Timestamps;
public readonly LiveEntityHydrationController Controller;
public Action<uint, AcceptedPhysicsTimestamps>? PublishTimestampsAction { get; set; }
public Action<uint, AcceptedPhysicsTimestamps>? PublishTimestampsAction
{
get => Timestamps.PublishAction;
set => Timestamps.PublishAction = value;
}
public Fixture(
bool originKnown,
@ -1617,6 +1623,7 @@ public sealed class LiveEntityHydrationControllerTests
Relationships = new RecordingRelationships(Operations);
Ready = new RecordingReadyPublisher(Operations);
Origin = new RecordingOrigin(originKnown);
Timestamps = new RecordingTimestampPublisher(Operations);
Network.ApplyAction = events =>
{
if (events.Position is not { } position)
@ -1631,6 +1638,15 @@ public sealed class LiveEntityHydrationControllerTests
out _,
out _);
};
var identity = new LocalPlayerIdentityState
{
ServerGuid = playerGuid,
};
var deletion = new LiveEntityDeletionController(
Runtime,
Objects,
Teardown,
identity);
Controller = new LiveEntityHydrationController(
Runtime,
Objects,
@ -1640,13 +1656,9 @@ public sealed class LiveEntityHydrationControllerTests
Ready,
Origin,
Network,
Teardown,
(guid, timestamps) =>
{
Operations.Add($"timestamps:{timestamps.Instance}");
PublishTimestampsAction?.Invoke(guid, timestamps);
},
() => playerGuid);
Timestamps,
identity,
deletion);
}
public LiveEntityRecord Record
@ -1671,6 +1683,23 @@ public sealed class LiveEntityHydrationControllerTests
}
}
private sealed class RecordingTimestampPublisher
: IAcceptedLocalPhysicsTimestampPublisher
{
private readonly List<string> _operations;
public RecordingTimestampPublisher(List<string> operations) =>
_operations = operations;
public Action<uint, AcceptedPhysicsTimestamps>? PublishAction { get; set; }
public void Publish(uint serverGuid, AcceptedPhysicsTimestamps timestamps)
{
_operations.Add($"timestamps:{timestamps.Instance}");
PublishAction?.Invoke(serverGuid, timestamps);
}
}
private class RecordingResources : ILiveEntityResourceLifecycle
{
public int RegisterCount { get; protected set; }

View file

@ -1,3 +1,4 @@
using System.Numerics;
using AcDream.App.World;
namespace AcDream.App.Tests.World;
@ -59,4 +60,17 @@ public sealed class LiveWorldOriginStateTests
Assert.Equal(50, state.CenterX);
Assert.Equal(51, state.CenterY);
}
[Fact]
public void CellLocalForSeed_UsesTheCurrentLandblockOrigin()
{
var state = new LiveWorldOriginState();
state.SetPlaceholder(0x30, 0x32);
Vector3 local = state.CellLocalForSeed(
new Vector3(200f, -300f, 5f),
0x3130_0001u);
Assert.Equal(new Vector3(8f, 84f, 5f), local);
}
}

View file

@ -36,9 +36,8 @@ public sealed class UpdateFrameOrchestratorTests
[Fact]
public void ConditionalReconciles_RemainAtTheirOwningEdges()
{
// Checkpoint A freezes only the outer ownership contract. Checkpoints
// E and F must replace these probes with production teleport/camera
// owner tests before the conditional edges can be claimed guarded.
// The outer ownership oracle is complemented by the production
// teleport and camera tests that pin both conditional reconcile edges.
var calls = new List<string>();
UpdateFrameOrchestrator frame = Create(
calls,
@ -228,19 +227,6 @@ public sealed class UpdateFrameOrchestratorTests
FieldInfo[] ownerFields = owner.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(ownerFields, field => field.FieldType == typeof(GameWindow));
if (owner == typeof(AcDream.App.Input.RetailLocalPlayerFrameController))
{
// Checkpoint D replaced input capture with its typed owner.
// Checkpoint F still owns the remaining player presentation
// callback composition. No other phase owner may add one.
Assert.Contains(
ownerFields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
Assert.DoesNotContain(
ownerFields,
field => field.FieldType == typeof(Func<AcDream.App.Input.MovementInput>));
continue;
}
Assert.DoesNotContain(
ownerFields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
@ -277,14 +263,24 @@ public sealed class UpdateFrameOrchestratorTests
[Fact]
public void ProductionFrame_PublishesPhysicsScriptTimeExactlyOnce()
{
string root = FindRepoRoot();
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
root,
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string adapters = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Update",
"UpdateFrameRuntimeAdapters.cs"));
Assert.Equal(1, source.Split("PublishTime(", StringSplitOptions.None).Length - 1);
Assert.Equal(0, CountOccurrences(source, "PublishTime("));
Assert.Equal(1, CountOccurrences(adapters, "_runner.PublishTime("));
Assert.Contains("new AcDream.App.Update.PhysicsScriptClockPublisher(", source,
StringComparison.Ordinal);
}
[Fact]
@ -363,12 +359,10 @@ public sealed class UpdateFrameOrchestratorTests
"GameWindow.cs"));
Assert.Contains("new AcDream.App.Streaming.StreamingFrameController(", source);
Assert.Equal(1, CountOccurrences(source, "_streamingFrame.Tick();"));
AssertAppearsInOrder(
Assert.DoesNotContain("_streamingFrame", source, StringComparison.Ordinal);
Assert.Equal(1, CountOccurrences(
source,
"_streamingFrame.Tick();",
"_gameplayInputFrame!.Tick(frameTiming);",
"_liveFrameCoordinator.Tick(frameDelta);");
"_updateFrameOrchestrator.Tick("));
Assert.DoesNotContain("_streamingController.Tick(observerCx", source,
StringComparison.Ordinal);
Assert.DoesNotContain("DungeonStreamingGate.Compute", source,
@ -387,7 +381,8 @@ public sealed class UpdateFrameOrchestratorTests
"GameWindow.cs"));
Assert.Contains("new AcDream.App.Input.GameplayInputFrameController(", source);
Assert.Equal(1, CountOccurrences(source, "_gameplayInputFrame!.Tick(frameTiming);"));
Assert.DoesNotContain("_gameplayInputFrame!.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_inputDispatcher?.Tick()", source,
StringComparison.Ordinal);
Assert.DoesNotContain("TryTakeRawSample", source,
@ -468,9 +463,8 @@ public sealed class UpdateFrameOrchestratorTests
"new AcDream.App.Streaming.LocalPlayerTeleportController(",
source,
StringComparison.Ordinal);
Assert.Equal(1, CountOccurrences(
source,
"_localPlayerTeleport!.Tick(frameDelta);"));
Assert.DoesNotContain("_localPlayerTeleport!.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_teleportTransit", source, StringComparison.Ordinal);
Assert.DoesNotContain("_teleportAnim", source, StringComparison.Ordinal);
Assert.DoesNotContain("_teleportViewPlane", source, StringComparison.Ordinal);
@ -486,9 +480,10 @@ public sealed class UpdateFrameOrchestratorTests
StringComparison.Ordinal);
AssertAppearsInOrder(
source,
"_liveEntityLiveness?.Tick(ClientTimerNow());",
"_localPlayerTeleport!.Tick(frameDelta);",
"_playerModeAutoEntry?.TryEnter();");
"new AcDream.App.Update.LiveEntityLivenessFramePhase(",
"_localPlayerTeleport,",
"new AcDream.App.Update.PlayerModeAutoEntryFramePhase(",
"cameraFrame);");
}
[Fact]
@ -649,7 +644,9 @@ public sealed class UpdateFrameOrchestratorTests
"AcDream.App",
"Rendering",
"GameWindow.cs"));
Assert.Equal(1, CountOccurrences(source, "_cameraFrame.Tick(frameTiming);"));
Assert.Equal(1, CountOccurrences(
source,
"_updateFrameOrchestrator.Tick("));
Assert.DoesNotContain("CanAdvanceLocalPlayer", source, StringComparison.Ordinal);
Assert.DoesNotContain("GetCombatCameraTargetPoint()", source,
StringComparison.Ordinal);
@ -657,11 +654,7 @@ public sealed class UpdateFrameOrchestratorTests
StringComparison.Ordinal);
Assert.DoesNotContain("_localPlayerFrame.TryGetPresentationAfterNetwork", source,
StringComparison.Ordinal);
AssertAppearsInOrder(
source,
"_localPlayerTeleport!.Tick(frameDelta);",
"_playerModeAutoEntry?.TryEnter();",
"_cameraFrame.Tick(frameTiming);");
Assert.DoesNotContain("_cameraFrame", source, StringComparison.Ordinal);
string cameraSource = File.ReadAllText(Path.Combine(
root,
@ -677,6 +670,123 @@ public sealed class UpdateFrameOrchestratorTests
"retail?.Update(");
}
[Fact]
public void GameWindow_OnUpdateOwnsOnlyProfilingAndOneOrchestratorTick()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
Assert.Equal(1, CountOccurrences(
source,
"_updateFrameOrchestrator.Tick("));
Assert.DoesNotContain("_updateFrameClock.Advance(", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_liveFrameCoordinator", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_liveEntityLiveness?.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_playerModeAutoEntry?.TryEnter", source,
StringComparison.Ordinal);
AssertAppearsInOrder(
source,
"private void OnUpdate(double dt)",
"_frameProfiler.BeginStage(",
"_updateFrameOrchestrator.Tick(",
"private void OnCameraModeChanged");
}
[Fact]
public void ProductionFrameAdaptersRetainTypedOwnersWithoutWindowCallbacks()
{
Type[] adapters =
[
typeof(LiveEntityTeardownFramePhase),
typeof(ConsoleUpdateFrameFailureSink),
typeof(PhysicsScriptClockPublisher),
typeof(LiveEntityLivenessFramePhase),
typeof(PlayerModeAutoEntryFramePhase),
];
foreach (Type adapter in adapters)
{
FieldInfo[] fields = adapter.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
fields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
FieldInfo clock = Assert.Single(
typeof(LiveEntityLivenessFramePhase).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_clock");
Assert.Equal(typeof(IClientMonotonicTimeSource), clock.FieldType);
FieldInfo teardownRuntime = Assert.Single(
typeof(LiveEntityTeardownFramePhase).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic));
Assert.Equal(typeof(LiveEntityRuntime), teardownRuntime.FieldType);
Type[] typedProductionOwners =
[
typeof(LiveEntityLivenessController),
typeof(AcDream.App.Input.LivePlayerModeAutoEntryContext),
typeof(LiveSessionLocalPhysicsTimestampPublisher),
typeof(AcDream.App.Physics.LiveEntityNetworkUpdateController),
typeof(AcDream.App.Rendering.LiveEntityPartArrayLifecycle),
typeof(AcDream.App.Physics.RemoteShadowPlacementSynchronizer),
typeof(AcDream.App.Physics.RemoteTeleportPlacementPresentation),
typeof(AcDream.App.Net.LiveEntitySessionController),
];
foreach (Type owner in typedProductionOwners)
{
FieldInfo[] fields = owner.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.DoesNotContain(fields, field => field.FieldType == typeof(GameWindow));
Assert.DoesNotContain(
fields,
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
}
FieldInfo originIdentity = Assert.Single(
typeof(LiveEntityWorldOriginCoordinator).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.Name == "_identity");
Assert.Equal(
typeof(AcDream.App.Input.ILocalPlayerIdentitySource),
originIdentity.FieldType);
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
Assert.DoesNotContain("PublishLocalPhysicsTimestamps", source,
StringComparison.Ordinal);
Assert.DoesNotContain("LoginWorldReady", source,
StringComparison.Ordinal);
Assert.DoesNotContain("candidate => _liveEntityHydration.OnPrune", source,
StringComparison.Ordinal);
Assert.DoesNotContain("CreateLiveEntitySessionSink", source,
StringComparison.Ordinal);
Assert.DoesNotContain("private System.Numerics.Vector3 CellLocalForSeed", source,
StringComparison.Ordinal);
Assert.DoesNotContain("OnPlayScriptReceived", source,
StringComparison.Ordinal);
Assert.Contains("_liveWorldOrigin.GetCenter", source,
StringComparison.Ordinal);
Assert.Contains("_liveWorldOrigin.CellLocalForSeed", source,
StringComparison.Ordinal);
Assert.Contains("_liveEntitySessionEvents.CreateSink()", source,
StringComparison.Ordinal);
}
private static UpdateFrameOrchestrator Create(
List<string> calls,
RecordingTeardown? teardown = null,