feat(runtime): seal dormant SetPosition evaluations
This commit is contained in:
parent
22651c823d
commit
99f867f053
16 changed files with 2298 additions and 62 deletions
|
|
@ -704,6 +704,9 @@ public sealed class PlayerMovementController
|
|||
internal bool IsSealedPublicationCandidate => _publicationLifecycle
|
||||
is PlayerMovementControllerPublicationLifecycle.CandidateSealed;
|
||||
|
||||
internal bool IsRuntimeOwnedDormant => _publicationLifecycle
|
||||
is PlayerMovementControllerPublicationLifecycle.RuntimeOwnedDormant;
|
||||
|
||||
internal void SealPublicationCandidate()
|
||||
{
|
||||
if (_publicationLifecycle
|
||||
|
|
|
|||
|
|
@ -14,6 +14,15 @@ internal enum RuntimeLocalPlayerPhysicsPublicationStatus
|
|||
Discarded,
|
||||
}
|
||||
|
||||
internal enum RuntimeLocalPlayerPhysicsActivationStatus
|
||||
{
|
||||
Evaluated,
|
||||
DeferredCell,
|
||||
RejectedPlacement,
|
||||
RejectedAuthority,
|
||||
RejectedToken,
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeLocalPlayerPhysicsPublicationToken(
|
||||
RuntimeEntityKey Entity,
|
||||
RuntimeEntityPlacementToken Placement,
|
||||
|
|
@ -31,6 +40,33 @@ internal readonly record struct RuntimeLocalPlayerPhysicsPublicationToken(
|
|||
&& Entity == Placement.Entity;
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeLocalPlayerPhysicsActivationToken(
|
||||
RuntimeEntityKey Entity,
|
||||
RuntimeEntityPlacementToken Placement,
|
||||
ulong ActivationId,
|
||||
uint LocalPlayerServerGuid,
|
||||
long LocalPlayerIdentityRevision,
|
||||
ulong PhysicsOwnershipEpoch,
|
||||
ulong ObjectClockEpoch,
|
||||
ulong ControllerOwnershipEpoch,
|
||||
ulong SessionGenerationAuthority)
|
||||
{
|
||||
internal bool IsValid => ActivationId != 0UL
|
||||
&& LocalPlayerServerGuid != 0u
|
||||
&& Placement.IsValid
|
||||
&& Entity == Placement.Entity;
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeLocalPlayerPhysicsActivationReceipt(
|
||||
RuntimeLocalPlayerPhysicsActivationToken Token,
|
||||
ulong EvaluationId,
|
||||
RuntimeDormantSetPositionEvaluation Placement)
|
||||
{
|
||||
internal bool IsValid => Token.IsValid
|
||||
&& EvaluationId != 0UL
|
||||
&& Placement.Placement == Token.Placement;
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeLocalPlayerPhysicsCandidateSnapshot(
|
||||
Vector3 Position,
|
||||
Quaternion Orientation,
|
||||
|
|
@ -45,10 +81,13 @@ public readonly record struct
|
|||
bool IsBound,
|
||||
bool IsDisposed,
|
||||
int CandidateCount,
|
||||
int PendingActivationCount,
|
||||
ulong LastPublicationId)
|
||||
{
|
||||
internal bool IsConverged => !IsBound
|
||||
|| (IsDisposed && CandidateCount == 0);
|
||||
|| (IsDisposed
|
||||
&& CandidateCount == 0
|
||||
&& PendingActivationCount == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -74,12 +113,28 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
internal required PhysicsBody Body { get; init; }
|
||||
}
|
||||
|
||||
private sealed class Activation
|
||||
{
|
||||
internal required RuntimeLocalPlayerPhysicsActivationToken Token
|
||||
{ get; init; }
|
||||
internal required RuntimeEntityRecord Record { get; init; }
|
||||
internal required RuntimeSetPositionCommand PlacementCommand
|
||||
{ get; init; }
|
||||
internal required PlayerMovementController Controller { get; init; }
|
||||
internal required PhysicsBody Body { get; init; }
|
||||
internal RuntimeLocalPlayerPhysicsActivationReceipt Receipt
|
||||
{ get; set; }
|
||||
}
|
||||
|
||||
private readonly RuntimeEntityDirectory _entities;
|
||||
private readonly RuntimePhysicsState _physics;
|
||||
private readonly RuntimeLocalPlayerMovementState _movement;
|
||||
private readonly RuntimeLocalPlayerIdentityState _identity;
|
||||
private Candidate? _candidate;
|
||||
private Activation? _activation;
|
||||
private ulong _nextPublicationId;
|
||||
private ulong _nextActivationId;
|
||||
private ulong _nextEvaluationId;
|
||||
private bool _disposed;
|
||||
|
||||
internal RuntimeLocalPlayerPhysicsPublicationState(
|
||||
|
|
@ -161,9 +216,15 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
}
|
||||
|
||||
internal RuntimeLocalPlayerPhysicsPublicationStatus Commit(
|
||||
in RuntimeLocalPlayerPhysicsPublicationToken token)
|
||||
in RuntimeLocalPlayerPhysicsPublicationToken token) =>
|
||||
Commit(token, out _);
|
||||
|
||||
internal RuntimeLocalPlayerPhysicsPublicationStatus Commit(
|
||||
in RuntimeLocalPlayerPhysicsPublicationToken token,
|
||||
out RuntimeLocalPlayerPhysicsActivationToken activationToken)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
activationToken = default;
|
||||
if (!token.IsValid
|
||||
|| _candidate is not { } candidate
|
||||
|| candidate.Token != token)
|
||||
|
|
@ -184,10 +245,104 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
candidate.Record.ObjectClock);
|
||||
candidate.Record.SetPhysicsBody(candidate.Body);
|
||||
_movement.CommitRuntimeOwnedController(candidate.Controller);
|
||||
activationToken = new RuntimeLocalPlayerPhysicsActivationToken(
|
||||
candidate.Token.Entity,
|
||||
candidate.Token.Placement,
|
||||
checked(++_nextActivationId),
|
||||
candidate.Token.LocalPlayerServerGuid,
|
||||
candidate.Token.LocalPlayerIdentityRevision,
|
||||
candidate.Record.PhysicsOwnershipEpoch,
|
||||
candidate.Record.ObjectClockEpoch,
|
||||
_movement.ControllerOwnershipEpoch,
|
||||
_entities.SessionLifetimeVersion);
|
||||
_activation = new Activation
|
||||
{
|
||||
Token = activationToken,
|
||||
Record = candidate.Record,
|
||||
PlacementCommand = candidate.PlacementCommand,
|
||||
Controller = candidate.Controller,
|
||||
Body = candidate.Body,
|
||||
};
|
||||
_candidate = null;
|
||||
return RuntimeLocalPlayerPhysicsPublicationStatus.Committed;
|
||||
}
|
||||
|
||||
internal RuntimeLocalPlayerPhysicsActivationStatus EvaluateActivation(
|
||||
in RuntimeLocalPlayerPhysicsActivationToken token,
|
||||
out RuntimeLocalPlayerPhysicsActivationReceipt receipt)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
receipt = default;
|
||||
if (!token.IsValid
|
||||
|| _activation is not { } activation
|
||||
|| activation.Token != token)
|
||||
{
|
||||
return RuntimeLocalPlayerPhysicsActivationStatus.RejectedToken;
|
||||
}
|
||||
if (!IsActivationCurrent(activation))
|
||||
{
|
||||
DiscardActivation();
|
||||
return RuntimeLocalPlayerPhysicsActivationStatus.RejectedAuthority;
|
||||
}
|
||||
if (!_physics.SetPosition.TryEvaluateDormantLocalActivation(
|
||||
activation.Record,
|
||||
activation.Body,
|
||||
token.Placement,
|
||||
activation.PlacementCommand,
|
||||
out RuntimeDormantSetPositionEvaluation placement))
|
||||
{
|
||||
if (ReferenceEquals(_activation, activation)
|
||||
&& !IsActivationCurrent(activation))
|
||||
{
|
||||
DiscardActivation();
|
||||
}
|
||||
return RuntimeLocalPlayerPhysicsActivationStatus.RejectedAuthority;
|
||||
}
|
||||
|
||||
receipt = new RuntimeLocalPlayerPhysicsActivationReceipt(
|
||||
token,
|
||||
checked(++_nextEvaluationId),
|
||||
placement);
|
||||
activation.Receipt = receipt;
|
||||
if (placement.Result.IsDeferred)
|
||||
return RuntimeLocalPlayerPhysicsActivationStatus.DeferredCell;
|
||||
return placement.Result.IsCommitted
|
||||
? RuntimeLocalPlayerPhysicsActivationStatus.Evaluated
|
||||
: RuntimeLocalPlayerPhysicsActivationStatus.RejectedPlacement;
|
||||
}
|
||||
|
||||
internal bool IsEvaluationCurrent(
|
||||
in RuntimeLocalPlayerPhysicsActivationReceipt receipt)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (!receipt.IsValid
|
||||
|| _activation is not { } activation
|
||||
|| activation.Token != receipt.Token
|
||||
|| activation.Receipt != receipt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return IsActivationCurrent(activation)
|
||||
&& _physics.SetPosition.IsDormantLocalEvaluationCurrent(
|
||||
activation.Record,
|
||||
activation.Body,
|
||||
receipt.Placement);
|
||||
}
|
||||
|
||||
internal bool DiscardActivation(
|
||||
in RuntimeLocalPlayerPhysicsActivationToken token)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (!token.IsValid
|
||||
|| _activation is not { } activation
|
||||
|| activation.Token != token)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
DiscardActivation();
|
||||
return true;
|
||||
}
|
||||
|
||||
internal RuntimeLocalPlayerPhysicsPublicationStatus Discard(
|
||||
in RuntimeLocalPlayerPhysicsPublicationToken token)
|
||||
{
|
||||
|
|
@ -231,12 +386,14 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
IsBound: true,
|
||||
_disposed,
|
||||
_candidate is null ? 0 : 1,
|
||||
_activation is null ? 0 : 1,
|
||||
_nextPublicationId);
|
||||
|
||||
internal void ResetSession()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
DiscardCurrent();
|
||||
DiscardActivation();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -244,6 +401,7 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
if (_disposed)
|
||||
return;
|
||||
DiscardCurrent();
|
||||
DiscardActivation();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
|
|
@ -251,7 +409,8 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
RuntimeEntityRecord record,
|
||||
in RuntimeEntityPlacementToken placement,
|
||||
in RuntimeSetPositionCommand command) =>
|
||||
record.Key is { } key
|
||||
_activation is null
|
||||
&& record.Key is { } key
|
||||
&& key == placement.Entity
|
||||
&& _entities.IsCurrent(record)
|
||||
&& !record.DeleteAcceptedForTeardown
|
||||
|
|
@ -276,7 +435,8 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
command);
|
||||
|
||||
private bool IsCurrent(Candidate candidate) =>
|
||||
candidate.Controller.IsSealedPublicationCandidate
|
||||
_activation is null
|
||||
&& candidate.Controller.IsSealedPublicationCandidate
|
||||
&& candidate.Controller.OwnsPhysicsBody(candidate.Body)
|
||||
&& _entities.SessionLifetimeVersion
|
||||
== candidate.Token.SessionGenerationAuthority
|
||||
|
|
@ -313,4 +473,55 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
_candidate = null;
|
||||
candidate?.Controller.DiscardRuntimeCandidate();
|
||||
}
|
||||
|
||||
private bool IsActivationCurrent(Activation activation) =>
|
||||
activation.Controller.IsRuntimeOwnedDormant
|
||||
&& activation.Controller.OwnsPhysicsBody(activation.Body)
|
||||
&& _entities.SessionLifetimeVersion
|
||||
== activation.Token.SessionGenerationAuthority
|
||||
&& _entities.IsCurrent(activation.Record)
|
||||
&& activation.Record.Key == activation.Token.Entity
|
||||
&& !_identity.IsDisposed
|
||||
&& _identity.ServerGuid == activation.Token.LocalPlayerServerGuid
|
||||
&& _identity.ServerGuid == activation.Record.ServerGuid
|
||||
&& _identity.Revision == activation.Token.LocalPlayerIdentityRevision
|
||||
&& activation.Record.PhysicsOwnershipEpoch
|
||||
== activation.Token.PhysicsOwnershipEpoch
|
||||
&& activation.Record.ObjectClockEpoch
|
||||
== activation.Token.ObjectClockEpoch
|
||||
&& _movement.CanCommitRuntimeOwnedController(
|
||||
activation.Token.ControllerOwnershipEpoch,
|
||||
activation.Controller)
|
||||
&& ReferenceEquals(activation.Record.PhysicsBody, activation.Body)
|
||||
&& activation.Record.PhysicsHost is null
|
||||
&& activation.Record.RemoteMotion is null
|
||||
&& activation.Record.Projectile is null
|
||||
&& !activation.Record.PhysicsBodyAcquisitionInProgress
|
||||
&& !activation.Record.RemoteMotionBindingInProgress
|
||||
&& !activation.Record.ProjectileBindingInProgress
|
||||
&& !activation.Record.RequiresRemotePlacementRuntime
|
||||
&& !activation.Record.DeleteAcceptedForTeardown
|
||||
&& _physics.SetPosition.IsExactPreparedPlacementCurrent(
|
||||
activation.Record,
|
||||
activation.Token.Placement,
|
||||
activation.PlacementCommand);
|
||||
|
||||
private void DiscardActivation()
|
||||
{
|
||||
Activation? activation = _activation;
|
||||
_activation = null;
|
||||
if (activation is not null
|
||||
&& _entities.IsCurrent(activation.Record)
|
||||
&& ReferenceEquals(
|
||||
activation.Record.PhysicsBody,
|
||||
activation.Body))
|
||||
{
|
||||
_entities.SetPhysicsBody(activation.Record, null);
|
||||
}
|
||||
if (activation is not null
|
||||
&& ReferenceEquals(_movement.Controller, activation.Controller))
|
||||
{
|
||||
_movement.Controller = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Collections.Immutable;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
||||
|
|
@ -1042,6 +1044,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
private bool _suppressCollisionOwnerJournal;
|
||||
private long _nextCollisionPreparationSequence;
|
||||
private long _latestCollisionPreparationStartSequence;
|
||||
private ulong _collisionWorldAuthority = 1UL;
|
||||
private readonly List<Action<RuntimeCollisionGenerationCommitted>>
|
||||
_collisionGenerationCommittedObservers = new();
|
||||
private bool _disposed;
|
||||
|
|
@ -1898,6 +1901,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
EnsureNotDisposed();
|
||||
EnsureCollisionMutationThread();
|
||||
uint canonical = CanonicalLandblock(landblockId);
|
||||
AdvanceCollisionWorldAuthority();
|
||||
if (_collisionAdmissions.Remove(
|
||||
canonical,
|
||||
out RuntimeCollisionAdmission? superseded))
|
||||
|
|
@ -2013,6 +2017,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
admission.Generation);
|
||||
_collisionGenerations[admission.LandblockId] = checked(
|
||||
admission.Generation + 1UL);
|
||||
AdvanceCollisionWorldAuthority();
|
||||
}
|
||||
TrimCollisionOwnerJournal();
|
||||
}
|
||||
|
|
@ -2226,6 +2231,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
try
|
||||
{
|
||||
Engine.CommitLandblockReplacement(replacement);
|
||||
AdvanceCollisionWorldAuthority();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -2496,6 +2502,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
|
||||
private void InvalidateCollisionAdmission(uint landblockId)
|
||||
{
|
||||
AdvanceCollisionWorldAuthority();
|
||||
ulong currentGeneration = _collisionGenerations.TryGetValue(
|
||||
landblockId,
|
||||
out ulong current)
|
||||
|
|
@ -2540,6 +2547,130 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
: 1UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact collision-prefix generation authority used by private
|
||||
/// SetPosition evaluations. Beginning a replacement generation advances
|
||||
/// this authority immediately, so a receipt cannot survive a prepared
|
||||
/// world replacement and later observe different collision rows.
|
||||
/// </summary>
|
||||
internal ulong CollisionGenerationAuthority(uint exactCellId)
|
||||
{
|
||||
uint landblockId = CanonicalLandblock(exactCellId);
|
||||
return landblockId != 0u
|
||||
&& _collisionGenerations.TryGetValue(
|
||||
landblockId,
|
||||
out ulong generation)
|
||||
? generation
|
||||
: 0UL;
|
||||
}
|
||||
|
||||
internal ulong CollisionWorldAuthority => _collisionWorldAuthority;
|
||||
|
||||
internal ulong ShadowWorldAuthority =>
|
||||
Engine.ShadowObjects.MutationRevision;
|
||||
|
||||
internal ClientObjectTable? ObjectTable => Engine.Objects;
|
||||
|
||||
internal ulong ObjectTableBindingAuthority =>
|
||||
Engine.ObjectsBindingRevision;
|
||||
|
||||
internal ulong ObjectTableAuthority =>
|
||||
ObjectTable?.MutationRevision ?? 0UL;
|
||||
|
||||
private void AdvanceCollisionWorldAuthority() =>
|
||||
_collisionWorldAuthority = checked(_collisionWorldAuthority + 1UL);
|
||||
|
||||
internal bool TrySealCollisionEvaluationAuthority(
|
||||
in PhysicsSetPositionResult result,
|
||||
ulong expectedCollisionWorldAuthority,
|
||||
ulong expectedShadowWorldAuthority,
|
||||
ClientObjectTable? expectedObjectTable,
|
||||
ulong expectedObjectTableBindingAuthority,
|
||||
ulong expectedObjectTableAuthority,
|
||||
out RuntimeCollisionEvaluationAuthority authority)
|
||||
{
|
||||
authority = default;
|
||||
if (_collisionWorldAuthority != expectedCollisionWorldAuthority
|
||||
|| ShadowWorldAuthority != expectedShadowWorldAuthority
|
||||
|| !ReferenceEquals(ObjectTable, expectedObjectTable)
|
||||
|| ObjectTableBindingAuthority
|
||||
!= expectedObjectTableBindingAuthority
|
||||
|| ObjectTableAuthority != expectedObjectTableAuthority)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var prefixes = new HashSet<uint>();
|
||||
var authorities = ImmutableArray.CreateBuilder<
|
||||
RuntimeCollisionGenerationAuthority>();
|
||||
void Add(uint cellId)
|
||||
{
|
||||
uint landblock = CanonicalLandblock(cellId);
|
||||
if (landblock == 0u || !prefixes.Add(landblock))
|
||||
return;
|
||||
authorities.Add(new RuntimeCollisionGenerationAuthority(
|
||||
landblock,
|
||||
CollisionGenerationAuthority(landblock)));
|
||||
}
|
||||
|
||||
Add(result.CellId);
|
||||
if (!result.QueriedCellIds.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (uint cellId in result.QueriedCellIds)
|
||||
Add(cellId);
|
||||
}
|
||||
foreach (uint prefix in prefixes)
|
||||
{
|
||||
if (_collisionAdmissions.ContainsKey(prefix))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_collisionWorldAuthority != expectedCollisionWorldAuthority
|
||||
|| ShadowWorldAuthority != expectedShadowWorldAuthority
|
||||
|| !ReferenceEquals(ObjectTable, expectedObjectTable)
|
||||
|| ObjectTableBindingAuthority
|
||||
!= expectedObjectTableBindingAuthority
|
||||
|| ObjectTableAuthority != expectedObjectTableAuthority)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
authority = new RuntimeCollisionEvaluationAuthority(
|
||||
expectedCollisionWorldAuthority,
|
||||
expectedShadowWorldAuthority,
|
||||
expectedObjectTable,
|
||||
expectedObjectTableBindingAuthority,
|
||||
expectedObjectTableAuthority,
|
||||
authorities.ToImmutable());
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool IsCollisionEvaluationAuthorityCurrent(
|
||||
in RuntimeCollisionEvaluationAuthority authority)
|
||||
{
|
||||
if (!authority.IsValid
|
||||
|| _collisionWorldAuthority != authority.CollisionWorldAuthority
|
||||
|| ShadowWorldAuthority != authority.ShadowWorldAuthority
|
||||
|| !ReferenceEquals(ObjectTable, authority.ObjectTable)
|
||||
|| ObjectTableBindingAuthority
|
||||
!= authority.ObjectTableBindingAuthority
|
||||
|| ObjectTableAuthority != authority.ObjectTableAuthority)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (RuntimeCollisionGenerationAuthority generation
|
||||
in authority.Generations)
|
||||
{
|
||||
if (generation.LandblockId == 0u
|
||||
|| _collisionAdmissions.ContainsKey(generation.LandblockId)
|
||||
|| CollisionGenerationAuthority(generation.LandblockId)
|
||||
!= generation.Generation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool HandleSetPositionCollisions(
|
||||
RuntimeEntityRecord record,
|
||||
ulong positionAuthorityVersion,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Entities;
|
||||
|
|
@ -82,6 +83,32 @@ internal readonly record struct RuntimeSetPositionCommand(
|
|||
float ShadowWorldOffsetY = 0f,
|
||||
RuntimePortalPlacementAuthority Portal = default);
|
||||
|
||||
internal readonly record struct RuntimeCollisionGenerationAuthority(
|
||||
uint LandblockId,
|
||||
ulong Generation);
|
||||
|
||||
internal readonly record struct RuntimeCollisionEvaluationAuthority(
|
||||
ulong CollisionWorldAuthority,
|
||||
ulong ShadowWorldAuthority,
|
||||
ClientObjectTable? ObjectTable,
|
||||
ulong ObjectTableBindingAuthority,
|
||||
ulong ObjectTableAuthority,
|
||||
ImmutableArray<RuntimeCollisionGenerationAuthority> Generations)
|
||||
{
|
||||
internal bool IsValid => CollisionWorldAuthority != 0UL
|
||||
&& !Generations.IsDefault;
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeDormantSetPositionEvaluation(
|
||||
RuntimeEntityPlacementToken Placement,
|
||||
RuntimeSetPositionCommand Command,
|
||||
PhysicsSetPositionResult Result,
|
||||
RuntimeCollisionEvaluationAuthority CollisionAuthority)
|
||||
{
|
||||
internal bool IsValid => Placement.IsValid
|
||||
&& CollisionAuthority.IsValid;
|
||||
}
|
||||
|
||||
public readonly record struct RuntimePlacementProjectionToken(
|
||||
ulong Sequence,
|
||||
ulong Revision,
|
||||
|
|
@ -597,6 +624,98 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
&& IsPreparationAuthorityCurrent(operation, authority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the exact authored local-player placement without mutating
|
||||
/// the canonical dormant body or any Runtime ownership index. Core's
|
||||
/// SetPosition transaction is pure; collision reporting is deliberately
|
||||
/// omitted until the later atomic activation commit.
|
||||
/// </summary>
|
||||
internal bool TryEvaluateDormantLocalActivation(
|
||||
RuntimeEntityRecord record,
|
||||
PhysicsBody body,
|
||||
in RuntimeEntityPlacementToken token,
|
||||
in RuntimeSetPositionCommand command,
|
||||
out RuntimeDormantSetPositionEvaluation evaluation)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
ArgumentNullException.ThrowIfNull(body);
|
||||
evaluation = default;
|
||||
if (!IsExactDormantLocalActivationCurrent(
|
||||
record,
|
||||
body,
|
||||
token,
|
||||
command,
|
||||
out Operation? operation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PhysicsSetPositionRequest canonicalRequest = command.Physics with
|
||||
{
|
||||
MoverPhysicsState = record.FinalPhysicsState,
|
||||
MovingEntityId = token.Entity.LocalEntityId,
|
||||
CurrentCellId = null,
|
||||
};
|
||||
if (!IsStructurallyValid(canonicalRequest))
|
||||
return false;
|
||||
|
||||
var canonicalCommand = command with { Physics = canonicalRequest };
|
||||
ulong collisionWorldAuthority = _physics.CollisionWorldAuthority;
|
||||
ulong shadowWorldAuthority = _physics.ShadowWorldAuthority;
|
||||
ClientObjectTable? objectTable = _physics.ObjectTable;
|
||||
ulong objectTableBindingAuthority =
|
||||
_physics.ObjectTableBindingAuthority;
|
||||
ulong objectTableAuthority = objectTable?.MutationRevision ?? 0UL;
|
||||
PhysicsSetPositionResult result = _physics.Engine.SetPosition(
|
||||
canonicalRequest,
|
||||
handleCollisions: null);
|
||||
if (!IsExactDormantLocalActivationCurrent(
|
||||
record,
|
||||
body,
|
||||
token,
|
||||
command,
|
||||
out Operation? current)
|
||||
|| !ReferenceEquals(current, operation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_physics.TrySealCollisionEvaluationAuthority(
|
||||
result,
|
||||
collisionWorldAuthority,
|
||||
shadowWorldAuthority,
|
||||
objectTable,
|
||||
objectTableBindingAuthority,
|
||||
objectTableAuthority,
|
||||
out RuntimeCollisionEvaluationAuthority collisionAuthority))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
evaluation = new RuntimeDormantSetPositionEvaluation(
|
||||
token,
|
||||
canonicalCommand,
|
||||
result,
|
||||
collisionAuthority);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool IsDormantLocalEvaluationCurrent(
|
||||
RuntimeEntityRecord record,
|
||||
PhysicsBody body,
|
||||
in RuntimeDormantSetPositionEvaluation evaluation) =>
|
||||
evaluation.IsValid
|
||||
&& IsExactDormantLocalActivationCurrent(
|
||||
record,
|
||||
body,
|
||||
evaluation.Placement,
|
||||
evaluation.Command,
|
||||
out _,
|
||||
allowCanonicalCommand: true)
|
||||
&& _physics.IsCollisionEvaluationAuthorityCurrent(
|
||||
evaluation.CollisionAuthority);
|
||||
|
||||
internal RuntimeSetPositionOutcome SubmitPreparedPlacement(
|
||||
in RuntimeEntityPlacementToken token,
|
||||
in RuntimeSetPositionCommand command) =>
|
||||
|
|
@ -1881,6 +2000,69 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
&& operation.Record.PlacementCommitVersion
|
||||
== operation.PlacementCommitVersion;
|
||||
|
||||
private bool IsExactDormantLocalActivationCurrent(
|
||||
RuntimeEntityRecord record,
|
||||
PhysicsBody body,
|
||||
in RuntimeEntityPlacementToken token,
|
||||
in RuntimeSetPositionCommand command,
|
||||
out Operation? operation,
|
||||
bool allowCanonicalCommand = false)
|
||||
{
|
||||
operation = null;
|
||||
if (!token.IsValid
|
||||
|| token.Entity != record.Key
|
||||
|| token.PreparationKind
|
||||
is not RuntimeEntityPlacementPreparationKind.AuthoredMover
|
||||
|| command.Kind is not (RuntimeSetPositionOperationKind.InitialLogin
|
||||
or RuntimeSetPositionOperationKind.LocalAuthoritative)
|
||||
|| command.Portal != default
|
||||
&& !command.Portal.IsValid
|
||||
|| !_operations.TryGetValue(token.Entity, out operation)
|
||||
|| operation.Token != token
|
||||
|| operation.Stage
|
||||
is not RuntimeEntityPlacementStage.AwaitingPreparation
|
||||
|| !ReferenceEquals(operation.Record, record)
|
||||
|| !IsCurrent(operation)
|
||||
|| !ReferenceEquals(record.PhysicsBody, body)
|
||||
|| body.InWorld
|
||||
|| (body.TransientState & TransientStateFlags.Active) != 0
|
||||
|| record.PhysicsHost is not null
|
||||
|| record.RemoteMotion is not null
|
||||
|| record.Projectile is not null
|
||||
|| record.PhysicsBodyAcquisitionInProgress
|
||||
|| record.RemoteMotionBindingInProgress
|
||||
|| record.ProjectileBindingInProgress
|
||||
|| record.RequiresRemotePlacementRuntime
|
||||
|| record.DeleteAcceptedForTeardown
|
||||
|| _physics.IsSpatialRoot(record)
|
||||
|| !_moverPreparationAuthorities.TryGetValue(
|
||||
token.Entity,
|
||||
out MoverPreparationAuthority authority)
|
||||
|| authority.OperationId != token.OperationId
|
||||
|| !authority.Prepared
|
||||
|| !IsPreparationAuthorityCurrent(operation, authority))
|
||||
{
|
||||
operation = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (authority.PreparedCommand == command)
|
||||
return true;
|
||||
if (!allowCanonicalCommand)
|
||||
return false;
|
||||
|
||||
RuntimeSetPositionCommand authored = authority.PreparedCommand;
|
||||
return authored with
|
||||
{
|
||||
Physics = authored.Physics with
|
||||
{
|
||||
MoverPhysicsState = record.FinalPhysicsState,
|
||||
MovingEntityId = token.Entity.LocalEntityId,
|
||||
CurrentCellId = null,
|
||||
},
|
||||
} == command;
|
||||
}
|
||||
|
||||
private bool IsVelocityCurrent(Operation operation) =>
|
||||
operation.SourceVelocityAuthorityVersion == 0UL
|
||||
|| operation.Record.VelocityAuthorityVersion
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue