feat(runtime): prepare authored SetPosition movers
This commit is contained in:
parent
237d1184d2
commit
442cb8f97b
8 changed files with 1516 additions and 70 deletions
|
|
@ -320,7 +320,7 @@ public sealed class RuntimeEntityDirectory
|
|||
PhysicsStateFlags state)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.FinalPhysicsState = state;
|
||||
record.SetFinalPhysicsState(state);
|
||||
}
|
||||
|
||||
internal bool StopMissileAfterCollision(
|
||||
|
|
|
|||
|
|
@ -164,6 +164,14 @@ public sealed class RuntimeEntityRecord
|
|||
PhysicsBody.State = FinalPhysicsState;
|
||||
}
|
||||
|
||||
internal void SetFinalPhysicsState(PhysicsStateFlags state)
|
||||
{
|
||||
if (state == FinalPhysicsState)
|
||||
return;
|
||||
PhysicsStateMutationVersion++;
|
||||
FinalPhysicsState = state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail collision reporting clears Missile, AlignPath, and PathClipped
|
||||
/// directly on the live CPhysicsObj. Keep the canonical record and its
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
namespace AcDream.Runtime.Physics;
|
||||
|
||||
internal enum RuntimeSetPositionMoverPreparationStatus
|
||||
{
|
||||
Prepared,
|
||||
RetrySetupUnavailable,
|
||||
RejectedAuthority,
|
||||
InvalidData,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Distinguishes a Setup payload which has not arrived yet from a completed
|
||||
/// lookup whose result is absent. Retail supplies its dummy placement sphere
|
||||
/// only inside <c>CPhysicsObj::SetPosition</c>; an unavailable lookup must not
|
||||
/// manufacture that fallback while asynchronous preparation is still live.
|
||||
/// </summary>
|
||||
internal readonly record struct RuntimeSetPositionMoverSetup(
|
||||
bool IsResolved,
|
||||
uint SetupTableId,
|
||||
FlatSetupCollision? Collision)
|
||||
{
|
||||
internal static RuntimeSetPositionMoverSetup Unavailable => default;
|
||||
|
||||
internal static RuntimeSetPositionMoverSetup ResolvedAbsent =>
|
||||
new(true, 0u, null);
|
||||
|
||||
internal static RuntimeSetPositionMoverSetup Resolved(
|
||||
uint setupTableId,
|
||||
FlatSetupCollision collision) =>
|
||||
new(
|
||||
true,
|
||||
setupTableId != 0u
|
||||
? setupTableId
|
||||
: throw new ArgumentOutOfRangeException(nameof(setupTableId)),
|
||||
collision ?? throw new ArgumentNullException(nameof(collision)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicit, immutable inputs surrounding retail's authored mover shape.
|
||||
/// None of these values are inferred from presentation state.
|
||||
/// </summary>
|
||||
internal readonly record struct RuntimeSetPositionMoverPreparation(
|
||||
RuntimeSetPositionMoverSetup Setup,
|
||||
RuntimeSetPositionOperationKind Kind,
|
||||
double GameTime,
|
||||
PhysicsPlacementClass PlacementClass,
|
||||
PhysicsSetPositionFlags Flags,
|
||||
Vector3 Line = default,
|
||||
float ScatterRadiusX = 0f,
|
||||
float ScatterRadiusY = 0f,
|
||||
uint ScatterAttempts = 0u,
|
||||
float ShadowWorldOffsetX = 0f,
|
||||
float ShadowWorldOffsetY = 0f,
|
||||
RuntimePortalPlacementAuthority Portal = default);
|
||||
|
||||
/// <summary>
|
||||
/// Pure preparation port of the mover inputs consumed by retail
|
||||
/// <c>CPhysicsObj::SetPosition</c> (0x005160C0). It preserves the complete DAT
|
||||
/// sphere order; Core's SPHEREPATH port applies retail's two-sphere cap later.
|
||||
/// </summary>
|
||||
internal static class RuntimeSetPositionMoverPreparer
|
||||
{
|
||||
internal static bool TryBuild(
|
||||
RuntimeEntityRecord record,
|
||||
in CreateObject.ServerPosition acceptedPosition,
|
||||
uint canonicalSetupTableId,
|
||||
RuntimeSetPositionOperationKind acceptedKind,
|
||||
RuntimePortalPlacementAuthority acceptedPortal,
|
||||
ulong velocityAuthorityVersion,
|
||||
in RuntimeSetPositionMoverPreparation preparation,
|
||||
out RuntimeSetPositionCommand command)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
command = default;
|
||||
if (!preparation.Setup.IsResolved
|
||||
|| preparation.Kind != acceptedKind
|
||||
|| preparation.Portal != acceptedPortal
|
||||
|| (canonicalSetupTableId == 0u
|
||||
? preparation.Setup.SetupTableId != 0u
|
||||
|| preparation.Setup.Collision is not null
|
||||
: preparation.Setup.SetupTableId != canonicalSetupTableId
|
||||
|| preparation.Setup.Collision is null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateObject.ServerPosition position = acceptedPosition;
|
||||
Vector3 cellLocal = new(
|
||||
position.PositionX,
|
||||
position.PositionY,
|
||||
position.PositionZ);
|
||||
Vector3 world = new(
|
||||
cellLocal.X + preparation.ShadowWorldOffsetX,
|
||||
cellLocal.Y + preparation.ShadowWorldOffsetY,
|
||||
cellLocal.Z);
|
||||
Quaternion orientation = new(
|
||||
position.RotationX,
|
||||
position.RotationY,
|
||||
position.RotationZ,
|
||||
position.RotationW);
|
||||
|
||||
float scale = record.Snapshot.Physics?.Scale
|
||||
?? record.Snapshot.ObjScale
|
||||
?? 1f;
|
||||
FlatSetupCollision? setup = preparation.Setup.Collision;
|
||||
ImmutableArray<FlatCollisionSphere> spheres = setup?.Spheres
|
||||
?? ImmutableArray<FlatCollisionSphere>.Empty;
|
||||
// CPartArray::GetStepUpHeight/GetStepDownHeight are Setup properties,
|
||||
// not sphere properties. An authored Setup with no collision spheres
|
||||
// still supplies both values; only a genuinely absent Setup takes the
|
||||
// retail dummy path with exact zero steps and no scale multiplication.
|
||||
float stepUp = setup is not null ? setup.StepUpHeight * scale : 0f;
|
||||
float stepDown = setup is not null ? setup.StepDownHeight * scale : 0f;
|
||||
|
||||
EntityCollisionFlags collisionFlags =
|
||||
EntityCollisionFlagsExt.FromPwdBitfield(
|
||||
record.Snapshot.ObjectDescriptionFlags ?? 0u);
|
||||
ObjectInfoState moverFlags = collisionFlags.ToMoverState();
|
||||
if (collisionFlags.HasFlag(EntityCollisionFlags.IsPlayer))
|
||||
moverFlags |= ObjectInfoState.IsPlayer;
|
||||
|
||||
var request = new PhysicsSetPositionRequest(
|
||||
world,
|
||||
orientation,
|
||||
position.LandblockId,
|
||||
cellLocal,
|
||||
spheres,
|
||||
scale,
|
||||
stepUp,
|
||||
stepDown,
|
||||
record.FinalPhysicsState,
|
||||
moverFlags,
|
||||
record.Key?.LocalEntityId ?? 0u,
|
||||
preparation.PlacementClass,
|
||||
preparation.Flags,
|
||||
preparation.Line,
|
||||
preparation.ScatterRadiusX,
|
||||
preparation.ScatterRadiusY,
|
||||
preparation.ScatterAttempts);
|
||||
command = new RuntimeSetPositionCommand(
|
||||
request,
|
||||
preparation.Kind,
|
||||
preparation.GameTime,
|
||||
velocityAuthorityVersion,
|
||||
preparation.ShadowWorldOffsetX,
|
||||
preparation.ShadowWorldOffsetY,
|
||||
preparation.Portal);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
|
|
@ -31,11 +32,18 @@ internal enum RuntimeEntityPlacementStage
|
|||
CancelledAwaitingAcknowledgement,
|
||||
}
|
||||
|
||||
internal enum RuntimeEntityPlacementPreparationKind : byte
|
||||
{
|
||||
LegacyDirect,
|
||||
AuthoredMover,
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeEntityPlacementToken(
|
||||
ulong SessionLifetimeVersion,
|
||||
RuntimeEntityKey Entity,
|
||||
ulong PositionAuthorityVersion,
|
||||
ulong OperationId)
|
||||
ulong OperationId,
|
||||
RuntimeEntityPlacementPreparationKind PreparationKind)
|
||||
{
|
||||
internal bool IsValid => OperationId != 0UL
|
||||
&& Entity.LocalEntityId != 0u
|
||||
|
|
@ -132,14 +140,16 @@ internal readonly record struct RuntimeSetPositionOwnershipSnapshot(
|
|||
int DeferredBucketOrderCount,
|
||||
int UnboundDeferredCellCount,
|
||||
int UnboundDeferredCellOrderCount,
|
||||
int PreparedMoverCount)
|
||||
int PreparedMoverCount,
|
||||
int MoverPreparationAuthorityCount)
|
||||
{
|
||||
internal bool IndexesConsistent =>
|
||||
LostDeadlineCount == LostDeadlineNodeCount
|
||||
&& LostDeadlineCount == LostDeadlineIndexCount
|
||||
&& ExpiredLostCellCount == ExpiredLostCellIndexCount
|
||||
&& DeferredBucketCount == DeferredBucketOrderCount
|
||||
&& UnboundDeferredCellCount == UnboundDeferredCellOrderCount;
|
||||
&& UnboundDeferredCellCount == UnboundDeferredCellOrderCount
|
||||
&& MoverPreparationAuthorityCount <= ActiveOperationCount;
|
||||
|
||||
internal bool IsConverged => ActiveOperationCount == 0
|
||||
&& AwaitingPreparationCount == 0
|
||||
|
|
@ -154,7 +164,8 @@ internal readonly record struct RuntimeSetPositionOwnershipSnapshot(
|
|||
&& DeferredBucketOrderCount == 0
|
||||
&& UnboundDeferredCellCount == 0
|
||||
&& UnboundDeferredCellOrderCount == 0
|
||||
&& PreparedMoverCount == 0;
|
||||
&& PreparedMoverCount == 0
|
||||
&& MoverPreparationAuthorityCount == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -176,6 +187,20 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
uint CellId,
|
||||
ulong CollisionGeneration);
|
||||
|
||||
private readonly record struct MoverPreparationAuthority(
|
||||
ulong OperationId,
|
||||
CreateObject.ServerPosition AcceptedPosition,
|
||||
uint SetupTableId,
|
||||
ulong PositionAuthorityVersion,
|
||||
ulong VelocityAuthorityVersion,
|
||||
ulong StateAuthorityVersion,
|
||||
ulong VectorAuthorityVersion,
|
||||
ulong ObjDescAuthorityVersion,
|
||||
ulong CreateIntegrationVersion,
|
||||
ulong PhysicsStateMutationVersion,
|
||||
bool Prepared,
|
||||
RuntimeSetPositionCommand PreparedCommand);
|
||||
|
||||
private sealed class Operation
|
||||
{
|
||||
internal required RuntimeEntityRecord Record { get; init; }
|
||||
|
|
@ -249,6 +274,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
_lostDeadlineNodeIndex = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, PhysicsSetPositionRequest>
|
||||
_preparedMovers = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, MoverPreparationAuthority>
|
||||
_moverPreparationAuthorities = [];
|
||||
private readonly LinkedList<RuntimeEntityKey> _expiredLostCells = [];
|
||||
private readonly Dictionary<RuntimeEntityKey, LinkedListNode<RuntimeEntityKey>>
|
||||
_expiredLostCellNodes = [];
|
||||
|
|
@ -299,7 +326,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
_deferredBucketOrder.Count,
|
||||
_unboundDeferredByCell.Count,
|
||||
_unboundDeferredCellOrder.Count,
|
||||
_preparedMovers.Count);
|
||||
_preparedMovers.Count,
|
||||
_moverPreparationAuthorities.Count);
|
||||
}
|
||||
|
||||
internal int PendingProjectionCount => _pendingProjection.Count;
|
||||
|
|
@ -345,35 +373,71 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
{
|
||||
EnsureNotDisposed();
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
RuntimeEntityPlacementToken token = BeginAcceptedPlacement(
|
||||
RuntimeEntityPlacementToken token = BeginAcceptedPlacementCore(
|
||||
record,
|
||||
expectedPositionAuthorityVersion,
|
||||
command.Kind,
|
||||
command.Portal);
|
||||
command.Portal,
|
||||
captureMoverPreparationAuthority: false);
|
||||
if (!token.IsValid)
|
||||
{
|
||||
return Rejected(command.Physics);
|
||||
}
|
||||
|
||||
return SubmitPreparedPlacement(token, command);
|
||||
return SubmitPreparedPlacementCore(
|
||||
token,
|
||||
command,
|
||||
allowDirectUnsealed: true);
|
||||
}
|
||||
|
||||
internal RuntimeEntityPlacementToken BeginAcceptedPlacement(
|
||||
RuntimeEntityRecord record,
|
||||
ulong expectedPositionAuthorityVersion,
|
||||
RuntimeSetPositionOperationKind kind,
|
||||
RuntimePortalPlacementAuthority portal = default)
|
||||
RuntimePortalPlacementAuthority portal = default) =>
|
||||
BeginAcceptedPlacementCore(
|
||||
record,
|
||||
expectedPositionAuthorityVersion,
|
||||
kind,
|
||||
portal,
|
||||
captureMoverPreparationAuthority: false);
|
||||
|
||||
internal RuntimeEntityPlacementToken BeginAuthoredPlacement(
|
||||
RuntimeEntityRecord record,
|
||||
ulong expectedPositionAuthorityVersion,
|
||||
RuntimeSetPositionOperationKind kind,
|
||||
RuntimePortalPlacementAuthority portal = default) =>
|
||||
BeginAcceptedPlacementCore(
|
||||
record,
|
||||
expectedPositionAuthorityVersion,
|
||||
kind,
|
||||
portal,
|
||||
captureMoverPreparationAuthority: true);
|
||||
|
||||
private RuntimeEntityPlacementToken BeginAcceptedPlacementCore(
|
||||
RuntimeEntityRecord record,
|
||||
ulong expectedPositionAuthorityVersion,
|
||||
RuntimeSetPositionOperationKind kind,
|
||||
RuntimePortalPlacementAuthority portal,
|
||||
bool captureMoverPreparationAuthority)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
CreateObject.ServerPosition? acceptedPosition =
|
||||
record.Snapshot.Physics?.Position ?? record.Snapshot.Position;
|
||||
if (record.Key is not { } key
|
||||
|| !_entities.IsCurrent(record)
|
||||
|| record.PositionAuthorityVersion
|
||||
!= expectedPositionAuthorityVersion
|
||||
|| (captureMoverPreparationAuthority
|
||||
&& acceptedPosition is null)
|
||||
|| !(portal.IsEmpty
|
||||
|| (portal.IsValid
|
||||
&& kind is RuntimeSetPositionOperationKind
|
||||
.LocalAuthoritative)))
|
||||
.LocalAuthoritative
|
||||
&& acceptedPosition is { } portalPosition
|
||||
&& portal.Projection.DestinationCell
|
||||
== portalPosition.LandblockId)))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
|
@ -382,7 +446,10 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
_entities.SessionLifetimeVersion,
|
||||
key,
|
||||
expectedPositionAuthorityVersion,
|
||||
checked(++_nextOperationId));
|
||||
checked(++_nextOperationId),
|
||||
captureMoverPreparationAuthority
|
||||
? RuntimeEntityPlacementPreparationKind.AuthoredMover
|
||||
: RuntimeEntityPlacementPreparationKind.LegacyDirect);
|
||||
var replacement = new Operation
|
||||
{
|
||||
Record = record,
|
||||
|
|
@ -442,20 +509,95 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
displaced.CollisionGeneration;
|
||||
}
|
||||
_operations[key] = replacement;
|
||||
if (captureMoverPreparationAuthority)
|
||||
{
|
||||
_moverPreparationAuthorities[key] = CapturePreparationAuthority(
|
||||
replacement,
|
||||
acceptedPosition!.Value,
|
||||
prepared: false);
|
||||
}
|
||||
if (discard is { } cancelled)
|
||||
PublishPlacement(cancelled);
|
||||
return IsCurrent(replacement) ? token : default;
|
||||
}
|
||||
|
||||
internal RuntimeSetPositionMoverPreparationStatus PrepareMover(
|
||||
in RuntimeEntityPlacementToken token,
|
||||
in RuntimeSetPositionMoverPreparation preparation,
|
||||
out RuntimeSetPositionCommand command)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
command = default;
|
||||
if (!token.IsValid
|
||||
|| !_operations.TryGetValue(token.Entity, out Operation? operation)
|
||||
|| operation.Token != token
|
||||
|| operation.Stage
|
||||
is not RuntimeEntityPlacementStage.AwaitingPreparation
|
||||
|| !IsCurrent(operation)
|
||||
|| !_moverPreparationAuthorities.TryGetValue(
|
||||
token.Entity,
|
||||
out MoverPreparationAuthority authority)
|
||||
|| authority.OperationId != token.OperationId
|
||||
|| !IsPreparationAuthorityCurrent(operation, authority))
|
||||
{
|
||||
return RuntimeSetPositionMoverPreparationStatus.RejectedAuthority;
|
||||
}
|
||||
|
||||
if (!preparation.Setup.IsResolved)
|
||||
{
|
||||
return RuntimeSetPositionMoverPreparationStatus
|
||||
.RetrySetupUnavailable;
|
||||
}
|
||||
|
||||
if (!RuntimeSetPositionMoverPreparer.TryBuild(
|
||||
operation.Record,
|
||||
authority.AcceptedPosition,
|
||||
authority.SetupTableId,
|
||||
operation.Kind,
|
||||
operation.Portal,
|
||||
authority.VelocityAuthorityVersion,
|
||||
preparation,
|
||||
out command)
|
||||
|| !IsStructurallyValid(command.Physics))
|
||||
{
|
||||
command = default;
|
||||
return RuntimeSetPositionMoverPreparationStatus.InvalidData;
|
||||
}
|
||||
|
||||
_moverPreparationAuthorities[token.Entity] = authority with
|
||||
{
|
||||
Prepared = true,
|
||||
PreparedCommand = command,
|
||||
};
|
||||
|
||||
return RuntimeSetPositionMoverPreparationStatus.Prepared;
|
||||
}
|
||||
|
||||
internal RuntimeSetPositionOutcome SubmitPreparedPlacement(
|
||||
in RuntimeEntityPlacementToken token,
|
||||
in RuntimeSetPositionCommand command)
|
||||
in RuntimeSetPositionCommand command) =>
|
||||
SubmitPreparedPlacementCore(
|
||||
token,
|
||||
command,
|
||||
allowDirectUnsealed: token.PreparationKind
|
||||
is RuntimeEntityPlacementPreparationKind.LegacyDirect);
|
||||
|
||||
private RuntimeSetPositionOutcome SubmitPreparedPlacementCore(
|
||||
in RuntimeEntityPlacementToken token,
|
||||
in RuntimeSetPositionCommand command,
|
||||
bool allowDirectUnsealed)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
Operation? operation = null;
|
||||
bool ownsToken = token.IsValid
|
||||
&& _operations.TryGetValue(token.Entity, out operation)
|
||||
&& operation.Token == token;
|
||||
MoverPreparationAuthority exactAuthority = default;
|
||||
bool hasPreparationAuthority = token.IsValid
|
||||
&& _moverPreparationAuthorities.TryGetValue(
|
||||
token.Entity,
|
||||
out exactAuthority)
|
||||
&& exactAuthority.OperationId == token.OperationId;
|
||||
if (!ownsToken
|
||||
|| operation is null
|
||||
|| operation.Stage
|
||||
|
|
@ -465,6 +607,13 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
|| !double.IsFinite(command.GameTime)
|
||||
|| command.Kind != operation.Kind
|
||||
|| command.Portal != operation.Portal
|
||||
|| (hasPreparationAuthority
|
||||
? !exactAuthority.Prepared
|
||||
|| exactAuthority.PreparedCommand != command
|
||||
|| !IsPreparationAuthorityCurrent(
|
||||
operation,
|
||||
exactAuthority)
|
||||
: !allowDirectUnsealed)
|
||||
|| (command.ExpectedVelocityAuthorityVersion != 0UL
|
||||
&& operation.Record.VelocityAuthorityVersion
|
||||
!= command.ExpectedVelocityAuthorityVersion))
|
||||
|
|
@ -511,6 +660,13 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
};
|
||||
var canonicalCommand = command with { Physics = canonicalRequest };
|
||||
operation.Command = canonicalCommand;
|
||||
if (hasPreparationAuthority)
|
||||
{
|
||||
_moverPreparationAuthorities[token.Entity] = exactAuthority with
|
||||
{
|
||||
PreparedCommand = canonicalCommand,
|
||||
};
|
||||
}
|
||||
|
||||
if (operation.InheritedLostDeadline
|
||||
&& !operation.WithdrawalAcknowledged
|
||||
|
|
@ -657,11 +813,13 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
if (pending.Kind is RuntimePlacementProjectionKind.Place)
|
||||
{
|
||||
operation.Stage = RuntimeEntityPlacementStage.AwaitingCommitAcknowledgement;
|
||||
_moverPreparationAuthorities.Remove(operation.Key);
|
||||
return _operations.Remove(operation.Key);
|
||||
}
|
||||
if (!operation.WakeableLostCell
|
||||
&& !operation.InheritedLostDeadline)
|
||||
{
|
||||
_moverPreparationAuthorities.Remove(operation.Key);
|
||||
return _operations.Remove(operation.Key);
|
||||
}
|
||||
|
||||
|
|
@ -934,7 +1092,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
_entities.SessionLifetimeVersion,
|
||||
key,
|
||||
record.PositionAuthorityVersion,
|
||||
checked(++_nextOperationId)),
|
||||
checked(++_nextOperationId),
|
||||
RuntimeEntityPlacementPreparationKind.AuthoredMover),
|
||||
Key = key,
|
||||
PositionAuthorityVersion = record.PositionAuthorityVersion,
|
||||
SessionLifetimeVersion = _entities.SessionLifetimeVersion,
|
||||
|
|
@ -953,6 +1112,14 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
RequiresPreparation = !hasPrepared,
|
||||
};
|
||||
_operations.Add(key, operation);
|
||||
_moverPreparationAuthorities[key] = CapturePreparationAuthority(
|
||||
operation,
|
||||
ServerPositionFrom(
|
||||
cellId,
|
||||
body.CellPosition.Frame.Origin,
|
||||
body.Orientation),
|
||||
prepared: hasPrepared,
|
||||
command);
|
||||
RuntimeSetPositionOutcome parked = ParkDeferred(
|
||||
operation,
|
||||
result,
|
||||
|
|
@ -1159,6 +1326,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
_lostDeadlineNodes.Clear();
|
||||
_lostDeadlineNodeIndex.Clear();
|
||||
_preparedMovers.Clear();
|
||||
_moverPreparationAuthorities.Clear();
|
||||
_pendingProjection.Clear();
|
||||
_expiredLostCells.Clear();
|
||||
_expiredLostCellNodes.Clear();
|
||||
|
|
@ -1215,6 +1383,22 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
CurrentCellId = null,
|
||||
},
|
||||
};
|
||||
if (_moverPreparationAuthorities.ContainsKey(operation.Key))
|
||||
{
|
||||
RebindPreparedCommand(operation);
|
||||
}
|
||||
else
|
||||
{
|
||||
_moverPreparationAuthorities[operation.Key] =
|
||||
CapturePreparationAuthority(
|
||||
operation,
|
||||
ServerPositionFrom(
|
||||
result.CellId,
|
||||
result.CellLocalPosition,
|
||||
result.Orientation),
|
||||
prepared: true,
|
||||
operation.Command);
|
||||
}
|
||||
IndexDeferred(operation);
|
||||
operation.Stage = operation.RequiresPreparation
|
||||
? RuntimeEntityPlacementStage.AwaitingPreparation
|
||||
|
|
@ -1243,12 +1427,16 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
if (!IsDeferredWakePreparationCurrent(operation))
|
||||
return;
|
||||
|
||||
UnindexDeferred(operation);
|
||||
operation.Command = operation.Command with
|
||||
{
|
||||
GameTime = _physics.PlacementSimulationTime(
|
||||
operation.Command.GameTime),
|
||||
};
|
||||
RebindPreparedCommand(operation);
|
||||
PhysicsSetPositionResult result = IsStructurallyValid(
|
||||
operation.Command.Physics)
|
||||
? _physics.Engine.SetPosition(
|
||||
|
|
@ -1626,7 +1814,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
_entities.SessionLifetimeVersion,
|
||||
key,
|
||||
record.PositionAuthorityVersion,
|
||||
checked(++_nextOperationId)),
|
||||
checked(++_nextOperationId),
|
||||
RuntimeEntityPlacementPreparationKind.LegacyDirect),
|
||||
Key = key,
|
||||
PositionAuthorityVersion = record.PositionAuthorityVersion,
|
||||
SessionLifetimeVersion = _entities.SessionLifetimeVersion,
|
||||
|
|
@ -1673,6 +1862,104 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
|| operation.Record.VelocityAuthorityVersion
|
||||
== operation.SourceVelocityAuthorityVersion;
|
||||
|
||||
private static MoverPreparationAuthority CapturePreparationAuthority(
|
||||
Operation operation,
|
||||
in CreateObject.ServerPosition acceptedPosition,
|
||||
bool prepared,
|
||||
in RuntimeSetPositionCommand preparedCommand = default) => new(
|
||||
operation.Token.OperationId,
|
||||
acceptedPosition,
|
||||
CanonicalSetupTableId(operation.Record),
|
||||
operation.Record.PositionAuthorityVersion,
|
||||
operation.Record.VelocityAuthorityVersion,
|
||||
operation.Record.StateAuthorityVersion,
|
||||
operation.Record.VectorAuthorityVersion,
|
||||
operation.Record.ObjDescAuthorityVersion,
|
||||
operation.Record.CreateIntegrationVersion,
|
||||
operation.Record.PhysicsStateMutationVersion,
|
||||
prepared,
|
||||
preparedCommand);
|
||||
|
||||
private static uint CanonicalSetupTableId(RuntimeEntityRecord record) =>
|
||||
record.Snapshot.Physics?.SetupTableId
|
||||
?? record.Snapshot.SetupTableId
|
||||
?? 0u;
|
||||
|
||||
private static CreateObject.ServerPosition ServerPositionFrom(
|
||||
uint cellId,
|
||||
Vector3 cellLocal,
|
||||
Quaternion orientation) => new(
|
||||
cellId,
|
||||
cellLocal.X,
|
||||
cellLocal.Y,
|
||||
cellLocal.Z,
|
||||
orientation.W,
|
||||
orientation.X,
|
||||
orientation.Y,
|
||||
orientation.Z);
|
||||
|
||||
private static bool IsPreparationAuthorityCurrent(
|
||||
Operation operation,
|
||||
in MoverPreparationAuthority authority) =>
|
||||
operation.Record.PositionAuthorityVersion
|
||||
== authority.PositionAuthorityVersion
|
||||
&& operation.Record.VelocityAuthorityVersion
|
||||
== authority.VelocityAuthorityVersion
|
||||
&& operation.Record.StateAuthorityVersion
|
||||
== authority.StateAuthorityVersion
|
||||
&& operation.Record.VectorAuthorityVersion
|
||||
== authority.VectorAuthorityVersion
|
||||
&& operation.Record.ObjDescAuthorityVersion
|
||||
== authority.ObjDescAuthorityVersion
|
||||
&& operation.Record.CreateIntegrationVersion
|
||||
== authority.CreateIntegrationVersion
|
||||
&& operation.Record.PhysicsStateMutationVersion
|
||||
== authority.PhysicsStateMutationVersion;
|
||||
|
||||
private void RebindPreparedCommand(Operation operation)
|
||||
{
|
||||
if (_moverPreparationAuthorities.TryGetValue(
|
||||
operation.Key,
|
||||
out MoverPreparationAuthority authority)
|
||||
&& authority.OperationId == operation.Token.OperationId
|
||||
&& authority.Prepared)
|
||||
{
|
||||
_moverPreparationAuthorities[operation.Key] = authority with
|
||||
{
|
||||
PreparedCommand = operation.Command,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDeferredWakePreparationCurrent(Operation operation)
|
||||
{
|
||||
if (_moverPreparationAuthorities.TryGetValue(
|
||||
operation.Key,
|
||||
out MoverPreparationAuthority authority)
|
||||
&& authority.OperationId == operation.Token.OperationId
|
||||
&& authority.Prepared
|
||||
&& authority.PreparedCommand == operation.Command
|
||||
&& IsPreparationAuthorityCurrent(operation, authority))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
operation.SourceVelocityAuthorityVersion =
|
||||
operation.Record.VelocityAuthorityVersion;
|
||||
operation.RequiresPreparation = true;
|
||||
operation.Stage = RuntimeEntityPlacementStage.AwaitingPreparation;
|
||||
operation.PreparedCommandAwaitingWithdrawalAck = null;
|
||||
_moverPreparationAuthorities[operation.Key] =
|
||||
CapturePreparationAuthority(
|
||||
operation,
|
||||
ServerPositionFrom(
|
||||
operation.ExactCellId,
|
||||
operation.Result.CellLocalPosition,
|
||||
operation.Result.Orientation),
|
||||
prepared: false);
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void PublishCancellation(
|
||||
in RuntimePlacementCancellationReceipt receipt)
|
||||
{
|
||||
|
|
@ -1712,6 +1999,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
CancelExactLostKey(key);
|
||||
if (!_operations.Remove(key, out Operation? operation))
|
||||
return false;
|
||||
_moverPreparationAuthorities.Remove(key);
|
||||
UnindexDeferred(operation);
|
||||
if (!preserveLostFamily && cancelLostFamily)
|
||||
CancelLostFamilyDeadlines(operation);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue