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
|
|
@ -193,6 +193,36 @@ public sealed class EntityPhysicsHost : IPhysicsObjHost
|
|||
return existing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an exact incarnation and returns the manager-stable host that
|
||||
/// a later <see cref="InstallOrRebind"/> will publish, without changing any
|
||||
/// live delegates. Player-mode construction uses this preparation edge so
|
||||
/// DAT/physics/camera failures cannot expose a half-rebound host.
|
||||
/// </summary>
|
||||
internal static EntityPhysicsHost SelectStableHostWithoutRebind(
|
||||
LiveEntityRuntime runtime,
|
||||
LiveEntityRecord expectedRecord,
|
||||
EntityPhysicsHost configuration)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(runtime);
|
||||
ArgumentNullException.ThrowIfNull(expectedRecord);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
if (!runtime.TryGetRecord(expectedRecord.ServerGuid, out LiveEntityRecord current)
|
||||
|| !ReferenceEquals(current, expectedRecord))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{expectedRecord.ServerGuid:X8} changed incarnation during physics-host preparation.");
|
||||
}
|
||||
|
||||
return current.PhysicsHost switch
|
||||
{
|
||||
null => configuration,
|
||||
EntityPhysicsHost existing => existing,
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Live entity 0x{expectedRecord.ServerGuid:X8} owns an incompatible physics-host implementation."),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the position-only CPhysicsObj facade used before an animated or
|
||||
/// player movement owner exists. The delegates capture the exact live
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
private readonly IAnimationLoader _animLoader;
|
||||
private readonly CombatTargetController? _combatTargetController;
|
||||
private readonly LiveWorldOriginState _origin;
|
||||
private readonly AcDream.App.Streaming.TeleportTransitCoordinator<
|
||||
WorldSession.EntityPositionUpdate> _teleportTransit;
|
||||
private readonly AcDream.App.Streaming.ILocalPlayerTeleportNetworkSink
|
||||
_localPlayerTeleport;
|
||||
private readonly Func<PlayerMovementController?> _playerControllerSource;
|
||||
private readonly LocalPlayerOutboundController _localPlayerOutbound;
|
||||
private readonly Func<EntityPhysicsHost?> _playerHostSource;
|
||||
|
|
@ -57,7 +57,6 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
private readonly IPhysicsScriptTimeSource _gameTime;
|
||||
private readonly Func<WorldSession?> _session;
|
||||
private readonly LiveEntityInboundAuthorityGate _authorityGate;
|
||||
private readonly Action<WorldSession.EntityPositionUpdate> _aimTeleportDestination;
|
||||
private readonly IMovementTruthDiagnosticSink _movementTruthDiagnostics;
|
||||
|
||||
private PlayerMovementController? _playerController => _playerControllerSource();
|
||||
|
|
@ -96,7 +95,7 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
IAnimationLoader animLoader,
|
||||
CombatTargetController? combatTargetController,
|
||||
LiveWorldOriginState origin,
|
||||
AcDream.App.Streaming.TeleportTransitCoordinator<WorldSession.EntityPositionUpdate> teleportTransit,
|
||||
AcDream.App.Streaming.ILocalPlayerTeleportNetworkSink localPlayerTeleport,
|
||||
Func<PlayerMovementController?> playerControllerSource,
|
||||
LocalPlayerOutboundController localPlayerOutbound,
|
||||
Func<EntityPhysicsHost?> playerHostSource,
|
||||
|
|
@ -104,7 +103,6 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
IPhysicsScriptTimeSource gameTime,
|
||||
Func<WorldSession?> session,
|
||||
Action<uint, AcceptedPhysicsTimestamps> publishTimestamps,
|
||||
Action<WorldSession.EntityPositionUpdate> aimTeleportDestination,
|
||||
IMovementTruthDiagnosticSink movementTruthDiagnostics)
|
||||
{
|
||||
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
|
||||
|
|
@ -127,7 +125,8 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
_animLoader = animLoader ?? throw new ArgumentNullException(nameof(animLoader));
|
||||
_combatTargetController = combatTargetController;
|
||||
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
|
||||
_teleportTransit = teleportTransit ?? throw new ArgumentNullException(nameof(teleportTransit));
|
||||
_localPlayerTeleport = localPlayerTeleport
|
||||
?? throw new ArgumentNullException(nameof(localPlayerTeleport));
|
||||
_playerControllerSource = playerControllerSource ?? throw new ArgumentNullException(nameof(playerControllerSource));
|
||||
_localPlayerOutbound = localPlayerOutbound
|
||||
?? throw new ArgumentNullException(nameof(localPlayerOutbound));
|
||||
|
|
@ -138,7 +137,6 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
_authorityGate = new LiveEntityInboundAuthorityGate(
|
||||
liveEntities,
|
||||
publishTimestamps);
|
||||
_aimTeleportDestination = aimTeleportDestination ?? throw new ArgumentNullException(nameof(aimTeleportDestination));
|
||||
_movementTruthDiagnostics = movementTruthDiagnostics
|
||||
?? throw new ArgumentNullException(nameof(movementTruthDiagnostics));
|
||||
}
|
||||
|
|
@ -1776,14 +1774,11 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
// packet first. The presentation coordinator exposes exactly one
|
||||
// sequence-correlated destination without reordering that state.
|
||||
if (timestampDisposition is AcDream.Core.Physics.PositionTimestampDisposition.Apply
|
||||
&& update.Guid == _playerServerGuid
|
||||
&& _teleportTransit.OfferDestination(
|
||||
update.TeleportSequence,
|
||||
timestamps.TeleportAdvanced,
|
||||
update,
|
||||
out var teleportDestination))
|
||||
&& update.Guid == _playerServerGuid)
|
||||
{
|
||||
_aimTeleportDestination(teleportDestination);
|
||||
_localPlayerTeleport.OfferDestination(
|
||||
update,
|
||||
timestamps.TeleportAdvanced);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
117
src/AcDream.App/Physics/LocalPlayerShadowSynchronizer.cs
Normal file
117
src/AcDream.App/Physics/LocalPlayerShadowSynchronizer.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Owns publication and suspension of the local player's collision shadow.
|
||||
/// Identity/incarnation checks stay beside the cached shadow pose so player
|
||||
/// mode, projection, and teleport placement cannot publish different owners.
|
||||
/// </summary>
|
||||
internal sealed class LocalPlayerShadowSynchronizer
|
||||
{
|
||||
private readonly PhysicsEngine _physics;
|
||||
private readonly LiveEntityRuntime _liveEntities;
|
||||
private readonly ILocalPlayerIdentitySource _identity;
|
||||
private readonly LiveWorldOriginState _origin;
|
||||
private readonly LocalPlayerShadowState _state;
|
||||
|
||||
public LocalPlayerShadowSynchronizer(
|
||||
PhysicsEngine physics,
|
||||
LiveEntityRuntime liveEntities,
|
||||
ILocalPlayerIdentitySource identity,
|
||||
LiveWorldOriginState origin,
|
||||
LocalPlayerShadowState state)
|
||||
{
|
||||
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
||||
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
|
||||
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
|
||||
_origin = origin ?? throw new ArgumentNullException(nameof(origin));
|
||||
_state = state ?? throw new ArgumentNullException(nameof(state));
|
||||
}
|
||||
|
||||
public void Sync(WorldEntity playerEntity, uint cellId, bool force = false)
|
||||
=> SyncPose(
|
||||
playerEntity,
|
||||
playerEntity.Position,
|
||||
playerEntity.Rotation,
|
||||
cellId,
|
||||
force);
|
||||
|
||||
public LocalPlayerShadowState.Snapshot? Capture() => _state.Current;
|
||||
|
||||
public void SyncPose(
|
||||
WorldEntity playerEntity,
|
||||
System.Numerics.Vector3 position,
|
||||
System.Numerics.Quaternion orientation,
|
||||
uint cellId,
|
||||
bool force = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(playerEntity);
|
||||
if (_liveEntities.IsHidden(_identity.ServerGuid)
|
||||
|| cellId == 0
|
||||
|| !IsCurrentVisibleProjection(playerEntity))
|
||||
{
|
||||
Suspend(playerEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force
|
||||
&& _state.Current is { } last
|
||||
&& last.CellId == cellId
|
||||
&& System.Numerics.Vector3.DistanceSquared(
|
||||
last.Position,
|
||||
position) <= 1e-4f
|
||||
&& MathF.Abs(System.Numerics.Quaternion.Dot(
|
||||
last.Orientation,
|
||||
orientation)) >= 0.99999f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ShadowPositionSynchronizer.Sync(
|
||||
_physics.ShadowObjects,
|
||||
playerEntity.Id,
|
||||
position,
|
||||
orientation,
|
||||
cellId,
|
||||
_origin.CenterX,
|
||||
_origin.CenterY);
|
||||
_state.Set(position, orientation, cellId);
|
||||
}
|
||||
|
||||
public void Restore(
|
||||
WorldEntity playerEntity,
|
||||
LocalPlayerShadowState.Snapshot? snapshot)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(playerEntity);
|
||||
if (snapshot is not { } prior || !IsCurrentVisibleProjection(playerEntity))
|
||||
{
|
||||
Suspend(playerEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
SyncPose(
|
||||
playerEntity,
|
||||
prior.Position,
|
||||
prior.Orientation,
|
||||
prior.CellId,
|
||||
force: true);
|
||||
}
|
||||
|
||||
public bool IsCurrentVisibleProjection(WorldEntity playerEntity) =>
|
||||
_liveEntities.TryGetRecord(playerEntity.ServerGuid, out LiveEntityRecord record)
|
||||
&& ReferenceEquals(record.WorldEntity, playerEntity)
|
||||
&& _liveEntities.IsCurrentSpatialRootObject(record);
|
||||
|
||||
public void Suspend(WorldEntity playerEntity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(playerEntity);
|
||||
_physics.ShadowObjects.Suspend(playerEntity.Id);
|
||||
_state.Clear();
|
||||
}
|
||||
|
||||
public void ResetSession() => _state.Clear();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue