refactor(runtime): extract local teleport and player mode
Move local teleport, player-mode, animation, shadow, and sealed-dungeon lifetimes out of GameWindow. Make player-mode entry transactional and isolate approach completions by controller lifetime and approach generation so stale callbacks cannot affect replacements. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
c557038353
commit
eeb0f6b45c
29 changed files with 3311 additions and 1073 deletions
|
|
@ -175,7 +175,16 @@ public sealed class GameWindowLiveEntityCompositionTests
|
|||
string source = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(), "src", "AcDream.App", "Rendering", "GameWindow.cs"));
|
||||
|
||||
Assert.Contains("_localPlayerMode.ResetSession();", source, StringComparison.Ordinal);
|
||||
Assert.Contains("_playerModeController?.ResetSession();", source,
|
||||
StringComparison.Ordinal);
|
||||
string playerModeSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"PlayerModeController.cs"));
|
||||
Assert.Contains("_mode.ResetSession();", playerModeSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_liveEntityNetworkUpdates?.ResetSessionState();",
|
||||
source,
|
||||
|
|
|
|||
|
|
@ -124,6 +124,40 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
|||
Assert.Equal(12f, watcher.Position.Frame.Origin.X);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreparedRebind_DoesNotPublishDelegatesUntilCommit()
|
||||
{
|
||||
const uint guid = 0x70000025u;
|
||||
var runtime = Runtime();
|
||||
LiveEntityRecord record = runtime.RegisterLiveEntity(Spawn(guid, 1)).Record!;
|
||||
EntityPhysicsHost original = Host(
|
||||
guid,
|
||||
position: new Vector3(1f, 0f, 0f));
|
||||
runtime.InstallPhysicsHost(record, original);
|
||||
EntityPhysicsHost configuration = Host(
|
||||
guid,
|
||||
position: new Vector3(9f, 0f, 0f));
|
||||
|
||||
EntityPhysicsHost stable = EntityPhysicsHost.SelectStableHostWithoutRebind(
|
||||
runtime,
|
||||
record,
|
||||
configuration);
|
||||
|
||||
// A late DAT/physics/camera preparation failure occurs here in the
|
||||
// player-mode transaction. The live record must still expose its
|
||||
// previous delegates and manager identity.
|
||||
Assert.Same(original, stable);
|
||||
Assert.Same(original, record.PhysicsHost);
|
||||
Assert.Equal(1f, original.Position.Frame.Origin.X);
|
||||
|
||||
EntityPhysicsHost committed = EntityPhysicsHost.InstallOrRebind(
|
||||
runtime,
|
||||
record,
|
||||
configuration);
|
||||
Assert.Same(original, committed);
|
||||
Assert.Equal(9f, committed.Position.Frame.Origin.X);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SameGuidReplacement_HostCallbacksRemainIncarnationLocal()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public sealed class LiveEntityWorldOriginCoordinatorTests
|
|||
world,
|
||||
Reveal(),
|
||||
() => playerGuid,
|
||||
_ => false);
|
||||
new FakeSealedDungeonCellClassifier());
|
||||
|
||||
LiveEntityOriginInitialization ignored = coordinator.TryInitialize(
|
||||
Spawn(0x50000002u, 0x09090001u));
|
||||
|
|
@ -59,7 +59,7 @@ public sealed class LiveEntityWorldOriginCoordinatorTests
|
|||
world,
|
||||
Reveal(),
|
||||
() => playerGuid,
|
||||
_ => false);
|
||||
new FakeSealedDungeonCellClassifier());
|
||||
|
||||
Assert.True(coordinator.TryInitialize(Spawn(playerGuid, 0x03040001u)).IsKnown);
|
||||
origin.Reset();
|
||||
|
|
@ -114,4 +114,10 @@ public sealed class LiveEntityWorldOriginCoordinatorTests
|
|||
ItemType: null,
|
||||
MotionState: null,
|
||||
MotionTableId: null);
|
||||
|
||||
private sealed class FakeSealedDungeonCellClassifier
|
||||
: ISealedDungeonCellClassifier
|
||||
{
|
||||
public bool IsSealedDungeon(uint cellId) => false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -414,18 +414,32 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("wantCaptureMouse: ()", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Equal(5, CountOccurrences(
|
||||
Assert.Equal(2, CountOccurrences(
|
||||
source,
|
||||
"_gameplayInputFrame?.EndMouseLook();"));
|
||||
Assert.Contains(
|
||||
"new(\"mouse capture\", () => _gameplayInputFrame?.EndMouseLook())",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
string teleportSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Streaming",
|
||||
"LocalPlayerTeleportController.cs"));
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"_teleportTransit.CanBegin(teleportSequence)",
|
||||
"_gameplayInputFrame?.EndMouseLook();",
|
||||
"_teleportTransit.QueueStart(teleportSequence)");
|
||||
teleportSource,
|
||||
"_transit.CanBegin(teleportSequence)",
|
||||
"_input.EndMouseLook();",
|
||||
"_transit.QueueStart(teleportSequence)");
|
||||
string playerModeSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"PlayerModeController.cs"));
|
||||
Assert.Contains("_input.EndMouseLook();", playerModeSource,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"private void OnFocusChanged(bool focused)",
|
||||
|
|
@ -433,6 +447,50 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
"_gameplayInputFrame?.EndMouseLook();");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameWindow_DelegatesTheCompleteLocalTeleportLifetime()
|
||||
{
|
||||
string root = FindRepoRoot();
|
||||
string source = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Rendering",
|
||||
"GameWindow.cs"));
|
||||
string networkSource = File.ReadAllText(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Physics",
|
||||
"LiveEntityNetworkUpdateController.cs"));
|
||||
|
||||
Assert.Contains(
|
||||
"new AcDream.App.Streaming.LocalPlayerTeleportController(",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Equal(1, CountOccurrences(
|
||||
source,
|
||||
"_localPlayerTeleport!.Tick(frameDelta);"));
|
||||
Assert.DoesNotContain("_teleportTransit", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_teleportAnim", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_teleportViewPlane", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_pendingTeleport", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("AimTeleportDestination", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("ResetTeleportTransitState", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("PlaceTeleportArrival", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("TryActivatePendingTeleportPresentation", source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Action<WorldSession.EntityPositionUpdate>", networkSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("ILocalPlayerTeleportNetworkSink", networkSource,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
source,
|
||||
"_liveEntityLiveness?.Tick(ClientTimerNow());",
|
||||
"_localPlayerTeleport!.Tick(frameDelta);",
|
||||
"_playerModeAutoEntry?.TryEnter();");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameplayInputOwnersUseTypedSeamsWithoutGameWindowBackReferences()
|
||||
{
|
||||
|
|
@ -498,6 +556,45 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
||||
|
||||
FieldInfo approachCompletions = Assert.Single(
|
||||
typeof(AcDream.App.Input.PlayerModeController).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.Name == "_approachCompletions");
|
||||
Assert.Equal(
|
||||
typeof(AcDream.App.Interaction.IPlayerApproachCompletionLifetimeOwner),
|
||||
approachCompletions.FieldType);
|
||||
Assert.DoesNotContain(
|
||||
typeof(AcDream.App.Input.PlayerModeController).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => field.FieldType
|
||||
== typeof(AcDream.App.Interaction.SelectionInteractionController));
|
||||
Assert.DoesNotContain(
|
||||
typeof(AcDream.App.Interaction.PlayerApproachCompletionState).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic),
|
||||
field => typeof(Delegate).IsAssignableFrom(field.FieldType));
|
||||
|
||||
string playerModeSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"PlayerModeController.cs"));
|
||||
AssertAppearsInOrder(
|
||||
playerModeSource,
|
||||
"controller.PreparePositionForCommit(",
|
||||
"_camera.EnterChaseMode(legacyCamera, retailCamera);",
|
||||
"EntityPhysicsHost.SelectStableHostWithoutRebind(",
|
||||
"_shadow.SyncPose(",
|
||||
"EntityPhysicsHost.InstallOrRebind(",
|
||||
"playerEntity.SetPosition(initial.Position);",
|
||||
"controller.CommitPreparedPosition();",
|
||||
"_controllerSlot.Controller = controller;",
|
||||
"_mode.IsPlayerMode = true;");
|
||||
Assert.Contains("_shadow.Restore(playerEntity, priorShadow);", playerModeSource,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("_camera.RestoreState(priorCamera);", playerModeSource,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
string mouseLookSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue