refactor(runtime): close simulation ownership
Move remote-motion construction, CreateObject vector initialization, final simulation-component retirement, and the combined J5 ownership ledger into Runtime. Delete App compatibility views and moved-state reconstruction while preserving the existing graphical projection and retail update order.
This commit is contained in:
parent
c30a3efeb0
commit
cdee7a4b49
57 changed files with 1013 additions and 410 deletions
|
|
@ -122,7 +122,7 @@ internal sealed class LiveCombatAttackOperations
|
||||||
private readonly CombatState _combat;
|
private readonly CombatState _combat;
|
||||||
private readonly ICombatAttackTargetSource _targets;
|
private readonly ICombatAttackTargetSource _targets;
|
||||||
private readonly ICombatGameplaySettingsSource _settings;
|
private readonly ICombatGameplaySettingsSource _settings;
|
||||||
private readonly ILocalPlayerControllerSource _player;
|
private readonly IRuntimeLocalPlayerControllerSource _player;
|
||||||
private readonly LocalPlayerOutboundController _outbound;
|
private readonly LocalPlayerOutboundController _outbound;
|
||||||
private readonly ILiveInWorldSource _inWorld;
|
private readonly ILiveInWorldSource _inWorld;
|
||||||
private readonly ILiveWorldSessionSource _session;
|
private readonly ILiveWorldSessionSource _session;
|
||||||
|
|
@ -132,7 +132,7 @@ internal sealed class LiveCombatAttackOperations
|
||||||
CombatState combat,
|
CombatState combat,
|
||||||
ICombatAttackTargetSource targets,
|
ICombatAttackTargetSource targets,
|
||||||
ICombatGameplaySettingsSource settings,
|
ICombatGameplaySettingsSource settings,
|
||||||
ILocalPlayerControllerSource player,
|
IRuntimeLocalPlayerControllerSource player,
|
||||||
LocalPlayerOutboundController outbound,
|
LocalPlayerOutboundController outbound,
|
||||||
ILiveInWorldSource inWorld,
|
ILiveInWorldSource inWorld,
|
||||||
ILiveWorldSessionSource session,
|
ILiveWorldSessionSource session,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ internal sealed record FrameRootDependencies(
|
||||||
LocalPlayerModeState PlayerMode,
|
LocalPlayerModeState PlayerMode,
|
||||||
LocalPlayerIdentityState PlayerIdentity,
|
LocalPlayerIdentityState PlayerIdentity,
|
||||||
ChaseCameraInputState ChaseCameraInput,
|
ChaseCameraInputState ChaseCameraInput,
|
||||||
LocalPlayerControllerSlot PlayerController,
|
RuntimeLocalPlayerMovementState PlayerController,
|
||||||
LiveWorldOriginState WorldOrigin,
|
LiveWorldOriginState WorldOrigin,
|
||||||
ParticleVisibilityController ParticleVisibility,
|
ParticleVisibilityController ParticleVisibility,
|
||||||
EntityEffectPoseRegistry EffectPoses,
|
EntityEffectPoseRegistry EffectPoses,
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ internal sealed record InteractionRetainedUiDependencies(
|
||||||
BufferedUiRegistry? UiRegistry,
|
BufferedUiRegistry? UiRegistry,
|
||||||
LiveCombatModeCommandSlot CombatModeCommands,
|
LiveCombatModeCommandSlot CombatModeCommands,
|
||||||
ILocalPlayerIdentitySource PlayerIdentity,
|
ILocalPlayerIdentitySource PlayerIdentity,
|
||||||
ILocalPlayerControllerSource PlayerController,
|
IRuntimeLocalPlayerControllerSource PlayerController,
|
||||||
ILocalPlayerModeSource PlayerMode,
|
ILocalPlayerModeSource PlayerMode,
|
||||||
Func<DeferredSelectionViewPlaneSource, SelectionCameraSource>
|
Func<DeferredSelectionViewPlaneSource, SelectionCameraSource>
|
||||||
SelectionCameraFactory,
|
SelectionCameraFactory,
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ internal sealed record LivePresentationDependencies(
|
||||||
CellVisibility CellVisibility,
|
CellVisibility CellVisibility,
|
||||||
LiveWorldOriginState WorldOrigin,
|
LiveWorldOriginState WorldOrigin,
|
||||||
LocalPlayerIdentityState PlayerIdentity,
|
LocalPlayerIdentityState PlayerIdentity,
|
||||||
LocalPlayerControllerSlot PlayerController,
|
RuntimeLocalPlayerMovementState PlayerController,
|
||||||
PointerPositionState PointerPosition,
|
PointerPositionState PointerPosition,
|
||||||
PlayerApproachCompletionState PlayerApproachCompletions,
|
PlayerApproachCompletionState PlayerApproachCompletions,
|
||||||
GameRenderResourceLifetime RenderResourceLifetime,
|
GameRenderResourceLifetime RenderResourceLifetime,
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ internal sealed record SessionPlayerDependencies(
|
||||||
RetailInboundEventDispatcher InboundEntityEvents,
|
RetailInboundEventDispatcher InboundEntityEvents,
|
||||||
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
||||||
LocalPlayerIdentityState PlayerIdentity,
|
LocalPlayerIdentityState PlayerIdentity,
|
||||||
LocalPlayerControllerSlot PlayerController,
|
RuntimeLocalPlayerMovementState PlayerController,
|
||||||
LocalPlayerPhysicsHostSlot PlayerHost,
|
LocalPlayerPhysicsHostSlot PlayerHost,
|
||||||
LocalPlayerModeState PlayerMode,
|
LocalPlayerModeState PlayerMode,
|
||||||
ChaseCameraInputState ChaseCameraInput,
|
ChaseCameraInputState ChaseCameraInput,
|
||||||
|
|
@ -507,7 +507,6 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
live.ProjectileController,
|
live.ProjectileController,
|
||||||
live.RemoteTeleport,
|
live.RemoteTeleport,
|
||||||
d.AnimatedEntities,
|
d.AnimatedEntities,
|
||||||
new LiveEntityRemoteMotionRuntimeView<RemoteMotion>(d.RuntimeSlot),
|
|
||||||
d.RemoteMovementObservations,
|
d.RemoteMovementObservations,
|
||||||
d.RemotePhysicsUpdater,
|
d.RemotePhysicsUpdater,
|
||||||
d.RemoteInboundMotion,
|
d.RemoteInboundMotion,
|
||||||
|
|
@ -874,7 +873,6 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
sessionHost,
|
sessionHost,
|
||||||
liveSessionCommands,
|
liveSessionCommands,
|
||||||
d.PlayerIdentity,
|
d.PlayerIdentity,
|
||||||
live.LiveEntities,
|
|
||||||
d.EntityObjects,
|
d.EntityObjects,
|
||||||
d.Inventory,
|
d.Inventory,
|
||||||
d.Character,
|
d.Character,
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ internal interface INearbyWorldDiagnosticSource
|
||||||
internal sealed class RuntimeNearbyWorldDiagnosticSource : INearbyWorldDiagnosticSource
|
internal sealed class RuntimeNearbyWorldDiagnosticSource : INearbyWorldDiagnosticSource
|
||||||
{
|
{
|
||||||
private readonly ILocalPlayerModeSource _mode;
|
private readonly ILocalPlayerModeSource _mode;
|
||||||
private readonly ILocalPlayerControllerSource _player;
|
private readonly IRuntimeLocalPlayerControllerSource _player;
|
||||||
private readonly CameraController _camera;
|
private readonly CameraController _camera;
|
||||||
private readonly LiveWorldOriginState _origin;
|
private readonly LiveWorldOriginState _origin;
|
||||||
private readonly GpuWorldState _world;
|
private readonly GpuWorldState _world;
|
||||||
|
|
@ -170,7 +170,7 @@ internal sealed class RuntimeNearbyWorldDiagnosticSource : INearbyWorldDiagnosti
|
||||||
|
|
||||||
public RuntimeNearbyWorldDiagnosticSource(
|
public RuntimeNearbyWorldDiagnosticSource(
|
||||||
ILocalPlayerModeSource mode,
|
ILocalPlayerModeSource mode,
|
||||||
ILocalPlayerControllerSource player,
|
IRuntimeLocalPlayerControllerSource player,
|
||||||
CameraController camera,
|
CameraController camera,
|
||||||
LiveWorldOriginState origin,
|
LiveWorldOriginState origin,
|
||||||
GpuWorldState world,
|
GpuWorldState world,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,4 @@
|
||||||
global using AcDream.Runtime.Gameplay;
|
global using AcDream.Runtime.Gameplay;
|
||||||
global using AcDream.Runtime.Physics;
|
global using AcDream.Runtime.Physics;
|
||||||
global using ILiveEntityRemoteMotionRuntime =
|
|
||||||
AcDream.Runtime.Physics.IRuntimeRemoteMotion;
|
|
||||||
global using ILiveEntityPhysicsHostConsumer =
|
|
||||||
AcDream.Runtime.Physics.IRuntimePhysicsHostConsumer;
|
|
||||||
global using ILiveEntityCanonicalRuntimeConsumer =
|
|
||||||
AcDream.Runtime.Physics.IRuntimeCanonicalPhysicsConsumer;
|
|
||||||
global using ILiveEntityCanonicalCellConsumer =
|
|
||||||
AcDream.Runtime.Physics.IRuntimeCanonicalCellConsumer;
|
|
||||||
global using ILiveEntityRemotePlacementRuntime =
|
|
||||||
AcDream.Runtime.Physics.IRuntimeRemotePlacement;
|
|
||||||
global using LocalPlayerControllerSlot =
|
|
||||||
AcDream.Runtime.Gameplay.RuntimeLocalPlayerMovementState;
|
|
||||||
global using ILocalPlayerControllerSource =
|
|
||||||
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerControllerSource;
|
|
||||||
global using ILocalPlayerMotionSource =
|
global using ILocalPlayerMotionSource =
|
||||||
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerMotionSource;
|
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerMotionSource;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ internal sealed class LiveLocalPlayerFrameRuntime : ILocalPlayerFrameRuntime
|
||||||
{
|
{
|
||||||
private readonly CameraController _camera;
|
private readonly CameraController _camera;
|
||||||
private readonly ILocalPlayerModeSource _mode;
|
private readonly ILocalPlayerModeSource _mode;
|
||||||
private readonly ILocalPlayerControllerSource _controller;
|
private readonly IRuntimeLocalPlayerControllerSource _controller;
|
||||||
private readonly IChaseCameraSource _chase;
|
private readonly IChaseCameraSource _chase;
|
||||||
private readonly DispatcherMovementInputSource _input;
|
private readonly DispatcherMovementInputSource _input;
|
||||||
private readonly IInputCaptureSource _capture;
|
private readonly IInputCaptureSource _capture;
|
||||||
|
|
@ -49,7 +49,7 @@ internal sealed class LiveLocalPlayerFrameRuntime : ILocalPlayerFrameRuntime
|
||||||
public LiveLocalPlayerFrameRuntime(
|
public LiveLocalPlayerFrameRuntime(
|
||||||
CameraController camera,
|
CameraController camera,
|
||||||
ILocalPlayerModeSource mode,
|
ILocalPlayerModeSource mode,
|
||||||
ILocalPlayerControllerSource controller,
|
IRuntimeLocalPlayerControllerSource controller,
|
||||||
IChaseCameraSource chase,
|
IChaseCameraSource chase,
|
||||||
DispatcherMovementInputSource input,
|
DispatcherMovementInputSource input,
|
||||||
IInputCaptureSource capture,
|
IInputCaptureSource capture,
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ internal sealed class MouseLookController : IMouseLookInputFrameController
|
||||||
private readonly IMouseSource _mouseSource;
|
private readonly IMouseSource _mouseSource;
|
||||||
private readonly IPointerPositionSource _pointer;
|
private readonly IPointerPositionSource _pointer;
|
||||||
private readonly ILocalPlayerModeSource _playerMode;
|
private readonly ILocalPlayerModeSource _playerMode;
|
||||||
private readonly ILocalPlayerControllerSource _playerController;
|
private readonly IRuntimeLocalPlayerControllerSource _playerController;
|
||||||
private readonly CameraController _camera;
|
private readonly CameraController _camera;
|
||||||
private readonly ChaseCameraInputState _chase;
|
private readonly ChaseCameraInputState _chase;
|
||||||
private readonly IMovementInputSource _movementInput;
|
private readonly IMovementInputSource _movementInput;
|
||||||
|
|
@ -111,7 +111,7 @@ internal sealed class MouseLookController : IMouseLookInputFrameController
|
||||||
IMouseSource mouseSource,
|
IMouseSource mouseSource,
|
||||||
IPointerPositionSource pointer,
|
IPointerPositionSource pointer,
|
||||||
ILocalPlayerModeSource playerMode,
|
ILocalPlayerModeSource playerMode,
|
||||||
ILocalPlayerControllerSource playerController,
|
IRuntimeLocalPlayerControllerSource playerController,
|
||||||
CameraController camera,
|
CameraController camera,
|
||||||
ChaseCameraInputState chase,
|
ChaseCameraInputState chase,
|
||||||
IMovementInputSource movementInput,
|
IMovementInputSource movementInput,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ internal sealed class MovementTruthDiagnosticController
|
||||||
: IMovementTruthDiagnosticSink
|
: IMovementTruthDiagnosticSink
|
||||||
{
|
{
|
||||||
private readonly bool _enabled;
|
private readonly bool _enabled;
|
||||||
private readonly ILocalPlayerControllerSource _player;
|
private readonly IRuntimeLocalPlayerControllerSource _player;
|
||||||
private readonly ILocalPlayerIdentitySource _identity;
|
private readonly ILocalPlayerIdentitySource _identity;
|
||||||
private MovementTruthOutbound? _lastOutbound;
|
private MovementTruthOutbound? _lastOutbound;
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ internal sealed class MovementTruthDiagnosticController
|
||||||
|
|
||||||
public MovementTruthDiagnosticController(
|
public MovementTruthDiagnosticController(
|
||||||
bool enabled,
|
bool enabled,
|
||||||
ILocalPlayerControllerSource player,
|
IRuntimeLocalPlayerControllerSource player,
|
||||||
ILocalPlayerIdentitySource identity)
|
ILocalPlayerIdentitySource identity)
|
||||||
{
|
{
|
||||||
_enabled = enabled;
|
_enabled = enabled;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ internal sealed class PlayerModeController :
|
||||||
IDevToolsPlayerModeTarget
|
IDevToolsPlayerModeTarget
|
||||||
{
|
{
|
||||||
private readonly LocalPlayerModeState _mode;
|
private readonly LocalPlayerModeState _mode;
|
||||||
private readonly LocalPlayerControllerSlot _controllerSlot;
|
private readonly RuntimeLocalPlayerMovementState _controllerSlot;
|
||||||
private readonly LocalPlayerPhysicsHostSlot _hostSlot;
|
private readonly LocalPlayerPhysicsHostSlot _hostSlot;
|
||||||
private readonly ChaseCameraInputState _chase;
|
private readonly ChaseCameraInputState _chase;
|
||||||
private readonly CameraController _camera;
|
private readonly CameraController _camera;
|
||||||
|
|
@ -49,7 +49,7 @@ internal sealed class PlayerModeController :
|
||||||
|
|
||||||
public PlayerModeController(
|
public PlayerModeController(
|
||||||
LocalPlayerModeState mode,
|
LocalPlayerModeState mode,
|
||||||
LocalPlayerControllerSlot controllerSlot,
|
RuntimeLocalPlayerMovementState controllerSlot,
|
||||||
LocalPlayerPhysicsHostSlot hostSlot,
|
LocalPlayerPhysicsHostSlot hostSlot,
|
||||||
ChaseCameraInputState chase,
|
ChaseCameraInputState chase,
|
||||||
CameraController camera,
|
CameraController camera,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ namespace AcDream.App.Net;
|
||||||
|
|
||||||
internal sealed record LiveSessionPlayerRuntime(
|
internal sealed record LiveSessionPlayerRuntime(
|
||||||
LocalPlayerIdentityState Identity,
|
LocalPlayerIdentityState Identity,
|
||||||
LocalPlayerControllerSlot Controller,
|
RuntimeLocalPlayerMovementState Controller,
|
||||||
LiveWorldOriginState WorldOrigin);
|
LiveWorldOriginState WorldOrigin);
|
||||||
|
|
||||||
internal sealed record LiveSessionDomainRuntime(
|
internal sealed record LiveSessionDomainRuntime(
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
private readonly ProjectileController _projectileController;
|
private readonly ProjectileController _projectileController;
|
||||||
private readonly RemoteTeleportController _remoteTeleportController;
|
private readonly RemoteTeleportController _remoteTeleportController;
|
||||||
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState> _animatedEntities;
|
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState> _animatedEntities;
|
||||||
private readonly LiveEntityRemoteMotionRuntimeView<RemoteMotion> _remoteDeadReckon;
|
|
||||||
private readonly RemoteMovementObservationTracker _remoteMovementObservations;
|
private readonly RemoteMovementObservationTracker _remoteMovementObservations;
|
||||||
private readonly RemotePhysicsUpdater _remotePhysicsUpdater;
|
private readonly RemotePhysicsUpdater _remotePhysicsUpdater;
|
||||||
private readonly RemoteInboundMotionDispatcher _remoteInboundMotion;
|
private readonly RemoteInboundMotionDispatcher _remoteInboundMotion;
|
||||||
|
|
@ -53,7 +52,7 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
private readonly LiveWorldOriginState _origin;
|
private readonly LiveWorldOriginState _origin;
|
||||||
private readonly AcDream.App.Streaming.ILocalPlayerTeleportNetworkSink
|
private readonly AcDream.App.Streaming.ILocalPlayerTeleportNetworkSink
|
||||||
_localPlayerTeleport;
|
_localPlayerTeleport;
|
||||||
private readonly ILocalPlayerControllerSource _playerControllerSource;
|
private readonly IRuntimeLocalPlayerControllerSource _playerControllerSource;
|
||||||
private readonly LocalPlayerOutboundController _localPlayerOutbound;
|
private readonly LocalPlayerOutboundController _localPlayerOutbound;
|
||||||
private readonly ILocalPlayerPhysicsHostSource _playerHostSource;
|
private readonly ILocalPlayerPhysicsHostSource _playerHostSource;
|
||||||
private readonly ILocalPlayerIdentitySource _playerIdentity;
|
private readonly ILocalPlayerIdentitySource _playerIdentity;
|
||||||
|
|
@ -83,7 +82,6 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
ProjectileController projectileController,
|
ProjectileController projectileController,
|
||||||
RemoteTeleportController remoteTeleportController,
|
RemoteTeleportController remoteTeleportController,
|
||||||
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animatedEntities,
|
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animatedEntities,
|
||||||
LiveEntityRemoteMotionRuntimeView<RemoteMotion> remoteDeadReckon,
|
|
||||||
RemoteMovementObservationTracker remoteMovementObservations,
|
RemoteMovementObservationTracker remoteMovementObservations,
|
||||||
RemotePhysicsUpdater remotePhysicsUpdater,
|
RemotePhysicsUpdater remotePhysicsUpdater,
|
||||||
RemoteInboundMotionDispatcher remoteInboundMotion,
|
RemoteInboundMotionDispatcher remoteInboundMotion,
|
||||||
|
|
@ -94,7 +92,7 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
RuntimeCombatTargetState? combatTargetController,
|
RuntimeCombatTargetState? combatTargetController,
|
||||||
LiveWorldOriginState origin,
|
LiveWorldOriginState origin,
|
||||||
AcDream.App.Streaming.ILocalPlayerTeleportNetworkSink localPlayerTeleport,
|
AcDream.App.Streaming.ILocalPlayerTeleportNetworkSink localPlayerTeleport,
|
||||||
ILocalPlayerControllerSource playerControllerSource,
|
IRuntimeLocalPlayerControllerSource playerControllerSource,
|
||||||
LocalPlayerOutboundController localPlayerOutbound,
|
LocalPlayerOutboundController localPlayerOutbound,
|
||||||
ILocalPlayerPhysicsHostSource playerHostSource,
|
ILocalPlayerPhysicsHostSource playerHostSource,
|
||||||
ILocalPlayerIdentitySource playerIdentity,
|
ILocalPlayerIdentitySource playerIdentity,
|
||||||
|
|
@ -113,7 +111,6 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
_projectileController = projectileController ?? throw new ArgumentNullException(nameof(projectileController));
|
_projectileController = projectileController ?? throw new ArgumentNullException(nameof(projectileController));
|
||||||
_remoteTeleportController = remoteTeleportController ?? throw new ArgumentNullException(nameof(remoteTeleportController));
|
_remoteTeleportController = remoteTeleportController ?? throw new ArgumentNullException(nameof(remoteTeleportController));
|
||||||
_animatedEntities = animatedEntities ?? throw new ArgumentNullException(nameof(animatedEntities));
|
_animatedEntities = animatedEntities ?? throw new ArgumentNullException(nameof(animatedEntities));
|
||||||
_remoteDeadReckon = remoteDeadReckon ?? throw new ArgumentNullException(nameof(remoteDeadReckon));
|
|
||||||
_remoteMovementObservations = remoteMovementObservations ?? throw new ArgumentNullException(nameof(remoteMovementObservations));
|
_remoteMovementObservations = remoteMovementObservations ?? throw new ArgumentNullException(nameof(remoteMovementObservations));
|
||||||
_remotePhysicsUpdater = remotePhysicsUpdater ?? throw new ArgumentNullException(nameof(remotePhysicsUpdater));
|
_remotePhysicsUpdater = remotePhysicsUpdater ?? throw new ArgumentNullException(nameof(remotePhysicsUpdater));
|
||||||
_remoteInboundMotion = remoteInboundMotion ?? throw new ArgumentNullException(nameof(remoteInboundMotion));
|
_remoteInboundMotion = remoteInboundMotion ?? throw new ArgumentNullException(nameof(remoteInboundMotion));
|
||||||
|
|
@ -151,7 +148,10 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
uint localEntityId,
|
uint localEntityId,
|
||||||
Func<bool> isCurrent)
|
Func<bool> isCurrent)
|
||||||
{
|
{
|
||||||
_remoteDeadReckon.TryGetValue(serverGuid, out RemoteMotion? remote);
|
_liveEntities.TryGetRemoteMotionRuntime(
|
||||||
|
serverGuid,
|
||||||
|
out IRuntimeRemoteMotion? remoteRuntime);
|
||||||
|
RemoteMotion? remote = remoteRuntime as RemoteMotion;
|
||||||
EntityPhysicsHost? host =
|
EntityPhysicsHost? host =
|
||||||
_liveEntities.TryGetPhysicsHost(serverGuid, out var registered)
|
_liveEntities.TryGetPhysicsHost(serverGuid, out var registered)
|
||||||
? registered as EntityPhysicsHost
|
? registered as EntityPhysicsHost
|
||||||
|
|
@ -570,7 +570,10 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
_remoteMovementObservations[motionKey] =
|
_remoteMovementObservations[motionKey] =
|
||||||
(prev.Pos, refreshedTime);
|
(prev.Pos, refreshedTime);
|
||||||
}
|
}
|
||||||
if (_remoteDeadReckon.TryGetValue(update.Guid, out var dr))
|
if (_liveEntities.TryGetRemoteMotionRuntime(
|
||||||
|
update.Guid,
|
||||||
|
out IRuntimeRemoteMotion? remoteRuntime)
|
||||||
|
&& remoteRuntime is RemoteMotion dr)
|
||||||
dr.LastServerPosTime = (refreshedTime - System.DateTime.UnixEpoch).TotalSeconds;
|
dr.LastServerPosTime = (refreshedTime - System.DateTime.UnixEpoch).TotalSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -635,12 +638,15 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
if (!IsCurrentOwner())
|
if (!IsCurrentOwner())
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
if (!_remoteDeadReckon.TryGetValue(update.Guid, out var remote))
|
if (!_liveEntities.TryGetRemoteMotionRuntime(
|
||||||
|
update.Guid,
|
||||||
|
out IRuntimeRemoteMotion? remoteRuntime)
|
||||||
|
|| remoteRuntime is not RemoteMotion remote)
|
||||||
{
|
{
|
||||||
remote = CreateRemoteMotion(update.Guid);
|
remote = _liveEntities.GetOrCreateRemoteMotionRuntime(
|
||||||
|
update.Guid);
|
||||||
remote.Body.Orientation = entity.Rotation;
|
remote.Body.Orientation = entity.Rotation;
|
||||||
remote.Body.Position = entity.Position;
|
remote.Body.Position = entity.Position;
|
||||||
_remoteDeadReckon[update.Guid] = remote;
|
|
||||||
}
|
}
|
||||||
if (!IsCurrentOwner(remote))
|
if (!IsCurrentOwner(remote))
|
||||||
return default;
|
return default;
|
||||||
|
|
@ -719,43 +725,6 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
&& runtime.IsCurrentSpatialRemoteMotion(record, remote);
|
&& runtime.IsCurrentSpatialRemoteMotion(record, remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates the optional MovementManager companion for one live object.
|
|
||||||
/// A retained projectile or static-animation owner contributes the
|
|
||||||
/// already-owned CPhysicsObj body; an ordinary remote gets local storage.
|
|
||||||
/// </summary>
|
|
||||||
private RemoteMotion CreateRemoteMotion(uint serverGuid)
|
|
||||||
{
|
|
||||||
LiveEntityRecord? record = null;
|
|
||||||
if (_liveEntities is { } liveEntities
|
|
||||||
&& liveEntities.TryGetRecord(serverGuid, out var currentRecord))
|
|
||||||
{
|
|
||||||
record = currentRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteMotion remote;
|
|
||||||
if (record?.PhysicsBody is { } canonicalBody)
|
|
||||||
{
|
|
||||||
// Retail has one CPhysicsObj. A projectile or Physics-Static
|
|
||||||
// animation workset can create it before the MovementManager;
|
|
||||||
// adopt its current frame/vectors without replaying CreateObject.
|
|
||||||
remote = new RemoteMotion(canonicalBody);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
remote = new RemoteMotion();
|
|
||||||
if (record is not null)
|
|
||||||
AcDream.App.Physics.RemotePhysicsBodyInitializer.Initialize(
|
|
||||||
remote.Body,
|
|
||||||
record);
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteMotion incarnation = remote;
|
|
||||||
remote.Movement.ActivatePhysicsObject = () =>
|
|
||||||
_liveEntities?.TryActivateOrdinaryObject(serverGuid, incarnation);
|
|
||||||
return remote;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// K-fix9 (2026-04-26): handle 0xF74E VectorUpdate from remote jumps.
|
/// K-fix9 (2026-04-26): handle 0xF74E VectorUpdate from remote jumps.
|
||||||
/// The payload seeds the world-space launch velocity and angular velocity.
|
/// The payload seeds the world-space launch velocity and angular velocity.
|
||||||
|
|
@ -822,7 +791,13 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
if (!_liveEntities.ContainsWorldEntity(update.Guid)) return;
|
if (!_liveEntities.ContainsWorldEntity(update.Guid)) return;
|
||||||
|
|
||||||
if (update.Guid == _playerServerGuid) return; // local jump uses our own physics
|
if (update.Guid == _playerServerGuid) return; // local jump uses our own physics
|
||||||
if (!_remoteDeadReckon.TryGetValue(update.Guid, out var rm)) return;
|
if (!_liveEntities.TryGetRemoteMotionRuntime(
|
||||||
|
update.Guid,
|
||||||
|
out IRuntimeRemoteMotion? remoteRuntime)
|
||||||
|
|| remoteRuntime is not RemoteMotion rm)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
LiveEntityRecord remoteRecord = acceptedVectorRecord;
|
LiveEntityRecord remoteRecord = acceptedVectorRecord;
|
||||||
|
|
||||||
// World-space velocity. Apply directly to the body — the per-tick
|
// World-space velocity. Apply directly to the body — the per-tick
|
||||||
|
|
@ -1249,10 +1224,14 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
// small. When HasVelocity is set, we also seed PhysicsBody
|
// small. When HasVelocity is set, we also seed PhysicsBody
|
||||||
// velocity (matches retail's set_velocity call in the same
|
// velocity (matches retail's set_velocity call in the same
|
||||||
// dispatcher).
|
// dispatcher).
|
||||||
if (!_remoteDeadReckon.TryGetValue(update.Guid, out var rmState))
|
if (!_liveEntities.TryGetRemoteMotionRuntime(
|
||||||
|
update.Guid,
|
||||||
|
out IRuntimeRemoteMotion? remoteRuntime)
|
||||||
|
|| remoteRuntime is not RemoteMotion rmState)
|
||||||
{
|
{
|
||||||
rmState = CreateRemoteMotion(update.Guid);
|
rmState =
|
||||||
_remoteDeadReckon[update.Guid] = rmState;
|
_liveEntities.GetOrCreateRemoteMotionRuntime(
|
||||||
|
update.Guid);
|
||||||
// Hard-snap orientation on first spawn so the per-tick
|
// Hard-snap orientation on first spawn so the per-tick
|
||||||
// slerp doesn't visibly rotate from Identity to truth.
|
// slerp doesn't visibly rotate from Identity to truth.
|
||||||
rmState.Body.Orientation = rot;
|
rmState.Body.Orientation = rot;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ internal static class LiveEntityShadowPublisher
|
||||||
LiveEntityRuntime runtime,
|
LiveEntityRuntime runtime,
|
||||||
LiveEntityRecord record,
|
LiveEntityRecord record,
|
||||||
WorldEntity entity,
|
WorldEntity entity,
|
||||||
ILiveEntityRemoteMotionRuntime remote,
|
IRuntimeRemoteMotion remote,
|
||||||
ulong positionAuthorityVersion,
|
ulong positionAuthorityVersion,
|
||||||
Action publish)
|
Action publish)
|
||||||
{
|
{
|
||||||
|
|
@ -47,7 +47,7 @@ internal static class LiveEntityShadowPublisher
|
||||||
LiveEntityRuntime runtime,
|
LiveEntityRuntime runtime,
|
||||||
LiveEntityRecord record,
|
LiveEntityRecord record,
|
||||||
WorldEntity entity,
|
WorldEntity entity,
|
||||||
ILiveEntityRemoteMotionRuntime remote,
|
IRuntimeRemoteMotion remote,
|
||||||
ulong positionAuthorityVersion) =>
|
ulong positionAuthorityVersion) =>
|
||||||
(record.FinalPhysicsState & PhysicsStateFlags.Hidden) == 0
|
(record.FinalPhysicsState & PhysicsStateFlags.Hidden) == 0
|
||||||
&& ReferenceEquals(record.WorldEntity, entity)
|
&& ReferenceEquals(record.WorldEntity, entity)
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,6 @@ internal sealed class ProjectileController
|
||||||
{
|
{
|
||||||
var created = new PhysicsBody
|
var created = new PhysicsBody
|
||||||
{
|
{
|
||||||
State = record.FinalPhysicsState,
|
|
||||||
Friction = NormalizeFriction(
|
Friction = NormalizeFriction(
|
||||||
physics.Friction ?? record.Snapshot.Friction),
|
physics.Friction ?? record.Snapshot.Friction),
|
||||||
Elasticity = NormalizeElasticity(
|
Elasticity = NormalizeElasticity(
|
||||||
|
|
@ -254,8 +253,6 @@ internal sealed class ProjectileController
|
||||||
Orientation = entity.Rotation,
|
Orientation = entity.Rotation,
|
||||||
LastUpdateTime = currentTime,
|
LastUpdateTime = currentTime,
|
||||||
};
|
};
|
||||||
SetVelocity(created, velocity, currentTime);
|
|
||||||
created.Omega = omega;
|
|
||||||
created.SnapToCell(
|
created.SnapToCell(
|
||||||
wirePosition.LandblockId,
|
wirePosition.LandblockId,
|
||||||
entity.Position,
|
entity.Position,
|
||||||
|
|
@ -919,22 +916,6 @@ internal sealed class ProjectileController
|
||||||
private static void Deactivate(PhysicsBody body) =>
|
private static void Deactivate(PhysicsBody body) =>
|
||||||
body.TransientState &= ~TransientStateFlags.Active;
|
body.TransientState &= ~TransientStateFlags.Active;
|
||||||
|
|
||||||
private static void SetVelocity(
|
|
||||||
PhysicsBody body,
|
|
||||||
Vector3 velocity,
|
|
||||||
double currentTime)
|
|
||||||
{
|
|
||||||
bool wasActive = (body.TransientState & TransientStateFlags.Active) != 0;
|
|
||||||
body.set_velocity(velocity);
|
|
||||||
if ((body.State & PhysicsStateFlags.Static) != 0)
|
|
||||||
{
|
|
||||||
Deactivate(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!wasActive && (body.State & PhysicsStateFlags.Static) == 0)
|
|
||||||
body.LastUpdateTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float NormalizeFriction(float? value) =>
|
private static float NormalizeFriction(float? value) =>
|
||||||
value is >= 0f and <= 1f && float.IsFinite(value.Value)
|
value is >= 0f and <= 1f && float.IsFinite(value.Value)
|
||||||
? value.Value
|
? value.Value
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
using System.Numerics;
|
|
||||||
using AcDream.App.World;
|
|
||||||
using AcDream.Core.Physics;
|
|
||||||
|
|
||||||
namespace AcDream.App.Physics;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies CreateObject physics vectors when the ordinary remote-motion body
|
|
||||||
/// is first constructed. This keeps initialization at object/body creation;
|
|
||||||
/// a later SetState classification change never replays retained vectors.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Retail <c>CPhysicsObj::set_description</c> <c>0x00514F40</c> installs the
|
|
||||||
/// unpacked velocity/omega during object initialization. Retail
|
|
||||||
/// <c>SmartBox::DoSetState</c> <c>0x004520D0</c> only calls
|
|
||||||
/// <c>CPhysicsObj::set_state</c> and therefore preserves the current vectors.
|
|
||||||
/// </remarks>
|
|
||||||
internal static class RemotePhysicsBodyInitializer
|
|
||||||
{
|
|
||||||
internal static void Initialize(PhysicsBody body, LiveEntityRecord record)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(body);
|
|
||||||
ArgumentNullException.ThrowIfNull(record);
|
|
||||||
|
|
||||||
body.State = record.FinalPhysicsState;
|
|
||||||
if (record.Snapshot.Physics is not { } physics)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (physics.Velocity is { } velocity && IsFinite(velocity))
|
|
||||||
body.set_velocity(velocity);
|
|
||||||
if (physics.AngularVelocity is { } omega && IsFinite(omega))
|
|
||||||
body.Omega = omega;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsFinite(Vector3 value) =>
|
|
||||||
float.IsFinite(value.X)
|
|
||||||
&& float.IsFinite(value.Y)
|
|
||||||
&& float.IsFinite(value.Z);
|
|
||||||
}
|
|
||||||
|
|
@ -73,7 +73,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
||||||
LiveEntityRecord Record,
|
LiveEntityRecord Record,
|
||||||
ulong PositionAuthorityVersion,
|
ulong PositionAuthorityVersion,
|
||||||
ulong VelocityAuthorityVersion,
|
ulong VelocityAuthorityVersion,
|
||||||
ILiveEntityRemotePlacementRuntime Remote,
|
IRuntimeRemotePlacement Remote,
|
||||||
PhysicsBody Body,
|
PhysicsBody Body,
|
||||||
WorldEntity Entity,
|
WorldEntity Entity,
|
||||||
Vector3 RequestedWorldPosition,
|
Vector3 RequestedWorldPosition,
|
||||||
|
|
@ -110,7 +110,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
||||||
/// revalidation once admitted.
|
/// revalidation once admitted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Result TryApply(
|
internal Result TryApply(
|
||||||
ILiveEntityRemotePlacementRuntime remote,
|
IRuntimeRemotePlacement remote,
|
||||||
WorldEntity entity,
|
WorldEntity entity,
|
||||||
Vector3 requestedWorldPosition,
|
Vector3 requestedWorldPosition,
|
||||||
uint requestedCellId,
|
uint requestedCellId,
|
||||||
|
|
@ -147,7 +147,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
||||||
LiveEntityRecord expectedRecord,
|
LiveEntityRecord expectedRecord,
|
||||||
ulong expectedPositionAuthorityVersion,
|
ulong expectedPositionAuthorityVersion,
|
||||||
ulong expectedVelocityAuthorityVersion,
|
ulong expectedVelocityAuthorityVersion,
|
||||||
ILiveEntityRemotePlacementRuntime remote,
|
IRuntimeRemotePlacement remote,
|
||||||
WorldEntity entity,
|
WorldEntity entity,
|
||||||
Vector3 requestedWorldPosition,
|
Vector3 requestedWorldPosition,
|
||||||
uint requestedCellId,
|
uint requestedCellId,
|
||||||
|
|
@ -422,7 +422,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
||||||
_pending.Remove(key);
|
_pending.Remove(key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (record.RemoteMotionRuntime is not ILiveEntityRemotePlacementRuntime currentRemote
|
if (record.RemoteMotionRuntime is not IRuntimeRemotePlacement currentRemote
|
||||||
|| record.PhysicsBody is null
|
|| record.PhysicsBody is null
|
||||||
|| !ReferenceEquals(record.PhysicsBody, pending.Body)
|
|| !ReferenceEquals(record.PhysicsBody, pending.Body)
|
||||||
|| !ReferenceEquals(currentRemote.Body, record.PhysicsBody))
|
|| !ReferenceEquals(currentRemote.Body, record.PhysicsBody))
|
||||||
|
|
@ -467,7 +467,7 @@ internal sealed class RemoteTeleportController : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RollbackPlacement CaptureRollback(
|
private static RollbackPlacement CaptureRollback(
|
||||||
ILiveEntityRemotePlacementRuntime remote,
|
IRuntimeRemotePlacement remote,
|
||||||
PhysicsBody body)
|
PhysicsBody body)
|
||||||
{
|
{
|
||||||
return new RollbackPlacement(
|
return new RollbackPlacement(
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace AcDream.App.Physics;
|
||||||
internal static class RemoteTeleportPlacement
|
internal static class RemoteTeleportPlacement
|
||||||
{
|
{
|
||||||
internal static bool Apply(
|
internal static bool Apply(
|
||||||
ILiveEntityRemotePlacementRuntime remote,
|
IRuntimeRemotePlacement remote,
|
||||||
PhysicsBody body,
|
PhysicsBody body,
|
||||||
ResolveResult placement,
|
ResolveResult placement,
|
||||||
Vector3 cellLocalPosition,
|
Vector3 cellLocalPosition,
|
||||||
|
|
|
||||||
|
|
@ -1005,7 +1005,6 @@ internal sealed class DatLiveEntityProjectionMaterializer
|
||||||
incarnation =>
|
incarnation =>
|
||||||
{
|
{
|
||||||
var created = new PhysicsBody { Orientation = entity.Rotation };
|
var created = new PhysicsBody { Orientation = entity.Rotation };
|
||||||
RemotePhysicsBodyInitializer.Initialize(created, incarnation);
|
|
||||||
created.SnapToCell(
|
created.SnapToCell(
|
||||||
staticPosition.LandblockId,
|
staticPosition.LandblockId,
|
||||||
entity.Position,
|
entity.Position,
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ internal interface IDevToolsRuntimeFacts
|
||||||
internal sealed class DevToolsRuntimeFacts : IDevToolsRuntimeFacts
|
internal sealed class DevToolsRuntimeFacts : IDevToolsRuntimeFacts
|
||||||
{
|
{
|
||||||
private readonly ILocalPlayerModeSource _mode;
|
private readonly ILocalPlayerModeSource _mode;
|
||||||
private readonly ILocalPlayerControllerSource _player;
|
private readonly IRuntimeLocalPlayerControllerSource _player;
|
||||||
private readonly CameraController _camera;
|
private readonly CameraController _camera;
|
||||||
private readonly ICanonicalWorldEntityCountSource _world;
|
private readonly ICanonicalWorldEntityCountSource _world;
|
||||||
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState> _animations;
|
private readonly LiveEntityAnimationRuntimeView<LiveEntityAnimationState> _animations;
|
||||||
|
|
@ -273,7 +273,7 @@ internal sealed class DevToolsRuntimeFacts : IDevToolsRuntimeFacts
|
||||||
|
|
||||||
public DevToolsRuntimeFacts(
|
public DevToolsRuntimeFacts(
|
||||||
ILocalPlayerModeSource mode,
|
ILocalPlayerModeSource mode,
|
||||||
ILocalPlayerControllerSource player,
|
IRuntimeLocalPlayerControllerSource player,
|
||||||
CameraController camera,
|
CameraController camera,
|
||||||
ICanonicalWorldEntityCountSource world,
|
ICanonicalWorldEntityCountSource world,
|
||||||
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animations,
|
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> animations,
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ internal sealed class RuntimeWorldFrameRootSource : IWorldFrameRootSource
|
||||||
private readonly CellVisibility _cells;
|
private readonly CellVisibility _cells;
|
||||||
private readonly ILocalPlayerModeSource _mode;
|
private readonly ILocalPlayerModeSource _mode;
|
||||||
private readonly IChaseCameraSource _chase;
|
private readonly IChaseCameraSource _chase;
|
||||||
private readonly ILocalPlayerControllerSource _player;
|
private readonly IRuntimeLocalPlayerControllerSource _player;
|
||||||
private readonly LiveWorldOriginState _origin;
|
private readonly LiveWorldOriginState _origin;
|
||||||
|
|
||||||
public RuntimeWorldFrameRootSource(
|
public RuntimeWorldFrameRootSource(
|
||||||
|
|
@ -226,7 +226,7 @@ internal sealed class RuntimeWorldFrameRootSource : IWorldFrameRootSource
|
||||||
CellVisibility cells,
|
CellVisibility cells,
|
||||||
ILocalPlayerModeSource mode,
|
ILocalPlayerModeSource mode,
|
||||||
IChaseCameraSource chase,
|
IChaseCameraSource chase,
|
||||||
ILocalPlayerControllerSource player,
|
IRuntimeLocalPlayerControllerSource player,
|
||||||
LiveWorldOriginState origin)
|
LiveWorldOriginState origin)
|
||||||
{
|
{
|
||||||
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
||||||
private readonly DebugLineRenderer? _lines;
|
private readonly DebugLineRenderer? _lines;
|
||||||
private readonly PhysicsEngine _physics;
|
private readonly PhysicsEngine _physics;
|
||||||
private readonly ILocalPlayerModeSource _mode;
|
private readonly ILocalPlayerModeSource _mode;
|
||||||
private readonly ILocalPlayerControllerSource _player;
|
private readonly IRuntimeLocalPlayerControllerSource _player;
|
||||||
private readonly DebugVmRenderFactsPublisher _debugVm;
|
private readonly DebugVmRenderFactsPublisher _debugVm;
|
||||||
private readonly bool _debugVmConsumerActive;
|
private readonly bool _debugVmConsumerActive;
|
||||||
private int _debugDrawLogCount;
|
private int _debugDrawLogCount;
|
||||||
|
|
@ -72,7 +72,7 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
||||||
DebugLineRenderer? lines,
|
DebugLineRenderer? lines,
|
||||||
PhysicsEngine physics,
|
PhysicsEngine physics,
|
||||||
ILocalPlayerModeSource mode,
|
ILocalPlayerModeSource mode,
|
||||||
ILocalPlayerControllerSource player,
|
IRuntimeLocalPlayerControllerSource player,
|
||||||
DebugVmRenderFactsPublisher debugVm,
|
DebugVmRenderFactsPublisher debugVm,
|
||||||
bool debugVmConsumerActive)
|
bool debugVmConsumerActive)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,12 @@ internal interface IWorldScenePViewDiagnosticSource
|
||||||
internal sealed class RuntimeWorldScenePViewDiagnosticSource :
|
internal sealed class RuntimeWorldScenePViewDiagnosticSource :
|
||||||
IWorldScenePViewDiagnosticSource
|
IWorldScenePViewDiagnosticSource
|
||||||
{
|
{
|
||||||
private readonly ILocalPlayerControllerSource _player;
|
private readonly IRuntimeLocalPlayerControllerSource _player;
|
||||||
private readonly PhysicsEngine _physics;
|
private readonly PhysicsEngine _physics;
|
||||||
private readonly CellVisibility _visibility;
|
private readonly CellVisibility _visibility;
|
||||||
|
|
||||||
public RuntimeWorldScenePViewDiagnosticSource(
|
public RuntimeWorldScenePViewDiagnosticSource(
|
||||||
ILocalPlayerControllerSource player,
|
IRuntimeLocalPlayerControllerSource player,
|
||||||
PhysicsEngine physics,
|
PhysicsEngine physics,
|
||||||
CellVisibility visibility)
|
CellVisibility visibility)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ internal sealed class CurrentGameRuntimeAdapter
|
||||||
LiveSessionHost sessionHost,
|
LiveSessionHost sessionHost,
|
||||||
ICommandBus commands,
|
ICommandBus commands,
|
||||||
LocalPlayerIdentityState playerIdentity,
|
LocalPlayerIdentityState playerIdentity,
|
||||||
LiveEntityRuntime entities,
|
|
||||||
RuntimeEntityObjectLifetime entityObjects,
|
RuntimeEntityObjectLifetime entityObjects,
|
||||||
RuntimeInventoryState inventory,
|
RuntimeInventoryState inventory,
|
||||||
RuntimeCharacterState character,
|
RuntimeCharacterState character,
|
||||||
|
|
@ -46,7 +45,6 @@ internal sealed class CurrentGameRuntimeAdapter
|
||||||
_view = new CurrentGameRuntimeViewAdapter(
|
_view = new CurrentGameRuntimeViewAdapter(
|
||||||
session,
|
session,
|
||||||
playerIdentity,
|
playerIdentity,
|
||||||
entities,
|
|
||||||
entityObjects,
|
entityObjects,
|
||||||
inventory,
|
inventory,
|
||||||
character,
|
character,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using AcDream.App.Input;
|
using AcDream.App.Input;
|
||||||
using AcDream.App.Net;
|
using AcDream.App.Net;
|
||||||
using AcDream.App.Streaming;
|
using AcDream.App.Streaming;
|
||||||
using AcDream.App.World;
|
|
||||||
using AcDream.Core.Chat;
|
using AcDream.Core.Chat;
|
||||||
using AcDream.Core.Items;
|
using AcDream.Core.Items;
|
||||||
using AcDream.Runtime;
|
using AcDream.Runtime;
|
||||||
|
|
@ -19,8 +18,6 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
|
||||||
{
|
{
|
||||||
private readonly LiveSessionController _session;
|
private readonly LiveSessionController _session;
|
||||||
private readonly LocalPlayerIdentityState _playerIdentity;
|
private readonly LocalPlayerIdentityState _playerIdentity;
|
||||||
private readonly LiveEntityRuntime _entities;
|
|
||||||
private readonly ClientObjectTable _objects;
|
|
||||||
private readonly IGameRuntimeClock _clock;
|
private readonly IGameRuntimeClock _clock;
|
||||||
private readonly IRuntimeEntityView _entityView;
|
private readonly IRuntimeEntityView _entityView;
|
||||||
private readonly IRuntimeInventoryView _inventoryView;
|
private readonly IRuntimeInventoryView _inventoryView;
|
||||||
|
|
@ -36,7 +33,6 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
|
||||||
public CurrentGameRuntimeViewAdapter(
|
public CurrentGameRuntimeViewAdapter(
|
||||||
LiveSessionController session,
|
LiveSessionController session,
|
||||||
LocalPlayerIdentityState playerIdentity,
|
LocalPlayerIdentityState playerIdentity,
|
||||||
LiveEntityRuntime entities,
|
|
||||||
RuntimeEntityObjectLifetime entityObjects,
|
RuntimeEntityObjectLifetime entityObjects,
|
||||||
RuntimeInventoryState inventory,
|
RuntimeInventoryState inventory,
|
||||||
RuntimeCharacterState character,
|
RuntimeCharacterState character,
|
||||||
|
|
@ -49,9 +45,7 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
|
||||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||||
_playerIdentity = playerIdentity
|
_playerIdentity = playerIdentity
|
||||||
?? throw new ArgumentNullException(nameof(playerIdentity));
|
?? throw new ArgumentNullException(nameof(playerIdentity));
|
||||||
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
|
|
||||||
ArgumentNullException.ThrowIfNull(entityObjects);
|
ArgumentNullException.ThrowIfNull(entityObjects);
|
||||||
_objects = entityObjects.Objects;
|
|
||||||
ArgumentNullException.ThrowIfNull(communication);
|
ArgumentNullException.ThrowIfNull(communication);
|
||||||
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
||||||
|
|
||||||
|
|
@ -115,10 +109,10 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
|
||||||
Generation,
|
Generation,
|
||||||
Lifecycle.State,
|
Lifecycle.State,
|
||||||
_clock.FrameNumber,
|
_clock.FrameNumber,
|
||||||
_entities.Count,
|
_entityView.Count,
|
||||||
_entities.MaterializedCount,
|
_entityView.MaterializedCount,
|
||||||
_objects.ObjectCount,
|
_inventoryView.ObjectCount,
|
||||||
_objects.ContainerCount,
|
_inventoryView.ContainerCount,
|
||||||
_inventoryStateView.Snapshot,
|
_inventoryStateView.Snapshot,
|
||||||
_characterView.Snapshot,
|
_characterView.Snapshot,
|
||||||
_socialView.Snapshot,
|
_socialView.Snapshot,
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlaceme
|
||||||
private readonly PhysicsEngine _physics;
|
private readonly PhysicsEngine _physics;
|
||||||
private readonly LiveEntityRuntime _liveEntities;
|
private readonly LiveEntityRuntime _liveEntities;
|
||||||
private readonly ILocalPlayerIdentitySource _identity;
|
private readonly ILocalPlayerIdentitySource _identity;
|
||||||
private readonly ILocalPlayerControllerSource _controller;
|
private readonly IRuntimeLocalPlayerControllerSource _controller;
|
||||||
private readonly ILocalPlayerPhysicsHostSource _host;
|
private readonly ILocalPlayerPhysicsHostSource _host;
|
||||||
private readonly ChaseCameraInputState _cameras;
|
private readonly ChaseCameraInputState _cameras;
|
||||||
private readonly LiveWorldOriginState _origin;
|
private readonly LiveWorldOriginState _origin;
|
||||||
|
|
@ -189,7 +189,7 @@ internal sealed class LocalPlayerTeleportPlacement : ILocalPlayerTeleportPlaceme
|
||||||
PhysicsEngine physics,
|
PhysicsEngine physics,
|
||||||
LiveEntityRuntime liveEntities,
|
LiveEntityRuntime liveEntities,
|
||||||
ILocalPlayerIdentitySource identity,
|
ILocalPlayerIdentitySource identity,
|
||||||
ILocalPlayerControllerSource controller,
|
IRuntimeLocalPlayerControllerSource controller,
|
||||||
ILocalPlayerPhysicsHostSource host,
|
ILocalPlayerPhysicsHostSource host,
|
||||||
ChaseCameraInputState cameras,
|
ChaseCameraInputState cameras,
|
||||||
LiveWorldOriginState origin,
|
LiveWorldOriginState origin,
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ internal sealed class StreamingFrameController : IStreamingFramePhase
|
||||||
|
|
||||||
private readonly bool _liveModeEnabled;
|
private readonly bool _liveModeEnabled;
|
||||||
private readonly ILocalPlayerModeSource _playerMode;
|
private readonly ILocalPlayerModeSource _playerMode;
|
||||||
private readonly ILocalPlayerControllerSource _playerController;
|
private readonly IRuntimeLocalPlayerControllerSource _playerController;
|
||||||
private readonly ILiveInWorldSource _session;
|
private readonly ILiveInWorldSource _session;
|
||||||
private readonly LiveWorldOriginState _origin;
|
private readonly LiveWorldOriginState _origin;
|
||||||
private readonly ILocalPlayerLandblockSource _playerLandblock;
|
private readonly ILocalPlayerLandblockSource _playerLandblock;
|
||||||
|
|
@ -119,7 +119,7 @@ internal sealed class StreamingFrameController : IStreamingFramePhase
|
||||||
public StreamingFrameController(
|
public StreamingFrameController(
|
||||||
bool liveModeEnabled,
|
bool liveModeEnabled,
|
||||||
ILocalPlayerModeSource playerMode,
|
ILocalPlayerModeSource playerMode,
|
||||||
ILocalPlayerControllerSource playerController,
|
IRuntimeLocalPlayerControllerSource playerController,
|
||||||
ILiveInWorldSource session,
|
ILiveInWorldSource session,
|
||||||
LiveWorldOriginState origin,
|
LiveWorldOriginState origin,
|
||||||
ILocalPlayerLandblockSource playerLandblock,
|
ILocalPlayerLandblockSource playerLandblock,
|
||||||
|
|
|
||||||
|
|
@ -223,33 +223,27 @@ public sealed class LiveEntityRecord
|
||||||
public PhysicsBody? PhysicsBody
|
public PhysicsBody? PhysicsBody
|
||||||
{
|
{
|
||||||
get => Canonical.PhysicsBody;
|
get => Canonical.PhysicsBody;
|
||||||
internal set => _directory.SetPhysicsBody(Canonical, value);
|
|
||||||
}
|
}
|
||||||
internal bool PhysicsBodyAcquisitionInProgress
|
internal bool PhysicsBodyAcquisitionInProgress
|
||||||
{
|
{
|
||||||
get => Canonical.PhysicsBodyAcquisitionInProgress;
|
get => Canonical.PhysicsBodyAcquisitionInProgress;
|
||||||
set => _directory.SetPhysicsBodyAcquisitionInProgress(Canonical, value);
|
|
||||||
}
|
}
|
||||||
public ILiveEntityAnimationRuntime? AnimationRuntime { get; internal set; }
|
public ILiveEntityAnimationRuntime? AnimationRuntime { get; internal set; }
|
||||||
public ILiveEntityRemoteMotionRuntime? RemoteMotionRuntime
|
public IRuntimeRemoteMotion? RemoteMotionRuntime
|
||||||
{
|
{
|
||||||
get => Canonical.RemoteMotion;
|
get => Canonical.RemoteMotion;
|
||||||
internal set => _directory.SetRemoteMotion(Canonical, value);
|
|
||||||
}
|
}
|
||||||
internal bool RemoteMotionBindingInProgress
|
internal bool RemoteMotionBindingInProgress
|
||||||
{
|
{
|
||||||
get => Canonical.RemoteMotionBindingInProgress;
|
get => Canonical.RemoteMotionBindingInProgress;
|
||||||
set => _directory.SetRemoteMotionBindingInProgress(Canonical, value);
|
|
||||||
}
|
}
|
||||||
public AcDream.Core.Physics.Motion.IPhysicsObjHost? PhysicsHost
|
public AcDream.Core.Physics.Motion.IPhysicsObjHost? PhysicsHost
|
||||||
{
|
{
|
||||||
get => Canonical.PhysicsHost;
|
get => Canonical.PhysicsHost;
|
||||||
internal set => _directory.SetPhysicsHost(Canonical, value);
|
|
||||||
}
|
}
|
||||||
internal bool RequiresRemotePlacementRuntime
|
internal bool RequiresRemotePlacementRuntime
|
||||||
{
|
{
|
||||||
get => Canonical.RequiresRemotePlacementRuntime;
|
get => Canonical.RequiresRemotePlacementRuntime;
|
||||||
set => _directory.SetRequiresRemotePlacementRuntime(Canonical, value);
|
|
||||||
}
|
}
|
||||||
public IRuntimeProjectile? ProjectileRuntime => Canonical.Projectile;
|
public IRuntimeProjectile? ProjectileRuntime => Canonical.Projectile;
|
||||||
public ILiveEntityEffectProfile? EffectProfile { get; internal set; }
|
public ILiveEntityEffectProfile? EffectProfile { get; internal set; }
|
||||||
|
|
@ -1389,7 +1383,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
ProjectionIsCurrent);
|
ProjectionIsCurrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRemoteMotionRuntime(uint serverGuid, ILiveEntityRemoteMotionRuntime runtime)
|
internal void SetRemoteMotionRuntime(uint serverGuid, IRuntimeRemoteMotion runtime)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(runtime);
|
ArgumentNullException.ThrowIfNull(runtime);
|
||||||
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record))
|
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record))
|
||||||
|
|
@ -1442,7 +1436,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
|
|
||||||
public bool TryGetRemoteMotionRuntime(
|
public bool TryGetRemoteMotionRuntime(
|
||||||
uint serverGuid,
|
uint serverGuid,
|
||||||
out ILiveEntityRemoteMotionRuntime runtime)
|
out IRuntimeRemoteMotion runtime)
|
||||||
{
|
{
|
||||||
if (_projections.TryGetCurrent(
|
if (_projections.TryGetCurrent(
|
||||||
serverGuid,
|
serverGuid,
|
||||||
|
|
@ -1457,7 +1451,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ClearRemoteMotionRuntime(uint serverGuid)
|
internal bool ClearRemoteMotionRuntime(uint serverGuid)
|
||||||
{
|
{
|
||||||
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|
||||||
|| record.RemoteMotionRuntime is null)
|
|| record.RemoteMotionRuntime is null)
|
||||||
|
|
@ -1468,6 +1462,26 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal RemoteMotion GetOrCreateRemoteMotionRuntime(uint serverGuid)
|
||||||
|
{
|
||||||
|
if (!_projections.TryGetCurrent(
|
||||||
|
serverGuid,
|
||||||
|
out LiveEntityRecord? record))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Cannot acquire remote motion before live entity 0x{serverGuid:X8} exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectionIsCurrent() =>
|
||||||
|
_projections.TryGetCurrent(
|
||||||
|
serverGuid,
|
||||||
|
out LiveEntityRecord? current)
|
||||||
|
&& ReferenceEquals(current, record);
|
||||||
|
return _physics.GetOrCreateRemoteMotion(
|
||||||
|
record.Canonical,
|
||||||
|
ProjectionIsCurrent);
|
||||||
|
}
|
||||||
|
|
||||||
internal IRuntimeProjectile BindProjectileRuntime(
|
internal IRuntimeProjectile BindProjectileRuntime(
|
||||||
uint serverGuid,
|
uint serverGuid,
|
||||||
PhysicsBody body,
|
PhysicsBody body,
|
||||||
|
|
@ -1874,27 +1888,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
destination.Add(key.LocalEntityId);
|
destination.Add(key.LocalEntityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Host side of retail <c>CPhysicsObj::set_active(1)</c> for a movement
|
|
||||||
/// manager owned by this exact incarnation. Static is a required no-op.
|
|
||||||
/// </summary>
|
|
||||||
internal bool TryActivateOrdinaryObject(
|
|
||||||
uint serverGuid,
|
|
||||||
ILiveEntityRemoteMotionRuntime runtime)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(runtime);
|
|
||||||
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|
|
||||||
|| !ReferenceEquals(record.RemoteMotionRuntime, runtime)
|
|
||||||
|| (record.FinalPhysicsState & PhysicsStateFlags.Static) != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
record.ObjectClock.Activate();
|
|
||||||
runtime.Body.TransientState |= TransientStateFlags.Active;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Commits retail <c>CPhysicsObj::set_velocity</c> (0x005113F0) to the
|
/// Commits retail <c>CPhysicsObj::set_velocity</c> (0x005113F0) to the
|
||||||
/// canonical body for this exact incarnation. The body and the retained
|
/// canonical body for this exact incarnation. The body and the retained
|
||||||
|
|
@ -1941,41 +1934,19 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(record);
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
ArgumentNullException.ThrowIfNull(body);
|
ArgumentNullException.ThrowIfNull(body);
|
||||||
if (!IsFinite(velocity)
|
bool ProjectionIsCurrent() =>
|
||||||
|| (angularVelocity is { } omega && !IsFinite(omega))
|
_projections.TryGetCurrent(
|
||||||
|| !double.IsFinite(currentTime)
|
|
||||||
|| !_projections.TryGetCurrent(
|
|
||||||
record.ServerGuid,
|
record.ServerGuid,
|
||||||
out LiveEntityRecord? current)
|
out LiveEntityRecord? current)
|
||||||
|| !ReferenceEquals(current, record)
|
&& ReferenceEquals(current, record)
|
||||||
|| !ReferenceEquals(current.PhysicsBody, body))
|
&& ReferenceEquals(current.PhysicsBody, body);
|
||||||
{
|
return _physics.TryCommitAuthoritativeVector(
|
||||||
return false;
|
record.Canonical,
|
||||||
}
|
body,
|
||||||
|
velocity,
|
||||||
bool wasBodyActive =
|
angularVelocity,
|
||||||
(body.TransientState & TransientStateFlags.Active) != 0;
|
currentTime,
|
||||||
body.set_velocity(velocity);
|
ProjectionIsCurrent);
|
||||||
if ((current.FinalPhysicsState & PhysicsStateFlags.Static) != 0)
|
|
||||||
{
|
|
||||||
// PhysicsBody models set_velocity's leaf write and therefore sets
|
|
||||||
// Active unconditionally. Retail CPhysicsObj::set_active(1) is a
|
|
||||||
// no-op for Static, so restore the exact prior bit here.
|
|
||||||
if (!wasBodyActive)
|
|
||||||
body.TransientState &= ~TransientStateFlags.Active;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool clockReactivated = current.ObjectClock.Activate();
|
|
||||||
// PhysicsBody.LastUpdateTime remains only the compatibility clock
|
|
||||||
// for older absolute-time helpers; the record clock is canonical.
|
|
||||||
if (clockReactivated || !wasBodyActive)
|
|
||||||
body.LastUpdateTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (angularVelocity is { } acceptedOmega)
|
|
||||||
body.Omega = acceptedOmega;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -2007,7 +1978,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
|
|
||||||
internal bool IsCurrentSpatialRemoteMotion(
|
internal bool IsCurrentSpatialRemoteMotion(
|
||||||
LiveEntityRecord record,
|
LiveEntityRecord record,
|
||||||
ILiveEntityRemoteMotionRuntime runtime) =>
|
IRuntimeRemoteMotion runtime) =>
|
||||||
IsCurrentRecord(record)
|
IsCurrentRecord(record)
|
||||||
&& _physics.IsSpatialRemote(record.Canonical, runtime)
|
&& _physics.IsSpatialRemote(record.Canonical, runtime)
|
||||||
&& HasSpatialRuntimeProjection(record);
|
&& HasSpatialRuntimeProjection(record);
|
||||||
|
|
@ -2546,11 +2517,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
body.TransientState &= ~TransientStateFlags.Active;
|
body.TransientState &= ~TransientStateFlags.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsFinite(Vector3 value) =>
|
|
||||||
float.IsFinite(value.X)
|
|
||||||
&& float.IsFinite(value.Y)
|
|
||||||
&& float.IsFinite(value.Z);
|
|
||||||
|
|
||||||
private void PublishProjectionVisibilityChanged(LiveEntityRecord record, bool visible)
|
private void PublishProjectionVisibilityChanged(LiveEntityRecord record, bool visible)
|
||||||
{
|
{
|
||||||
Delegate[] subscribers = ProjectionVisibilityChanged?.GetInvocationList()
|
Delegate[] subscribers = ProjectionVisibilityChanged?.GetInvocationList()
|
||||||
|
|
@ -2728,16 +2694,11 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.WorldEntity is not null)
|
if (record.WorldEntity is not null)
|
||||||
{
|
|
||||||
_ = RequireProjectionKey(record);
|
_ = RequireProjectionKey(record);
|
||||||
_directory.ReleaseLocalId(record.Canonical);
|
_entityObjects.CompleteProjectionRetirement(
|
||||||
}
|
record.Canonical);
|
||||||
|
|
||||||
record.AnimationRuntime = null;
|
record.AnimationRuntime = null;
|
||||||
record.RemoteMotionRuntime = null;
|
|
||||||
record.PhysicsHost = null;
|
|
||||||
record.RequiresRemotePlacementRuntime = false;
|
|
||||||
record.PhysicsBody = null;
|
|
||||||
record.EffectProfile = null;
|
record.EffectProfile = null;
|
||||||
record.IsSpatiallyProjected = false;
|
record.IsSpatiallyProjected = false;
|
||||||
record.IsSpatiallyVisible = false;
|
record.IsSpatiallyVisible = false;
|
||||||
|
|
|
||||||
|
|
@ -133,36 +133,3 @@ public sealed class LiveEntityAnimationRuntimeView<TAnimation>
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Storage-free typed view over remote-motion components.</summary>
|
|
||||||
public sealed class LiveEntityRemoteMotionRuntimeView<TRemoteMotion>
|
|
||||||
where TRemoteMotion : class, ILiveEntityRemoteMotionRuntime
|
|
||||||
{
|
|
||||||
private readonly ILiveEntityRuntimeSource _runtime;
|
|
||||||
|
|
||||||
internal LiveEntityRemoteMotionRuntimeView(ILiveEntityRuntimeSource runtime) =>
|
|
||||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
|
||||||
|
|
||||||
public TRemoteMotion this[uint serverGuid]
|
|
||||||
{
|
|
||||||
set => (_runtime.Current
|
|
||||||
?? throw new InvalidOperationException("Live entity runtime is not initialized."))
|
|
||||||
.SetRemoteMotionRuntime(serverGuid, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetValue(uint serverGuid, out TRemoteMotion motion)
|
|
||||||
{
|
|
||||||
if (_runtime.Current?.TryGetRemoteMotionRuntime(serverGuid, out var found) == true
|
|
||||||
&& found is TRemoteMotion typed)
|
|
||||||
{
|
|
||||||
motion = typed;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
motion = null!;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(uint serverGuid) =>
|
|
||||||
_runtime.Current?.ClearRemoteMotionRuntime(serverGuid) == true;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,27 @@ public sealed class PhysicsEngine
|
||||||
DataCache?.CellGraph.RemoveLandblock(landblockId);
|
DataCache?.CellGraph.RemoveLandblock(landblockId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases every collision landblock and retained shadow registration
|
||||||
|
/// owned by this engine. Runtime calls this only at terminal disposal;
|
||||||
|
/// ordinary streaming still uses the typed per-landblock retirement path.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (_landblocks.Count != 0)
|
||||||
|
{
|
||||||
|
var landblocks = new uint[_landblocks.Count];
|
||||||
|
_landblocks.Keys.CopyTo(landblocks, 0);
|
||||||
|
foreach (uint landblockId in landblocks)
|
||||||
|
RemoveLandblock(landblockId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic live registrations can remain intentionally suspended with
|
||||||
|
// no cell rows after their last landblock retires. Terminal engine
|
||||||
|
// ownership must retire those logical registrations as well.
|
||||||
|
ShadowObjects.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retire a Near landblock's indoor-cell and static-object collision layer
|
/// Retire a Near landblock's indoor-cell and static-object collision layer
|
||||||
/// while preserving its terrain surface and world offset for Far-tier use.
|
/// while preserving its terrain surface and world offset for Far-tier use.
|
||||||
|
|
|
||||||
|
|
@ -795,6 +795,21 @@ public sealed class ShadowObjectRegistry
|
||||||
/// <summary>Suspended logical registrations awaiting spatial re-entry.</summary>
|
/// <summary>Suspended logical registrations awaiting spatial re-entry.</summary>
|
||||||
public int SuspendedRegistrationCount => _suspendedEntities.Count;
|
public int SuspendedRegistrationCount => _suspendedEntities.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retires the complete logical registry at terminal physics-engine
|
||||||
|
/// disposal, including suspended live registrations that own no cell row.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_cells.Clear();
|
||||||
|
_entityToCells.Clear();
|
||||||
|
_suspendedEntities.Clear();
|
||||||
|
_withdrawnPrefixesByOwner.Clear();
|
||||||
|
_entityShapes.Clear();
|
||||||
|
_entityReg.Clear();
|
||||||
|
_fallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Debug: enumerate every registered ShadowEntry (deduplicated across cells).
|
/// Debug: enumerate every registered ShadowEntry (deduplicated across cells).
|
||||||
/// Single-shape entities return one entry per shape; multi-part entities
|
/// Single-shape entities return one entry per shape; multi-part entities
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,29 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
|
||||||
bool HasLastStreamDispatchFailure,
|
bool HasLastStreamDispatchFailure,
|
||||||
int PendingDispatchCount,
|
int PendingDispatchCount,
|
||||||
bool IsDispatching,
|
bool IsDispatching,
|
||||||
bool IsSessionClearInProgress);
|
bool IsSessionClearInProgress,
|
||||||
|
bool IsDisposed)
|
||||||
|
{
|
||||||
|
public bool IsConverged =>
|
||||||
|
IsDisposed
|
||||||
|
&& ActiveEntityCount == 0
|
||||||
|
&& TeardownEntityCount == 0
|
||||||
|
&& ClaimedLocalIdCount == 0
|
||||||
|
&& AcceptedSnapshotCount == 0
|
||||||
|
&& UnresolvedParentRelationCount == 0
|
||||||
|
&& StagedParentRelationCount == 0
|
||||||
|
&& RecoveryParentRelationCount == 0
|
||||||
|
&& CommittedParentRelationCount == 0
|
||||||
|
&& ObjectCount == 0
|
||||||
|
&& ContainerCount == 0
|
||||||
|
&& ContainerProjectionCount == 0
|
||||||
|
&& EquipmentOwnerCount == 0
|
||||||
|
&& PendingMoveCount == 0
|
||||||
|
&& StreamSubscriberCount == 0
|
||||||
|
&& PendingDispatchCount == 0
|
||||||
|
&& !IsDispatching
|
||||||
|
&& !IsSessionClearInProgress;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// One exact DeleteObject acceptance issued by a
|
/// One exact DeleteObject acceptance issued by a
|
||||||
|
|
@ -140,7 +162,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
Events.LastDispatchFailure is not null,
|
Events.LastDispatchFailure is not null,
|
||||||
Events.PendingDispatchCount,
|
Events.PendingDispatchCount,
|
||||||
Events.IsDispatching,
|
Events.IsDispatching,
|
||||||
_sessionClearInProgress);
|
_sessionClearInProgress,
|
||||||
|
_disposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BindEventContext(
|
public void BindEventContext(
|
||||||
|
|
@ -355,17 +378,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
Entities.RetainTeardown(canonical);
|
Entities.RetainTeardown(canonical);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Physics.RemoveSpatialProjection(canonical);
|
CompleteProjectionRetirement(canonical);
|
||||||
Entities.SetRemoteMotion(canonical, null);
|
|
||||||
Entities.SetRemoteMotionBindingInProgress(canonical, false);
|
|
||||||
Entities.SetProjectile(canonical, null);
|
|
||||||
Entities.SetProjectileBindingInProgress(canonical, false);
|
|
||||||
Entities.SetRequiresRemotePlacementRuntime(canonical, false);
|
|
||||||
Entities.SetPhysicsHost(canonical, null);
|
|
||||||
Entities.SetPhysicsBody(canonical, null);
|
|
||||||
Entities.SetPhysicsBodyAcquisitionInProgress(canonical, false);
|
|
||||||
Entities.SetHasPartArray(canonical, false);
|
|
||||||
Entities.ReleaseLocalId(canonical);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -379,6 +392,30 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Completes the presentation acknowledgement for one retained
|
||||||
|
/// incarnation. Runtime retires every presentation-independent component
|
||||||
|
/// and releases its local identity only after the graphical host has
|
||||||
|
/// finished using those exact components for ExitWorld teardown.
|
||||||
|
/// </summary>
|
||||||
|
internal void CompleteProjectionRetirement(
|
||||||
|
RuntimeEntityRecord canonical)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
ArgumentNullException.ThrowIfNull(canonical);
|
||||||
|
Physics.RemoveSpatialProjection(canonical);
|
||||||
|
Entities.SetRemoteMotion(canonical, null);
|
||||||
|
Entities.SetRemoteMotionBindingInProgress(canonical, false);
|
||||||
|
Entities.SetProjectile(canonical, null);
|
||||||
|
Entities.SetProjectileBindingInProgress(canonical, false);
|
||||||
|
Entities.SetRequiresRemotePlacementRuntime(canonical, false);
|
||||||
|
Entities.SetPhysicsHost(canonical, null);
|
||||||
|
Entities.SetPhysicsBody(canonical, null);
|
||||||
|
Entities.SetPhysicsBodyAcquisitionInProgress(canonical, false);
|
||||||
|
Entities.SetHasPartArray(canonical, false);
|
||||||
|
Entities.ReleaseLocalId(canonical);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies the retained-object half of an accepted CreateObject while the
|
/// Applies the retained-object half of an accepted CreateObject while the
|
||||||
/// exact canonical incarnation and its integration version remain current.
|
/// exact canonical incarnation and its integration version remain current.
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ internal sealed class RuntimeEntityObjectViews
|
||||||
: IRuntimeEntityView
|
: IRuntimeEntityView
|
||||||
{
|
{
|
||||||
public int Count => owner.Count;
|
public int Count => owner.Count;
|
||||||
|
public int MaterializedCount => owner.ClaimedLocalIdCount;
|
||||||
|
|
||||||
public bool TryGet(
|
public bool TryGet(
|
||||||
uint serverGuid,
|
uint serverGuid,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ public interface IRuntimeEntityView
|
||||||
{
|
{
|
||||||
int Count { get; }
|
int Count { get; }
|
||||||
|
|
||||||
|
int MaterializedCount { get; }
|
||||||
|
|
||||||
bool TryGet(uint serverGuid, out RuntimeEntitySnapshot entity);
|
bool TryGet(uint serverGuid, out RuntimeEntitySnapshot entity);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,19 @@ public readonly record struct RuntimePhysicsOwnershipSnapshot(
|
||||||
int CollisionAdmissionCount,
|
int CollisionAdmissionCount,
|
||||||
int CollisionGenerationCount,
|
int CollisionGenerationCount,
|
||||||
bool OwnsProductionDataCache,
|
bool OwnsProductionDataCache,
|
||||||
bool IsDisposed);
|
bool IsDisposed)
|
||||||
|
{
|
||||||
|
public bool IsConverged =>
|
||||||
|
IsDisposed
|
||||||
|
&& LandblockCount == 0
|
||||||
|
&& RetainedShadowRegistrationCount == 0
|
||||||
|
&& SpatialRootCount == 0
|
||||||
|
&& SpatialRemoteCount == 0
|
||||||
|
&& SpatialProjectileCount == 0
|
||||||
|
&& CollisionAdmissionCount == 0
|
||||||
|
&& CollisionGenerationCount == 0
|
||||||
|
&& OwnsProductionDataCache;
|
||||||
|
}
|
||||||
|
|
||||||
public readonly record struct RuntimePhysicsCellCommit(
|
public readonly record struct RuntimePhysicsCellCommit(
|
||||||
RuntimeEntityRecord Record,
|
RuntimeEntityRecord Record,
|
||||||
|
|
@ -437,6 +449,8 @@ public sealed class RuntimePhysicsState : IDisposable
|
||||||
record,
|
record,
|
||||||
expectedPlacementContract
|
expectedPlacementContract
|
||||||
|| runtime is IRuntimeRemotePlacement);
|
|| runtime is IRuntimeRemotePlacement);
|
||||||
|
if (expectedBody is null)
|
||||||
|
InitializeNewPhysicsBody(record, candidateBody);
|
||||||
Entities.SetRemoteMotion(record, runtime);
|
Entities.SetRemoteMotion(record, runtime);
|
||||||
Entities.SetPhysicsBody(record, candidateBody);
|
Entities.SetPhysicsBody(record, candidateBody);
|
||||||
candidateBody.State = record.FinalPhysicsState;
|
candidateBody.State = record.FinalPhysicsState;
|
||||||
|
|
@ -452,6 +466,52 @@ public sealed class RuntimePhysicsState : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs the one built-in remote-motion component for an exact
|
||||||
|
/// canonical incarnation. A graphical host may attach animation and pose
|
||||||
|
/// sinks afterward, but cannot construct or replace simulation state.
|
||||||
|
/// </summary>
|
||||||
|
internal RemoteMotion GetOrCreateRemoteMotion(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
Func<bool>? externalOwnerValid = null)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
EnsureCurrent(record);
|
||||||
|
if (record.RemoteMotion is { } retained)
|
||||||
|
{
|
||||||
|
return retained as RemoteMotion
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} owns a non-production remote-motion component.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var created = new RemoteMotion(record.PhysicsBody);
|
||||||
|
created.Movement.ActivatePhysicsObject = () =>
|
||||||
|
TryActivateOrdinaryObject(record, created);
|
||||||
|
SetRemoteMotion(record, created, externalOwnerValid);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryActivateOrdinaryObject(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
IRuntimeRemoteMotion runtime,
|
||||||
|
Func<bool>? externalOwnerValid = null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
ArgumentNullException.ThrowIfNull(runtime);
|
||||||
|
if (!Entities.IsCurrent(record)
|
||||||
|
|| !ReferenceEquals(record.RemoteMotion, runtime)
|
||||||
|
|| (record.FinalPhysicsState & PhysicsStateFlags.Static) != 0
|
||||||
|
|| !(externalOwnerValid?.Invoke() ?? true))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
record.ObjectClock.Activate();
|
||||||
|
runtime.Body.TransientState |= TransientStateFlags.Active;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Acquires the one retail <c>CPhysicsObj</c> body for an exact accepted
|
/// Acquires the one retail <c>CPhysicsObj</c> body for an exact accepted
|
||||||
/// object incarnation. Factories may cross a host boundary, so ownership
|
/// object incarnation. Factories may cross a host boundary, so ownership
|
||||||
|
|
@ -499,6 +559,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
||||||
$"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} acquired two physics bodies within one incarnation.");
|
$"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} acquired two physics bodies within one incarnation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InitializeNewPhysicsBody(record, candidate);
|
||||||
Entities.SetPhysicsBody(record, candidate);
|
Entities.SetPhysicsBody(record, candidate);
|
||||||
candidate.State = record.FinalPhysicsState;
|
candidate.State = record.FinalPhysicsState;
|
||||||
SynchronizeBodyActiveState(record);
|
SynchronizeBodyActiveState(record);
|
||||||
|
|
@ -905,6 +966,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
return;
|
return;
|
||||||
|
Engine.Clear();
|
||||||
_spatialRemotes.Clear();
|
_spatialRemotes.Clear();
|
||||||
_spatialProjectiles.Clear();
|
_spatialProjectiles.Clear();
|
||||||
_spatialRoots.Clear();
|
_spatialRoots.Clear();
|
||||||
|
|
@ -1039,4 +1101,22 @@ public sealed class RuntimePhysicsState : IDisposable
|
||||||
else
|
else
|
||||||
body.TransientState &= ~TransientStateFlags.Active;
|
body.TransientState &= ~TransientStateFlags.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retail <c>CPhysicsObj::set_description</c> (0x00514F40) installs the
|
||||||
|
/// accepted CreateObject velocity and omega once, when the incarnation's
|
||||||
|
/// CPhysicsObj is first acquired. Later SetState never replays them.
|
||||||
|
/// </summary>
|
||||||
|
private static void InitializeNewPhysicsBody(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
PhysicsBody body)
|
||||||
|
{
|
||||||
|
body.State = record.FinalPhysicsState;
|
||||||
|
if (record.Snapshot.Physics is not { } physics)
|
||||||
|
return;
|
||||||
|
if (physics.Velocity is { } velocity && IsFinite(velocity))
|
||||||
|
body.set_velocity(velocity);
|
||||||
|
if (physics.AngularVelocity is { } omega && IsFinite(omega))
|
||||||
|
body.Omega = omega;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
src/AcDream.Runtime/RuntimeSimulationOwnership.cs
Normal file
44
src/AcDream.Runtime/RuntimeSimulationOwnership.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
using AcDream.Runtime.Entities;
|
||||||
|
using AcDream.Runtime.Gameplay;
|
||||||
|
using AcDream.Runtime.Physics;
|
||||||
|
|
||||||
|
namespace AcDream.Runtime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One allocation-free terminal ownership ledger for the complete J5
|
||||||
|
/// presentation-independent simulation graph. It owns no mutable state.
|
||||||
|
/// Graphical and no-window hosts inspect the same canonical owners.
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct RuntimeSimulationOwnershipSnapshot(
|
||||||
|
RuntimeEntityObjectOwnershipSnapshot EntityObjects,
|
||||||
|
RuntimePhysicsOwnershipSnapshot Physics,
|
||||||
|
RuntimeGameplayOwnershipSnapshot Gameplay)
|
||||||
|
{
|
||||||
|
public bool IsConverged =>
|
||||||
|
EntityObjects.IsConverged
|
||||||
|
&& Physics.IsConverged
|
||||||
|
&& Gameplay.IsConverged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RuntimeSimulationOwnership
|
||||||
|
{
|
||||||
|
public static RuntimeSimulationOwnershipSnapshot Capture(
|
||||||
|
RuntimeEntityObjectLifetime entityObjects,
|
||||||
|
RuntimeInventoryState inventory,
|
||||||
|
RuntimeCharacterState character,
|
||||||
|
RuntimeCommunicationState communication,
|
||||||
|
RuntimeActionState actions,
|
||||||
|
RuntimeLocalPlayerMovementState movement)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(entityObjects);
|
||||||
|
return new RuntimeSimulationOwnershipSnapshot(
|
||||||
|
entityObjects.CaptureOwnership(),
|
||||||
|
entityObjects.Physics.CaptureOwnership(),
|
||||||
|
RuntimeGameplayOwnership.Capture(
|
||||||
|
inventory,
|
||||||
|
character,
|
||||||
|
communication,
|
||||||
|
actions,
|
||||||
|
movement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -96,7 +96,7 @@ public sealed class LiveCombatAttackOperationsTests
|
||||||
var combat = new CombatState();
|
var combat = new CombatState();
|
||||||
var targets = new FakeTargets();
|
var targets = new FakeTargets();
|
||||||
var settings = new FakeSettings();
|
var settings = new FakeSettings();
|
||||||
var player = new LocalPlayerControllerSlot();
|
var player = new RuntimeLocalPlayerMovementState();
|
||||||
var live = new FakeLiveSource { IsInWorld = inWorld };
|
var live = new FakeLiveSource { IsInWorld = inWorld };
|
||||||
var feedback = new FakeFeedback();
|
var feedback = new FakeFeedback();
|
||||||
var outbound = new LocalPlayerOutboundController(
|
var outbound = new LocalPlayerOutboundController(
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,4 @@
|
||||||
global using AcDream.Runtime.Gameplay;
|
global using AcDream.Runtime.Gameplay;
|
||||||
global using AcDream.Runtime.Physics;
|
global using AcDream.Runtime.Physics;
|
||||||
global using ILiveEntityRemoteMotionRuntime =
|
|
||||||
AcDream.Runtime.Physics.IRuntimeRemoteMotion;
|
|
||||||
global using ILiveEntityPhysicsHostConsumer =
|
|
||||||
AcDream.Runtime.Physics.IRuntimePhysicsHostConsumer;
|
|
||||||
global using ILiveEntityCanonicalRuntimeConsumer =
|
|
||||||
AcDream.Runtime.Physics.IRuntimeCanonicalPhysicsConsumer;
|
|
||||||
global using ILiveEntityCanonicalCellConsumer =
|
|
||||||
AcDream.Runtime.Physics.IRuntimeCanonicalCellConsumer;
|
|
||||||
global using ILiveEntityRemotePlacementRuntime =
|
|
||||||
AcDream.Runtime.Physics.IRuntimeRemotePlacement;
|
|
||||||
global using LocalPlayerControllerSlot =
|
|
||||||
AcDream.Runtime.Gameplay.RuntimeLocalPlayerMovementState;
|
|
||||||
global using ILocalPlayerControllerSource =
|
|
||||||
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerControllerSource;
|
|
||||||
global using ILocalPlayerMotionSource =
|
global using ILocalPlayerMotionSource =
|
||||||
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerMotionSource;
|
AcDream.Runtime.Gameplay.IRuntimeLocalPlayerMotionSource;
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ public sealed class MouseLookControllerTests
|
||||||
{
|
{
|
||||||
PlayerMovementController player = CreatePlayer();
|
PlayerMovementController player = CreatePlayer();
|
||||||
var mode = new LocalPlayerModeState();
|
var mode = new LocalPlayerModeState();
|
||||||
var slot = new LocalPlayerControllerSlot { Controller = player };
|
var slot = new RuntimeLocalPlayerMovementState { Controller = player };
|
||||||
var camera = new CameraController(new OrbitCamera(), new FlyCamera());
|
var camera = new CameraController(new OrbitCamera(), new FlyCamera());
|
||||||
var chase = new ChaseCameraInputState();
|
var chase = new ChaseCameraInputState();
|
||||||
var mouse = new FakeMouse();
|
var mouse = new FakeMouse();
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ using AcDream.Core.Physics;
|
||||||
|
|
||||||
namespace AcDream.App.Tests.Input;
|
namespace AcDream.App.Tests.Input;
|
||||||
|
|
||||||
public sealed class LocalPlayerControllerSlotTests
|
public sealed class RuntimeLocalPlayerMovementStateTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MotionPreparation_RoutesCompletionWithoutPublishingController()
|
public void MotionPreparation_RoutesCompletionWithoutPublishingController()
|
||||||
{
|
{
|
||||||
var slot = new LocalPlayerControllerSlot();
|
var slot = new RuntimeLocalPlayerMovementState();
|
||||||
ILocalPlayerMotionSource source = slot;
|
ILocalPlayerMotionSource source = slot;
|
||||||
var controller = new PlayerMovementController(new PhysicsEngine());
|
var controller = new PlayerMovementController(new PhysicsEngine());
|
||||||
controller.Motion.AddToQueue(0, MotionCommand.Ready, 0);
|
controller.Motion.AddToQueue(0, MotionCommand.Ready, 0);
|
||||||
|
|
@ -29,7 +29,7 @@ public sealed class LocalPlayerControllerSlotTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MotionPreparation_HandsOffToCommittedControllerOnDispose()
|
public void MotionPreparation_HandsOffToCommittedControllerOnDispose()
|
||||||
{
|
{
|
||||||
var slot = new LocalPlayerControllerSlot();
|
var slot = new RuntimeLocalPlayerMovementState();
|
||||||
ILocalPlayerMotionSource source = slot;
|
ILocalPlayerMotionSource source = slot;
|
||||||
var controller = new PlayerMovementController(new PhysicsEngine());
|
var controller = new PlayerMovementController(new PhysicsEngine());
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ public sealed class LocalPlayerControllerSlotTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MotionPreparation_DrainsPriorAnimationBeforePublishingCandidate()
|
public void MotionPreparation_DrainsPriorAnimationBeforePublishingCandidate()
|
||||||
{
|
{
|
||||||
var slot = new LocalPlayerControllerSlot();
|
var slot = new RuntimeLocalPlayerMovementState();
|
||||||
ILocalPlayerMotionSource source = slot;
|
ILocalPlayerMotionSource source = slot;
|
||||||
var prior = new PlayerMovementController(new PhysicsEngine());
|
var prior = new PlayerMovementController(new PhysicsEngine());
|
||||||
var candidate = new PlayerMovementController(new PhysicsEngine());
|
var candidate = new PlayerMovementController(new PhysicsEngine());
|
||||||
|
|
@ -71,7 +71,7 @@ public sealed class LocalPlayerControllerSlotTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MotionPreparation_RejectsConcurrentCandidate()
|
public void MotionPreparation_RejectsConcurrentCandidate()
|
||||||
{
|
{
|
||||||
var slot = new LocalPlayerControllerSlot();
|
var slot = new RuntimeLocalPlayerMovementState();
|
||||||
var first = new PlayerMovementController(new PhysicsEngine());
|
var first = new PlayerMovementController(new PhysicsEngine());
|
||||||
var second = new PlayerMovementController(new PhysicsEngine());
|
var second = new PlayerMovementController(new PhysicsEngine());
|
||||||
|
|
||||||
|
|
@ -105,11 +105,10 @@ public sealed class ProjectileControllerTests
|
||||||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||||
WorldEntity entity = record.WorldEntity!;
|
WorldEntity entity = record.WorldEntity!;
|
||||||
Assert.Null(record.AnimationRuntime);
|
Assert.Null(record.AnimationRuntime);
|
||||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
var remote =
|
||||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||||
remote.Body.Position = entity.Position;
|
remote.Body.Position = entity.Position;
|
||||||
remote.Body.Orientation = entity.Rotation;
|
remote.Body.Orientation = entity.Rotation;
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
|
||||||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1));
|
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0, 1, 1));
|
||||||
Assert.Same(remote.Body, record.ProjectileRuntime!.Body);
|
Assert.Same(remote.Body, record.ProjectileRuntime!.Body);
|
||||||
|
|
||||||
|
|
@ -686,7 +685,9 @@ public sealed class ProjectileControllerTests
|
||||||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
||||||
PhysicsBody body = record.PhysicsBody!;
|
PhysicsBody body = record.PhysicsBody!;
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, new AcDream.Runtime.Physics.RemoteMotion(body));
|
Assert.Same(
|
||||||
|
body,
|
||||||
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid).Body);
|
||||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||||
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
||||||
record, record.FinalPhysicsState, 2.0, 1, 1));
|
record, record.FinalPhysicsState, 2.0, 1, 1));
|
||||||
|
|
@ -710,7 +711,9 @@ public sealed class ProjectileControllerTests
|
||||||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
||||||
PhysicsBody body = record.PhysicsBody!;
|
PhysicsBody body = record.PhysicsBody!;
|
||||||
Vector3 position = body.Position;
|
Vector3 position = body.Position;
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, new AcDream.Runtime.Physics.RemoteMotion(body));
|
Assert.Same(
|
||||||
|
body,
|
||||||
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid).Body);
|
||||||
|
|
||||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||||
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
||||||
|
|
@ -949,9 +952,8 @@ public sealed class ProjectileControllerTests
|
||||||
var fixture = new Fixture();
|
var fixture = new Fixture();
|
||||||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
var remote =
|
||||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
|
||||||
|
|
||||||
PhysicsStateFlags ordinaryState =
|
PhysicsStateFlags ordinaryState =
|
||||||
PhysicsStateFlags.Gravity | PhysicsStateFlags.IgnoreCollisions;
|
PhysicsStateFlags.Gravity | PhysicsStateFlags.IgnoreCollisions;
|
||||||
|
|
@ -1001,9 +1003,8 @@ public sealed class ProjectileControllerTests
|
||||||
var fixture = new Fixture();
|
var fixture = new Fixture();
|
||||||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
var remote =
|
||||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
|
||||||
|
|
||||||
record.FinalPhysicsState = MissileState;
|
record.FinalPhysicsState = MissileState;
|
||||||
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
||||||
|
|
@ -1073,15 +1074,14 @@ public sealed class ProjectileControllerTests
|
||||||
seedCellId: startCell,
|
seedCellId: startCell,
|
||||||
isStatic: false);
|
isStatic: false);
|
||||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
var remote =
|
||||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||||
Assert.Equal(new Vector3(40f, 0f, 0f), remote.Body.Velocity);
|
Assert.Equal(new Vector3(40f, 0f, 0f), remote.Body.Velocity);
|
||||||
Assert.Equal(new Vector3(0f, 0f, 2f), remote.Body.Omega);
|
Assert.Equal(new Vector3(0f, 0f, 2f), remote.Body.Omega);
|
||||||
remote.Body.set_velocity(new Vector3(8f, 0f, 0f));
|
remote.Body.set_velocity(new Vector3(8f, 0f, 0f));
|
||||||
remote.Body.Omega = new Vector3(0f, 0f, 3f);
|
remote.Body.Omega = new Vector3(0f, 0f, 3f);
|
||||||
remote.Body.Position = entity.Position;
|
remote.Body.Position = entity.Position;
|
||||||
remote.Body.State = record.FinalPhysicsState;
|
remote.Body.State = record.FinalPhysicsState;
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
|
||||||
PhysicsBody body = remote.Body;
|
PhysicsBody body = remote.Body;
|
||||||
|
|
||||||
record.FinalPhysicsState = MissileState;
|
record.FinalPhysicsState = MissileState;
|
||||||
|
|
@ -1139,11 +1139,10 @@ public sealed class ProjectileControllerTests
|
||||||
velocity: new Vector3(float.NaN, 0f, 0f));
|
velocity: new Vector3(float.NaN, 0f, 0f));
|
||||||
WorldEntity entity = record.WorldEntity!;
|
WorldEntity entity = record.WorldEntity!;
|
||||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
var remote =
|
||||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||||
remote.Body.Position = entity.Position;
|
remote.Body.Position = entity.Position;
|
||||||
remote.Body.Orientation = entity.Rotation;
|
remote.Body.Orientation = entity.Rotation;
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
|
||||||
|
|
||||||
Assert.True(float.IsNaN(record.Snapshot.Physics!.Value.Velocity!.Value.X));
|
Assert.True(float.IsNaN(record.Snapshot.Physics!.Value.Velocity!.Value.X));
|
||||||
Assert.Equal(Vector3.Zero, remote.Body.Velocity);
|
Assert.Equal(Vector3.Zero, remote.Body.Velocity);
|
||||||
|
|
@ -1165,12 +1164,11 @@ public sealed class ProjectileControllerTests
|
||||||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||||
WorldEntity entity = record.WorldEntity!;
|
WorldEntity entity = record.WorldEntity!;
|
||||||
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
|
||||||
var remote = new AcDream.Runtime.Physics.RemoteMotion();
|
var remote =
|
||||||
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||||
remote.Body.Position = entity.Position;
|
remote.Body.Position = entity.Position;
|
||||||
remote.Body.Orientation = entity.Rotation;
|
remote.Body.Orientation = entity.Rotation;
|
||||||
remote.Body.Velocity = new Vector3(float.PositiveInfinity, 0f, 0f);
|
remote.Body.Velocity = new Vector3(float.PositiveInfinity, 0f, 0f);
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
|
||||||
|
|
||||||
record.FinalPhysicsState = MissileState;
|
record.FinalPhysicsState = MissileState;
|
||||||
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
Assert.True(fixture.Controller.ApplyAuthoritativeState(
|
||||||
|
|
@ -1189,8 +1187,9 @@ public sealed class ProjectileControllerTests
|
||||||
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
LiveEntityRecord record = fixture.Spawn(instance: 1);
|
||||||
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
|
||||||
PhysicsBody body = record.PhysicsBody!;
|
PhysicsBody body = record.PhysicsBody!;
|
||||||
var remote = new AcDream.Runtime.Physics.RemoteMotion(body);
|
var remote =
|
||||||
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
|
fixture.Live.GetOrCreateRemoteMotionRuntime(Guid);
|
||||||
|
Assert.Same(body, remote.Body);
|
||||||
|
|
||||||
fixture.Live.RebucketLiveEntity(Guid, CellB);
|
fixture.Live.RebucketLiveEntity(Guid, CellB);
|
||||||
Assert.Equal(CellB, remote.CellId);
|
Assert.Equal(CellB, remote.CellId);
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ public sealed class RemoteTeleportControllerTests
|
||||||
public void Unregister(WorldEntity entity) { }
|
public void Unregister(WorldEntity entity) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class BodyOnlyRemote(PhysicsBody body) : ILiveEntityRemoteMotionRuntime
|
private sealed class BodyOnlyRemote(PhysicsBody body) : IRuntimeRemoteMotion
|
||||||
{
|
{
|
||||||
public PhysicsBody Body { get; } = body;
|
public PhysicsBody Body { get; } = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class MutablePlacementRemote(PhysicsBody body) : ILiveEntityRemotePlacementRuntime
|
private sealed class MutablePlacementRemote(PhysicsBody body) : IRuntimeRemotePlacement
|
||||||
{
|
{
|
||||||
private uint _cellId;
|
private uint _cellId;
|
||||||
private Func<uint>? _readCell;
|
private Func<uint>? _readCell;
|
||||||
|
|
@ -56,7 +56,7 @@ public sealed class RemoteTeleportControllerTests
|
||||||
|
|
||||||
private sealed class AlternatingBodyRemote(
|
private sealed class AlternatingBodyRemote(
|
||||||
PhysicsBody first,
|
PhysicsBody first,
|
||||||
PhysicsBody later) : ILiveEntityRemotePlacementRuntime
|
PhysicsBody later) : IRuntimeRemotePlacement
|
||||||
{
|
{
|
||||||
private int _reads;
|
private int _reads;
|
||||||
public PhysicsBody Body => _reads++ == 0 ? first : later;
|
public PhysicsBody Body => _reads++ == 0 ? first : later;
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ public sealed class WorldRenderFrameBuilderTests
|
||||||
cells,
|
cells,
|
||||||
mode,
|
mode,
|
||||||
chase,
|
chase,
|
||||||
new LocalPlayerControllerSlot(),
|
new RuntimeLocalPlayerMovementState(),
|
||||||
origin);
|
origin);
|
||||||
var camera = new FlyCamera { Position = new Vector3(5f, 6f, 7f) };
|
var camera = new FlyCamera { Position = new Vector3(5f, 6f, 7f) };
|
||||||
WorldCameraFrame cameraFrame = CameraFrame(camera);
|
WorldCameraFrame cameraFrame = CameraFrame(camera);
|
||||||
|
|
@ -227,7 +227,7 @@ public sealed class WorldRenderFrameBuilderTests
|
||||||
new CellVisibility(),
|
new CellVisibility(),
|
||||||
new LocalPlayerModeState(),
|
new LocalPlayerModeState(),
|
||||||
new ChaseCameraInputState(),
|
new ChaseCameraInputState(),
|
||||||
new LocalPlayerControllerSlot(),
|
new RuntimeLocalPlayerMovementState(),
|
||||||
new LiveWorldOriginState());
|
new LiveWorldOriginState());
|
||||||
WorldCameraFrame camera = CameraFrame(new FlyCamera());
|
WorldCameraFrame camera = CameraFrame(new FlyCamera());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -827,7 +827,6 @@ public sealed class CurrentGameRuntimeAdapterTests
|
||||||
Host,
|
Host,
|
||||||
Commands,
|
Commands,
|
||||||
Identity,
|
Identity,
|
||||||
Entities,
|
|
||||||
EntityObjects,
|
EntityObjects,
|
||||||
InventoryState,
|
InventoryState,
|
||||||
Character,
|
Character,
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,13 @@ public sealed class RuntimePhysicsOwnershipTests
|
||||||
runtimeRoot,
|
runtimeRoot,
|
||||||
"Physics",
|
"Physics",
|
||||||
"RuntimePhysicsState.cs"));
|
"RuntimePhysicsState.cs"));
|
||||||
|
string app = string.Join(
|
||||||
|
"\n",
|
||||||
|
Directory.EnumerateFiles(
|
||||||
|
appRoot,
|
||||||
|
"*.cs",
|
||||||
|
SearchOption.AllDirectories)
|
||||||
|
.Select(File.ReadAllText));
|
||||||
|
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"ResolveWithTransition(",
|
"ResolveWithTransition(",
|
||||||
|
|
@ -131,6 +138,67 @@ public sealed class RuntimePhysicsOwnershipTests
|
||||||
"public bool TryGetPhysicsHost(",
|
"public bool TryGetPhysicsHost(",
|
||||||
runtimePhysics,
|
runtimePhysics,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"GetOrCreateRemoteMotion(",
|
||||||
|
runtimePhysics,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"_physics.TryCommitAuthoritativeVector(",
|
||||||
|
liveRuntime,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"TryActivateOrdinaryObject(",
|
||||||
|
liveRuntime,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"_entityObjects.CompleteProjectionRetirement(",
|
||||||
|
liveRuntime,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"record.RemoteMotionRuntime =",
|
||||||
|
liveRuntime,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"record.PhysicsBody =",
|
||||||
|
liveRuntime,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"new RemoteMotion(",
|
||||||
|
app,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"LiveEntityRemoteMotionRuntimeView",
|
||||||
|
app,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"ILiveEntityRemoteMotionRuntime",
|
||||||
|
app,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"ILiveEntityRemotePlacementRuntime",
|
||||||
|
app,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"LocalPlayerControllerSlot",
|
||||||
|
app,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"ILocalPlayerControllerSource",
|
||||||
|
app,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.False(File.Exists(Path.Combine(
|
||||||
|
appRoot,
|
||||||
|
"Physics",
|
||||||
|
"RemotePhysicsBodyInitializer.cs")));
|
||||||
|
Assert.False(typeof(AcDream.App.World.LiveEntityRecord)
|
||||||
|
.GetProperty(nameof(AcDream.App.World.LiveEntityRecord.PhysicsBody))!
|
||||||
|
.CanWrite);
|
||||||
|
Assert.False(typeof(AcDream.App.World.LiveEntityRecord)
|
||||||
|
.GetProperty(nameof(AcDream.App.World.LiveEntityRecord.RemoteMotionRuntime))!
|
||||||
|
.CanWrite);
|
||||||
|
Assert.False(typeof(AcDream.App.World.LiveEntityRecord)
|
||||||
|
.GetProperty(nameof(AcDream.App.World.LiveEntityRecord.PhysicsHost))!
|
||||||
|
.CanWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FindRepositoryRoot()
|
private static string FindRepositoryRoot()
|
||||||
|
|
|
||||||
|
|
@ -411,7 +411,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
||||||
MeshRefs = Array.Empty<MeshRef>(),
|
MeshRefs = Array.Empty<MeshRef>(),
|
||||||
})!;
|
})!;
|
||||||
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
|
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
|
||||||
var controllerSlot = new LocalPlayerControllerSlot
|
var controllerSlot = new RuntimeLocalPlayerMovementState
|
||||||
{
|
{
|
||||||
Controller = new PlayerMovementController(new PhysicsEngine()),
|
Controller = new PlayerMovementController(new PhysicsEngine()),
|
||||||
};
|
};
|
||||||
|
|
@ -494,7 +494,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
||||||
MeshRefs = Array.Empty<MeshRef>(),
|
MeshRefs = Array.Empty<MeshRef>(),
|
||||||
})!;
|
})!;
|
||||||
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
|
var identity = new LocalPlayerIdentityState { ServerGuid = guid };
|
||||||
var controllerSlot = new LocalPlayerControllerSlot
|
var controllerSlot = new RuntimeLocalPlayerMovementState
|
||||||
{
|
{
|
||||||
Controller = new PlayerMovementController(new PhysicsEngine()),
|
Controller = new PlayerMovementController(new PhysicsEngine()),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -422,7 +422,7 @@ public sealed class StreamingFrameControllerTests
|
||||||
}
|
}
|
||||||
|
|
||||||
internal LocalPlayerModeState Mode { get; } = new();
|
internal LocalPlayerModeState Mode { get; } = new();
|
||||||
internal LocalPlayerControllerSlot Player { get; } = new();
|
internal RuntimeLocalPlayerMovementState Player { get; } = new();
|
||||||
internal SessionSource Session { get; } = new();
|
internal SessionSource Session { get; } = new();
|
||||||
internal LiveWorldOriginState Origin { get; } = new();
|
internal LiveWorldOriginState Origin { get; } = new();
|
||||||
internal PlayerLandblockSource PlayerLandblock { get; } = new();
|
internal PlayerLandblockSource PlayerLandblock { get; } = new();
|
||||||
|
|
|
||||||
|
|
@ -144,12 +144,7 @@ public sealed class GameWindowLiveEntityCompositionTests
|
||||||
typeof(LiveEntityAnimationRuntimeView<LiveEntityAnimationState>)
|
typeof(LiveEntityAnimationRuntimeView<LiveEntityAnimationState>)
|
||||||
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
||||||
field => field.Name == "_runtime");
|
field => field.Name == "_runtime");
|
||||||
FieldInfo remoteRuntime = Assert.Single(
|
|
||||||
typeof(LiveEntityRemoteMotionRuntimeView<RemoteMotion>)
|
|
||||||
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic),
|
|
||||||
field => field.Name == "_runtime");
|
|
||||||
Assert.Equal(typeof(ILiveEntityRuntimeSource), animationRuntime.FieldType);
|
Assert.Equal(typeof(ILiveEntityRuntimeSource), animationRuntime.FieldType);
|
||||||
Assert.Equal(typeof(ILiveEntityRuntimeSource), remoteRuntime.FieldType);
|
|
||||||
|
|
||||||
FieldInfo[] projectileFields = typeof(ProjectileController).GetFields(
|
FieldInfo[] projectileFields = typeof(ProjectileController).GetFields(
|
||||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
|
@ -192,7 +187,7 @@ public sealed class GameWindowLiveEntityCompositionTests
|
||||||
source,
|
source,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
"new LiveEntityRemoteMotionRuntimeView<RemoteMotion>(() =>",
|
"LiveEntityRemoteMotionRuntimeView",
|
||||||
source,
|
source,
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
Assert.Contains("new DatProjectileSetupResolver", livePresentation);
|
Assert.Contains("new DatProjectileSetupResolver", livePresentation);
|
||||||
|
|
|
||||||
|
|
@ -371,7 +371,11 @@ public sealed class LiveEntityHydrationControllerTests
|
||||||
LiveEntityRecord record = fixture.Record;
|
LiveEntityRecord record = fixture.Record;
|
||||||
WorldEntity entity = record.WorldEntity!;
|
WorldEntity entity = record.WorldEntity!;
|
||||||
var body = new PhysicsBody();
|
var body = new PhysicsBody();
|
||||||
record.PhysicsBody = body;
|
Assert.Same(
|
||||||
|
body,
|
||||||
|
fixture.Runtime.GetOrCreatePhysicsBody(
|
||||||
|
record.ServerGuid,
|
||||||
|
_ => body));
|
||||||
fixture.Relationships.OnUnparentAction = _ =>
|
fixture.Relationships.OnUnparentAction = _ =>
|
||||||
fixture.Runtime.WithdrawLiveEntityProjection(record)
|
fixture.Runtime.WithdrawLiveEntityProjection(record)
|
||||||
? ChildUnparentDisposition.Completed
|
? ChildUnparentDisposition.Completed
|
||||||
|
|
|
||||||
|
|
@ -874,7 +874,7 @@ public sealed class LiveEntityLifecycleStressTests
|
||||||
_animations.TryGetValue(id, out Animation? animation) ? animation : null;
|
_animations.TryGetValue(id, out Animation? animation) ? animation : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class TestRemoteMotion : ILiveEntityRemotePlacementRuntime
|
private sealed class TestRemoteMotion : IRuntimeRemotePlacement
|
||||||
{
|
{
|
||||||
private uint _cellId;
|
private uint _cellId;
|
||||||
private Func<uint>? _readCell;
|
private Func<uint>? _readCell;
|
||||||
|
|
|
||||||
|
|
@ -710,8 +710,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
||||||
interruptCurrentMovement);
|
interruptCurrentMovement);
|
||||||
|
|
||||||
private sealed class HostConsumerMotion :
|
private sealed class HostConsumerMotion :
|
||||||
ILiveEntityRemoteMotionRuntime,
|
IRuntimeRemoteMotion,
|
||||||
ILiveEntityPhysicsHostConsumer
|
IRuntimePhysicsHostConsumer
|
||||||
{
|
{
|
||||||
private Func<IPhysicsObjHost?>? _read;
|
private Func<IPhysicsObjHost?>? _read;
|
||||||
public PhysicsBody Body { get; } = new();
|
public PhysicsBody Body { get; } = new();
|
||||||
|
|
@ -725,8 +725,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class ThrowingCellConsumerMotion :
|
private sealed class ThrowingCellConsumerMotion :
|
||||||
ILiveEntityRemoteMotionRuntime,
|
IRuntimeRemoteMotion,
|
||||||
ILiveEntityCanonicalRuntimeConsumer
|
IRuntimeCanonicalPhysicsConsumer
|
||||||
{
|
{
|
||||||
private bool _failOnce = true;
|
private bool _failOnce = true;
|
||||||
public PhysicsBody Body { get; } = new();
|
public PhysicsBody Body { get; } = new();
|
||||||
|
|
@ -748,14 +748,14 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
||||||
|
|
||||||
private sealed class AlternatingBodyMotion(
|
private sealed class AlternatingBodyMotion(
|
||||||
PhysicsBody first,
|
PhysicsBody first,
|
||||||
PhysicsBody second) : ILiveEntityRemoteMotionRuntime
|
PhysicsBody second) : IRuntimeRemoteMotion
|
||||||
{
|
{
|
||||||
private int _reads;
|
private int _reads;
|
||||||
public PhysicsBody Body => _reads++ == 0 ? first : second;
|
public PhysicsBody Body => _reads++ == 0 ? first : second;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class SequenceBodyMotion(params PhysicsBody[] bodies) :
|
private sealed class SequenceBodyMotion(params PhysicsBody[] bodies) :
|
||||||
ILiveEntityRemoteMotionRuntime
|
IRuntimeRemoteMotion
|
||||||
{
|
{
|
||||||
public int ReadCount { get; private set; }
|
public int ReadCount { get; private set; }
|
||||||
|
|
||||||
|
|
@ -771,8 +771,8 @@ public sealed class LiveEntityPhysicsHostOwnershipTests
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class CallbackCanonicalMotion(Action callback) :
|
private sealed class CallbackCanonicalMotion(Action callback) :
|
||||||
ILiveEntityRemoteMotionRuntime,
|
IRuntimeRemoteMotion,
|
||||||
ILiveEntityCanonicalRuntimeConsumer
|
IRuntimeCanonicalPhysicsConsumer
|
||||||
{
|
{
|
||||||
public PhysicsBody Body { get; } = new();
|
public PhysicsBody Body { get; } = new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public sealed class LiveEntityRuntimeTests
|
||||||
|
|
||||||
private sealed class EffectProfile : ILiveEntityEffectProfile { }
|
private sealed class EffectProfile : ILiveEntityEffectProfile { }
|
||||||
|
|
||||||
private sealed class RemoteMotionRuntime : ILiveEntityRemoteMotionRuntime
|
private sealed class RemoteMotionRuntime : IRuntimeRemoteMotion
|
||||||
{
|
{
|
||||||
public PhysicsBody Body { get; } = new();
|
public PhysicsBody Body { get; } = new();
|
||||||
}
|
}
|
||||||
|
|
@ -1156,43 +1156,6 @@ public sealed class LiveEntityRuntimeTests
|
||||||
Assert.False(remote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
Assert.False(remote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MovementActivation_ReactivatesExactIncarnation_ButStaticIsNoOp()
|
|
||||||
{
|
|
||||||
const uint ordinaryGuid = 0x70000050u;
|
|
||||||
const uint staticGuid = 0x70000051u;
|
|
||||||
var spatial = new GpuWorldState();
|
|
||||||
spatial.AddLandblock(EmptyLandblock(0x0101FFFFu));
|
|
||||||
var runtime = LiveEntityRuntimeFixture.Create(spatial, new RecordingResources());
|
|
||||||
|
|
||||||
LiveEntityRecord ordinary = RegisterProjection(
|
|
||||||
runtime,
|
|
||||||
Spawn(ordinaryGuid, 1, 1, 0x01010001u));
|
|
||||||
var ordinaryRemote = new RemoteMotionRuntime();
|
|
||||||
runtime.SetRemoteMotionRuntime(ordinaryGuid, ordinaryRemote);
|
|
||||||
ordinary.ObjectClock.Deactivate();
|
|
||||||
ordinaryRemote.Body.TransientState &= ~TransientStateFlags.Active;
|
|
||||||
|
|
||||||
Assert.True(runtime.TryActivateOrdinaryObject(ordinaryGuid, ordinaryRemote));
|
|
||||||
Assert.True(ordinary.ObjectClock.IsActive);
|
|
||||||
Assert.True(ordinaryRemote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
|
||||||
|
|
||||||
LiveEntityRecord staticRecord = RegisterProjection(
|
|
||||||
runtime,
|
|
||||||
Spawn(
|
|
||||||
staticGuid,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
0x01010001u,
|
|
||||||
PhysicsStateFlags.Static));
|
|
||||||
var staticRemote = new RemoteMotionRuntime();
|
|
||||||
runtime.SetRemoteMotionRuntime(staticGuid, staticRemote);
|
|
||||||
|
|
||||||
Assert.False(runtime.TryActivateOrdinaryObject(staticGuid, staticRemote));
|
|
||||||
Assert.False(staticRecord.ObjectClock.IsActive);
|
|
||||||
Assert.False(staticRemote.Body.TransientState.HasFlag(TransientStateFlags.Active));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthoritativeVector_WakesOrdinaryClockButPreservesStaticActivity()
|
public void AuthoritativeVector_WakesOrdinaryClockButPreservesStaticActivity()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,10 @@ public sealed class RuntimeEntityOwnershipTests
|
||||||
Assert.Contains(
|
Assert.Contains(
|
||||||
viewFields,
|
viewFields,
|
||||||
field => field.FieldType == typeof(IRuntimeInventoryView));
|
field => field.FieldType == typeof(IRuntimeInventoryView));
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
viewFields,
|
||||||
|
field => field.FieldType == typeof(LiveEntityRuntime)
|
||||||
|
|| field.FieldType == typeof(ClientObjectTable));
|
||||||
Assert.DoesNotContain(
|
Assert.DoesNotContain(
|
||||||
typeof(CurrentGameRuntimeViewAdapter).GetNestedTypes(
|
typeof(CurrentGameRuntimeViewAdapter).GetNestedTypes(
|
||||||
BindingFlags.NonPublic),
|
BindingFlags.NonPublic),
|
||||||
|
|
@ -271,6 +275,21 @@ public sealed class RuntimeEntityOwnershipTests
|
||||||
Assert.DoesNotContain("_entityObjects.PublishEntity", liveSource);
|
Assert.DoesNotContain("_entityObjects.PublishEntity", liveSource);
|
||||||
Assert.DoesNotContain("_directory.TryApply", liveSource);
|
Assert.DoesNotContain("_directory.TryApply", liveSource);
|
||||||
Assert.DoesNotContain("_directory.RemoveActive", liveSource);
|
Assert.DoesNotContain("_directory.RemoveActive", liveSource);
|
||||||
|
|
||||||
|
string viewSource = File.ReadAllText(Path.Combine(
|
||||||
|
root,
|
||||||
|
"src",
|
||||||
|
"AcDream.App",
|
||||||
|
"Runtime",
|
||||||
|
"CurrentGameRuntimeViewAdapter.cs"));
|
||||||
|
Assert.Contains(
|
||||||
|
"_entityView.MaterializedCount",
|
||||||
|
viewSource,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.DoesNotContain(
|
||||||
|
"_entities.MaterializedCount",
|
||||||
|
viewSource,
|
||||||
|
StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsPresentationType(Type type)
|
private static bool IsPresentationType(Type type)
|
||||||
|
|
|
||||||
|
|
@ -1123,7 +1123,10 @@ public sealed class RuntimeEntityObjectLifetimeTests
|
||||||
lifetime.Dispose();
|
lifetime.Dispose();
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
default(RuntimeEntityObjectOwnershipSnapshot),
|
default(RuntimeEntityObjectOwnershipSnapshot) with
|
||||||
|
{
|
||||||
|
IsDisposed = true,
|
||||||
|
},
|
||||||
lifetime.CaptureOwnership());
|
lifetime.CaptureOwnership());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1158,7 +1161,10 @@ public sealed class RuntimeEntityObjectLifetimeTests
|
||||||
lifetime.Dispose();
|
lifetime.Dispose();
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
default(RuntimeEntityObjectOwnershipSnapshot),
|
default(RuntimeEntityObjectOwnershipSnapshot) with
|
||||||
|
{
|
||||||
|
IsDisposed = true,
|
||||||
|
},
|
||||||
lifetime.CaptureOwnership());
|
lifetime.CaptureOwnership());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,60 @@ using AcDream.Core.Items;
|
||||||
using AcDream.Core.Social;
|
using AcDream.Core.Social;
|
||||||
using AcDream.Runtime.Entities;
|
using AcDream.Runtime.Entities;
|
||||||
using AcDream.Runtime.Gameplay;
|
using AcDream.Runtime.Gameplay;
|
||||||
|
using AcDream.Runtime;
|
||||||
|
|
||||||
namespace AcDream.Runtime.Tests.Gameplay;
|
namespace AcDream.Runtime.Tests.Gameplay;
|
||||||
|
|
||||||
public sealed class RuntimeGameplayOwnershipTests
|
public sealed class RuntimeGameplayOwnershipTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void J5LedgerConvergesEntityPhysicsAndGameplayAsOneLifetime()
|
||||||
|
{
|
||||||
|
var entities = new RuntimeEntityObjectLifetime();
|
||||||
|
var inventory = new RuntimeInventoryState(entities);
|
||||||
|
var character = new RuntimeCharacterState();
|
||||||
|
var communication = new RuntimeCommunicationState();
|
||||||
|
var actions = RuntimeActionTestFactory.Create(inventory.Transactions);
|
||||||
|
var movement = new RuntimeLocalPlayerMovementState();
|
||||||
|
|
||||||
|
movement.Execute(RuntimeMovementCommand.ToggleRunLock);
|
||||||
|
inventory.Transactions.IncrementBusyCount();
|
||||||
|
actions.Selection.Select(
|
||||||
|
0x70000001u,
|
||||||
|
AcDream.Core.Selection.SelectionChangeSource.System);
|
||||||
|
character.Spellbook.OnSpellLearned(42u);
|
||||||
|
communication.Chat.OnSystemMessage("runtime-only", 0u);
|
||||||
|
|
||||||
|
RuntimeSimulationOwnershipSnapshot populated =
|
||||||
|
RuntimeSimulationOwnership.Capture(
|
||||||
|
entities,
|
||||||
|
inventory,
|
||||||
|
character,
|
||||||
|
communication,
|
||||||
|
actions,
|
||||||
|
movement);
|
||||||
|
Assert.False(populated.IsConverged);
|
||||||
|
|
||||||
|
movement.Dispose();
|
||||||
|
actions.Dispose();
|
||||||
|
communication.Dispose();
|
||||||
|
character.Dispose();
|
||||||
|
inventory.Dispose();
|
||||||
|
entities.Dispose();
|
||||||
|
|
||||||
|
RuntimeSimulationOwnershipSnapshot retired =
|
||||||
|
RuntimeSimulationOwnership.Capture(
|
||||||
|
entities,
|
||||||
|
inventory,
|
||||||
|
character,
|
||||||
|
communication,
|
||||||
|
actions,
|
||||||
|
movement);
|
||||||
|
Assert.True(retired.IsConverged);
|
||||||
|
Assert.True(retired.EntityObjects.IsDisposed);
|
||||||
|
Assert.True(retired.Physics.IsDisposed);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CombinedLedgerConvergesEveryGameplayOwnerAndSubscription()
|
public void CombinedLedgerConvergesEveryGameplayOwnerAndSubscription()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,49 @@ public sealed class RuntimePhysicsStateTests
|
||||||
Assert.Equal(0, first.Physics.Engine.LandblockCount);
|
Assert.Equal(0, first.Physics.Engine.LandblockCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TerminalDisposalClearsLandblocksShadowsAndWorksets()
|
||||||
|
{
|
||||||
|
var lifetime = new RuntimeEntityObjectLifetime();
|
||||||
|
RuntimeEntityRecord record =
|
||||||
|
lifetime.Entities.AddActive(Spawn(0x70000041u, 1));
|
||||||
|
lifetime.Physics.AcknowledgeSpatialProjection(
|
||||||
|
record,
|
||||||
|
spatial: true);
|
||||||
|
|
||||||
|
RuntimeCollisionAdmission admission =
|
||||||
|
lifetime.Physics.BeginCollisionAdmission(0x0101FFFFu);
|
||||||
|
lifetime.Physics.AdmitCollisionAssets(
|
||||||
|
admission,
|
||||||
|
CollisionAssets(0x0101FFFFu));
|
||||||
|
_ = lifetime.Physics.CompleteCollisionAdmission(admission);
|
||||||
|
lifetime.Physics.Engine.ShadowObjects.Register(
|
||||||
|
entityId: record.LocalEntityId!.Value,
|
||||||
|
gfxObjId: 0x01000001u,
|
||||||
|
worldPos: new Vector3(10f, 20f, 5f),
|
||||||
|
rotation: Quaternion.Identity,
|
||||||
|
radius: 0.5f,
|
||||||
|
worldOffsetX: 0f,
|
||||||
|
worldOffsetY: 0f,
|
||||||
|
landblockId: 0x0101FFFFu,
|
||||||
|
seedCellId: 0x01010001u,
|
||||||
|
isStatic: false);
|
||||||
|
|
||||||
|
Assert.False(lifetime.Physics.CaptureOwnership().IsConverged);
|
||||||
|
Assert.True(
|
||||||
|
lifetime.Physics.Engine.ShadowObjects
|
||||||
|
.RetainedRegistrationCount > 0);
|
||||||
|
|
||||||
|
lifetime.Dispose();
|
||||||
|
|
||||||
|
RuntimePhysicsOwnershipSnapshot retired =
|
||||||
|
lifetime.Physics.CaptureOwnership();
|
||||||
|
Assert.True(retired.IsConverged);
|
||||||
|
Assert.Equal(0, retired.LandblockCount);
|
||||||
|
Assert.Equal(0, retired.RetainedShadowRegistrationCount);
|
||||||
|
Assert.Equal(0, retired.SpatialRootCount);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RemoteBindingOwnsCanonicalBodyCellAndTypedCellPublication()
|
public void RemoteBindingOwnsCanonicalBodyCellAndTypedCellPublication()
|
||||||
{
|
{
|
||||||
|
|
@ -368,6 +411,66 @@ public sealed class RuntimePhysicsStateTests
|
||||||
Assert.Equal(0, lifetime.Physics.SpatialRemoteCount);
|
Assert.Equal(0, lifetime.Physics.SpatialRemoteCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuiltInRemoteConstructionInstallsCreateVectorsOnlyOnce()
|
||||||
|
{
|
||||||
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||||
|
RuntimeEntityRecord record = lifetime.Entities.AddActive(
|
||||||
|
Spawn(
|
||||||
|
0x70000042u,
|
||||||
|
1,
|
||||||
|
velocity: new Vector3(40f, 0f, 0f),
|
||||||
|
angularVelocity: new Vector3(0f, 0f, 2f)));
|
||||||
|
|
||||||
|
RemoteMotion remote =
|
||||||
|
lifetime.Physics.GetOrCreateRemoteMotion(record);
|
||||||
|
|
||||||
|
Assert.Equal(new Vector3(40f, 0f, 0f), remote.Body.Velocity);
|
||||||
|
Assert.Equal(new Vector3(0f, 0f, 2f), remote.Body.Omega);
|
||||||
|
remote.Body.set_velocity(new Vector3(8f, 0f, 0f));
|
||||||
|
remote.Body.Omega = new Vector3(0f, 0f, 3f);
|
||||||
|
|
||||||
|
Assert.Same(
|
||||||
|
remote,
|
||||||
|
lifetime.Physics.GetOrCreateRemoteMotion(record));
|
||||||
|
Assert.Equal(new Vector3(8f, 0f, 0f), remote.Body.Velocity);
|
||||||
|
Assert.Equal(new Vector3(0f, 0f, 3f), remote.Body.Omega);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuiltInMovementActivationUsesExactRuntimeIncarnation()
|
||||||
|
{
|
||||||
|
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||||
|
RuntimeEntityRecord ordinary =
|
||||||
|
lifetime.Entities.AddActive(Spawn(0x70000043u, 1));
|
||||||
|
RemoteMotion ordinaryRemote =
|
||||||
|
lifetime.Physics.GetOrCreateRemoteMotion(ordinary);
|
||||||
|
ordinary.ObjectClock.Deactivate();
|
||||||
|
ordinaryRemote.Body.TransientState &=
|
||||||
|
~TransientStateFlags.Active;
|
||||||
|
|
||||||
|
ordinaryRemote.Movement.ActivatePhysicsObject!();
|
||||||
|
|
||||||
|
Assert.True(ordinary.ObjectClock.IsActive);
|
||||||
|
Assert.True(ordinaryRemote.Body.IsActive);
|
||||||
|
|
||||||
|
RuntimeEntityRecord staticRecord =
|
||||||
|
lifetime.Entities.AddActive(Spawn(0x70000044u, 1));
|
||||||
|
lifetime.Entities.SetFinalPhysicsState(
|
||||||
|
staticRecord,
|
||||||
|
PhysicsStateFlags.Static);
|
||||||
|
RemoteMotion staticRemote =
|
||||||
|
lifetime.Physics.GetOrCreateRemoteMotion(staticRecord);
|
||||||
|
staticRecord.ObjectClock.Deactivate();
|
||||||
|
staticRemote.Body.TransientState &=
|
||||||
|
~TransientStateFlags.Active;
|
||||||
|
|
||||||
|
staticRemote.Movement.ActivatePhysicsObject!();
|
||||||
|
|
||||||
|
Assert.False(staticRecord.ObjectClock.IsActive);
|
||||||
|
Assert.False(staticRemote.Body.IsActive);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RemotePhysicsTickUsesCanonicalRuntimeAndPublishesPoseSnapshot()
|
public void RemotePhysicsTickUsesCanonicalRuntimeAndPublishesPoseSnapshot()
|
||||||
{
|
{
|
||||||
|
|
@ -523,7 +626,11 @@ public sealed class RuntimePhysicsStateTests
|
||||||
0u);
|
0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static WorldSession.EntitySpawn Spawn(uint guid, ushort instance)
|
private static WorldSession.EntitySpawn Spawn(
|
||||||
|
uint guid,
|
||||||
|
ushort instance,
|
||||||
|
Vector3? velocity = null,
|
||||||
|
Vector3? angularVelocity = null)
|
||||||
{
|
{
|
||||||
var position = new CreateObject.ServerPosition(
|
var position = new CreateObject.ServerPosition(
|
||||||
0x0101FFFFu,
|
0x0101FFFFu,
|
||||||
|
|
@ -559,9 +666,9 @@ public sealed class RuntimePhysicsStateTests
|
||||||
Friction: null,
|
Friction: null,
|
||||||
Elasticity: null,
|
Elasticity: null,
|
||||||
Translucency: null,
|
Translucency: null,
|
||||||
Velocity: null,
|
Velocity: velocity,
|
||||||
Acceleration: null,
|
Acceleration: null,
|
||||||
AngularVelocity: null,
|
AngularVelocity: angularVelocity,
|
||||||
DefaultScriptType: null,
|
DefaultScriptType: null,
|
||||||
DefaultScriptIntensity: null,
|
DefaultScriptIntensity: null,
|
||||||
Timestamps: timestamps);
|
Timestamps: timestamps);
|
||||||
|
|
|
||||||
384
tests/AcDream.Runtime.Tests/RuntimeSimulationFixtureHostTests.cs
Normal file
384
tests/AcDream.Runtime.Tests/RuntimeSimulationFixtureHostTests.cs
Normal file
|
|
@ -0,0 +1,384 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using AcDream.Core.Combat;
|
||||||
|
using AcDream.Core.Items;
|
||||||
|
using AcDream.Core.Net;
|
||||||
|
using AcDream.Core.Net.Messages;
|
||||||
|
using AcDream.Core.Physics;
|
||||||
|
using AcDream.Core.Spells;
|
||||||
|
using AcDream.Runtime.Entities;
|
||||||
|
using AcDream.Runtime.Gameplay;
|
||||||
|
using AcDream.Runtime.Physics;
|
||||||
|
|
||||||
|
namespace AcDream.Runtime.Tests;
|
||||||
|
|
||||||
|
public sealed class RuntimeSimulationFixtureHostTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void RuntimeOnlyHostDrivesCompleteJ5SimulationWithoutPresentation()
|
||||||
|
{
|
||||||
|
var host = new RuntimeOnlySimulationFixtureHost();
|
||||||
|
|
||||||
|
host.Move();
|
||||||
|
host.Use(0x70000011u);
|
||||||
|
host.Attack();
|
||||||
|
host.Cast();
|
||||||
|
host.AdvanceProjectile(failProjection: false);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
[
|
||||||
|
"movement:run",
|
||||||
|
"use:70000011",
|
||||||
|
"attack:prepare",
|
||||||
|
"attack:Medium:0.5",
|
||||||
|
"cast:stop",
|
||||||
|
"cast:1",
|
||||||
|
"cast:busy",
|
||||||
|
"projectile:ack",
|
||||||
|
],
|
||||||
|
host.Trace);
|
||||||
|
|
||||||
|
string[] loaded = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.Select(static assembly =>
|
||||||
|
assembly.GetName().Name ?? string.Empty)
|
||||||
|
.ToArray();
|
||||||
|
Assert.DoesNotContain(loaded, static name =>
|
||||||
|
name.StartsWith(
|
||||||
|
"AcDream.App",
|
||||||
|
StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| name.StartsWith(
|
||||||
|
"AcDream.UI.",
|
||||||
|
StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| name.StartsWith(
|
||||||
|
"Silk.NET",
|
||||||
|
StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| name.StartsWith(
|
||||||
|
"OpenAL",
|
||||||
|
StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| name.StartsWith(
|
||||||
|
"ImGui",
|
||||||
|
StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
host.Dispose();
|
||||||
|
Assert.True(host.CaptureOwnership().IsConverged);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProjectionAcknowledgementFailureStillAllowsTerminalConvergence()
|
||||||
|
{
|
||||||
|
var host = new RuntimeOnlySimulationFixtureHost();
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
host.AdvanceProjectile(failProjection: true));
|
||||||
|
|
||||||
|
host.Dispose();
|
||||||
|
|
||||||
|
RuntimeSimulationOwnershipSnapshot ownership =
|
||||||
|
host.CaptureOwnership();
|
||||||
|
Assert.True(ownership.IsConverged);
|
||||||
|
Assert.Equal(0, ownership.Physics.SpatialProjectileCount);
|
||||||
|
Assert.Equal(0, ownership.EntityObjects.ActiveEntityCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class RuntimeOnlySimulationFixtureHost :
|
||||||
|
IRuntimeInteractionTransport,
|
||||||
|
IRuntimeCombatAttackOperations,
|
||||||
|
IRuntimeCombatTargetOperations,
|
||||||
|
IRuntimeCombatModeOperations,
|
||||||
|
IRuntimeSpellCastOperations,
|
||||||
|
IDisposable
|
||||||
|
{
|
||||||
|
private const uint ProjectileGuid = 0x70000021u;
|
||||||
|
private double _now = 10d;
|
||||||
|
private uint _sequence;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public RuntimeOnlySimulationFixtureHost()
|
||||||
|
{
|
||||||
|
EntityObjects = new RuntimeEntityObjectLifetime();
|
||||||
|
Inventory = new RuntimeInventoryState(EntityObjects);
|
||||||
|
Character = new RuntimeCharacterState();
|
||||||
|
Character.Spellbook.InstallMetadata(SpellMetadata());
|
||||||
|
Character.Spellbook.OnSpellLearned(1u);
|
||||||
|
Communication = new RuntimeCommunicationState();
|
||||||
|
Actions = new RuntimeActionState(
|
||||||
|
Inventory.Transactions,
|
||||||
|
Character.Spellbook,
|
||||||
|
this,
|
||||||
|
this,
|
||||||
|
this,
|
||||||
|
this,
|
||||||
|
now: () => _now);
|
||||||
|
Movement = new RuntimeLocalPlayerMovementState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuntimeEntityObjectLifetime EntityObjects { get; }
|
||||||
|
public RuntimeInventoryState Inventory { get; }
|
||||||
|
public RuntimeCharacterState Character { get; }
|
||||||
|
public RuntimeCommunicationState Communication { get; }
|
||||||
|
public RuntimeActionState Actions { get; }
|
||||||
|
public RuntimeLocalPlayerMovementState Movement { get; }
|
||||||
|
public List<string> Trace { get; } = [];
|
||||||
|
|
||||||
|
public void Move()
|
||||||
|
{
|
||||||
|
Assert.True(Movement.Execute(
|
||||||
|
RuntimeMovementCommand.ToggleRunLock));
|
||||||
|
Assert.True(Movement.AutoRunActive);
|
||||||
|
Trace.Add("movement:run");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Use(uint serverGuid)
|
||||||
|
{
|
||||||
|
ItemUseRequestReservation reservation =
|
||||||
|
Actions.Transactions.BeginUseRequestReservation();
|
||||||
|
RuntimeInteractionDispatchResult result =
|
||||||
|
Actions.Transactions.TryDispatchUse(
|
||||||
|
serverGuid,
|
||||||
|
ownedByPlayer: false,
|
||||||
|
useable: true,
|
||||||
|
reservation,
|
||||||
|
this,
|
||||||
|
out _);
|
||||||
|
Assert.Equal(
|
||||||
|
RuntimeInteractionDispatchResult.Dispatched,
|
||||||
|
result);
|
||||||
|
Actions.Transactions.CompleteUse(0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Attack()
|
||||||
|
{
|
||||||
|
Actions.Combat.SetCombatMode(CombatMode.Melee);
|
||||||
|
Actions.CombatAttack.SetDesiredPower(0.5f);
|
||||||
|
Actions.CombatAttack.PressAttack(AttackHeight.Medium);
|
||||||
|
_now += 0.5d;
|
||||||
|
Actions.CombatAttack.ReleaseAttack();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cast() =>
|
||||||
|
Assert.Equal(
|
||||||
|
CastRequestResult.Sent,
|
||||||
|
Actions.SpellCast.Cast(1u));
|
||||||
|
|
||||||
|
public void AdvanceProjectile(bool failProjection)
|
||||||
|
{
|
||||||
|
RuntimeEntityRecord record =
|
||||||
|
EntityObjects.RegisterEntity(
|
||||||
|
Spawn(ProjectileGuid, 1)).Canonical!;
|
||||||
|
PhysicsBody body =
|
||||||
|
EntityObjects.Physics.GetOrCreatePhysicsBody(
|
||||||
|
record,
|
||||||
|
static canonical =>
|
||||||
|
{
|
||||||
|
var created = new PhysicsBody
|
||||||
|
{
|
||||||
|
Position = new Vector3(10f, 20f, 5f),
|
||||||
|
Orientation = Quaternion.Identity,
|
||||||
|
InWorld = true,
|
||||||
|
TransientState =
|
||||||
|
TransientStateFlags.Active,
|
||||||
|
};
|
||||||
|
created.SnapToCell(
|
||||||
|
canonical.FullCellId,
|
||||||
|
created.Position,
|
||||||
|
created.Position);
|
||||||
|
return created;
|
||||||
|
});
|
||||||
|
_ = EntityObjects.Physics.BindProjectile(
|
||||||
|
record,
|
||||||
|
body,
|
||||||
|
new ProjectileCollisionSphere(
|
||||||
|
Vector3.Zero,
|
||||||
|
0.25f));
|
||||||
|
EntityObjects.Physics.AcknowledgeSpatialProjection(
|
||||||
|
record,
|
||||||
|
spatial: true);
|
||||||
|
|
||||||
|
var updater =
|
||||||
|
new RuntimeProjectilePhysicsUpdater(
|
||||||
|
EntityObjects.Physics);
|
||||||
|
Assert.True(updater.TryBegin(
|
||||||
|
record,
|
||||||
|
quantum: 0.05f,
|
||||||
|
record.ObjectClockEpoch,
|
||||||
|
externalOwnerValid: null,
|
||||||
|
out RuntimeProjectilePhysicsCommit commit));
|
||||||
|
_ = updater.Complete(
|
||||||
|
commit,
|
||||||
|
liveCenterX: 1,
|
||||||
|
liveCenterY: 1,
|
||||||
|
_ =>
|
||||||
|
{
|
||||||
|
if (failProjection)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"projection acknowledgement");
|
||||||
|
}
|
||||||
|
Trace.Add("projectile:ack");
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public RuntimeSimulationOwnershipSnapshot CaptureOwnership() =>
|
||||||
|
RuntimeSimulationOwnership.Capture(
|
||||||
|
EntityObjects,
|
||||||
|
Inventory,
|
||||||
|
Character,
|
||||||
|
Communication,
|
||||||
|
Actions,
|
||||||
|
Movement);
|
||||||
|
|
||||||
|
public bool IsInWorld => true;
|
||||||
|
public uint LocalPlayerId => 0x50000001u;
|
||||||
|
public bool CanSend => true;
|
||||||
|
public bool IsDualWield => false;
|
||||||
|
public bool PlayerReadyForAttack => true;
|
||||||
|
public bool AutoRepeatAttack => false;
|
||||||
|
public bool AutoTarget => false;
|
||||||
|
|
||||||
|
public bool TrySendUse(uint serverGuid, out uint sequence)
|
||||||
|
{
|
||||||
|
sequence = ++_sequence;
|
||||||
|
Trace.Add($"use:{serverGuid:X8}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySendPickup(
|
||||||
|
uint itemGuid,
|
||||||
|
uint destinationContainerId,
|
||||||
|
int placement,
|
||||||
|
out uint sequence)
|
||||||
|
{
|
||||||
|
sequence = ++_sequence;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanStartAttack() => true;
|
||||||
|
public void PrepareAttackRequest() =>
|
||||||
|
Trace.Add("attack:prepare");
|
||||||
|
|
||||||
|
public bool SendAttack(AttackHeight height, float power)
|
||||||
|
{
|
||||||
|
Trace.Add($"attack:{height}:{power:0.0}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendCancelAttack() { }
|
||||||
|
public uint? SelectClosestTarget() => null;
|
||||||
|
public IReadOnlyList<ClientObject> GetOrderedEquipment() => [];
|
||||||
|
public void NotifyExplicitCombatModeRequest() { }
|
||||||
|
public void SendChangeCombatMode(CombatMode mode) { }
|
||||||
|
public bool HasRequiredComponents(uint spellId) => true;
|
||||||
|
|
||||||
|
public bool IsTargetCompatible(
|
||||||
|
uint targetId,
|
||||||
|
SpellMetadata spell,
|
||||||
|
bool showMessage) => true;
|
||||||
|
|
||||||
|
public void StopCompletely() =>
|
||||||
|
Trace.Add("cast:stop");
|
||||||
|
|
||||||
|
public void SendUntargeted(uint spellId) =>
|
||||||
|
Trace.Add($"cast:{spellId}");
|
||||||
|
|
||||||
|
public void SendTargeted(uint targetId, uint spellId) =>
|
||||||
|
Trace.Add($"cast:{targetId}:{spellId}");
|
||||||
|
|
||||||
|
public void DisplayMessage(string message) =>
|
||||||
|
Trace.Add($"message:{message}");
|
||||||
|
|
||||||
|
public void IncrementBusy()
|
||||||
|
{
|
||||||
|
Inventory.Transactions.IncrementBusyCount();
|
||||||
|
Trace.Add("cast:busy");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
_disposed = true;
|
||||||
|
Movement.Dispose();
|
||||||
|
Actions.Dispose();
|
||||||
|
Communication.Dispose();
|
||||||
|
Character.Dispose();
|
||||||
|
Inventory.Dispose();
|
||||||
|
EntityObjects.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SpellTable SpellMetadata()
|
||||||
|
{
|
||||||
|
const string header =
|
||||||
|
"Spell ID,Name,Flags [Hex],IsUntargetted,TargetMask [Hex]";
|
||||||
|
const string row = "1,Runtime Fixture,0x0,true,0x0";
|
||||||
|
return SpellTable.LoadFromReader(
|
||||||
|
new System.IO.StringReader($"{header}\n{row}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WorldSession.EntitySpawn Spawn(
|
||||||
|
uint guid,
|
||||||
|
ushort instance)
|
||||||
|
{
|
||||||
|
var position = new CreateObject.ServerPosition(
|
||||||
|
0x01010001u,
|
||||||
|
10f,
|
||||||
|
20f,
|
||||||
|
5f,
|
||||||
|
1f,
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
0f);
|
||||||
|
var timestamps = new PhysicsTimestamps(
|
||||||
|
Position: 1,
|
||||||
|
Movement: 1,
|
||||||
|
State: 1,
|
||||||
|
Vector: 1,
|
||||||
|
Teleport: 0,
|
||||||
|
ServerControlledMove: 1,
|
||||||
|
ForcePosition: 0,
|
||||||
|
ObjDesc: 1,
|
||||||
|
Instance: instance);
|
||||||
|
var physics = new PhysicsSpawnData(
|
||||||
|
RawState: (uint)(
|
||||||
|
PhysicsStateFlags.ReportCollisions
|
||||||
|
| PhysicsStateFlags.Missile),
|
||||||
|
Position: position,
|
||||||
|
Movement: null,
|
||||||
|
AnimationFrame: null,
|
||||||
|
SetupTableId: 0x02000001u,
|
||||||
|
MotionTableId: null,
|
||||||
|
SoundTableId: null,
|
||||||
|
PhysicsScriptTableId: null,
|
||||||
|
Parent: null,
|
||||||
|
Children: null,
|
||||||
|
Scale: null,
|
||||||
|
Friction: null,
|
||||||
|
Elasticity: null,
|
||||||
|
Translucency: null,
|
||||||
|
Velocity: new Vector3(10f, 0f, 0f),
|
||||||
|
Acceleration: null,
|
||||||
|
AngularVelocity: null,
|
||||||
|
DefaultScriptType: null,
|
||||||
|
DefaultScriptIntensity: null,
|
||||||
|
Timestamps: timestamps);
|
||||||
|
return new WorldSession.EntitySpawn(
|
||||||
|
guid,
|
||||||
|
position,
|
||||||
|
0x02000001u,
|
||||||
|
Array.Empty<CreateObject.AnimPartChange>(),
|
||||||
|
Array.Empty<CreateObject.TextureChange>(),
|
||||||
|
Array.Empty<CreateObject.SubPaletteSwap>(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"runtime projectile",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
PhysicsState: physics.RawState,
|
||||||
|
InstanceSequence: instance,
|
||||||
|
MovementSequence: 1,
|
||||||
|
ServerControlSequence: 1,
|
||||||
|
PositionSequence: 1,
|
||||||
|
Physics: physics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue