fix(runtime): preserve hidden setposition collision ownership
This commit is contained in:
parent
9b0f59bd1b
commit
0fbc7a1fb7
8 changed files with 343 additions and 43 deletions
|
|
@ -883,23 +883,17 @@ public sealed class ShadowObjectRegistry
|
||||||
DataCache = DataCache,
|
DataCache = DataCache,
|
||||||
};
|
};
|
||||||
staging.InstallOwnerState(source);
|
staging.InstallOwnerState(source);
|
||||||
if (suspendOwner)
|
staging.CommitSetPosition(
|
||||||
{
|
entityId,
|
||||||
if (!staging.Suspend(entityId))
|
worldPosition,
|
||||||
return false;
|
worldRotation,
|
||||||
}
|
seedCellId,
|
||||||
else
|
worldOffsetX,
|
||||||
{
|
worldOffsetY,
|
||||||
staging.CommitSetPosition(
|
action,
|
||||||
entityId,
|
crossCellIds);
|
||||||
worldPosition,
|
if (suspendOwner && !staging.Suspend(entityId))
|
||||||
worldRotation,
|
return false;
|
||||||
seedCellId,
|
|
||||||
worldOffsetX,
|
|
||||||
worldOffsetY,
|
|
||||||
action,
|
|
||||||
crossCellIds);
|
|
||||||
}
|
|
||||||
if (!staging.TryCaptureOwnerState(
|
if (!staging.TryCaptureOwnerState(
|
||||||
entityId,
|
entityId,
|
||||||
out PreparedShadowOwnerState? replacement)
|
out PreparedShadowOwnerState? replacement)
|
||||||
|
|
|
||||||
|
|
@ -752,6 +752,12 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
transition = Entities.ApplyRawPhysicsState(
|
transition = Entities.ApplyRawPhysicsState(
|
||||||
canonical,
|
canonical,
|
||||||
update.PhysicsState);
|
update.PhysicsState);
|
||||||
|
if (canonical.Key is { } key)
|
||||||
|
{
|
||||||
|
Physics.Engine.ShadowObjects.UpdatePhysicsState(
|
||||||
|
key.LocalEntityId,
|
||||||
|
(uint)canonical.FinalPhysicsState);
|
||||||
|
}
|
||||||
ulong stateVersion = canonical.StateAuthorityVersion;
|
ulong stateVersion = canonical.StateAuthorityVersion;
|
||||||
ulong physicsMutationVersion =
|
ulong physicsMutationVersion =
|
||||||
canonical.PhysicsStateMutationVersion;
|
canonical.PhysicsStateMutationVersion;
|
||||||
|
|
|
||||||
|
|
@ -438,6 +438,12 @@ internal sealed class RuntimeCollisionReportingState : IDisposable
|
||||||
bool reported = false;
|
bool reported = false;
|
||||||
for (int index = 0; index < receipt.Actions.Length; index++)
|
for (int index = 0; index < receipt.Actions.Length; index++)
|
||||||
{
|
{
|
||||||
|
if (IsExactSetPositionBatchOwnerHidden(receipt))
|
||||||
|
{
|
||||||
|
return new(
|
||||||
|
SetPositionCollisionBatchDispatchStatus.Completed,
|
||||||
|
reported);
|
||||||
|
}
|
||||||
if (receipt.Owner.PositionAuthorityVersion
|
if (receipt.Owner.PositionAuthorityVersion
|
||||||
!= receipt.OwnerPositionAuthorityVersion
|
!= receipt.OwnerPositionAuthorityVersion
|
||||||
|| _owners.TryGetValue(
|
|| _owners.TryGetValue(
|
||||||
|
|
@ -463,6 +469,12 @@ internal sealed class RuntimeCollisionReportingState : IDisposable
|
||||||
action, receipt.PhysicsTime, receipt.BatchId);
|
action, receipt.PhysicsTime, receipt.BatchId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsExactSetPositionBatchOwnerHidden(receipt))
|
||||||
|
{
|
||||||
|
return new(
|
||||||
|
SetPositionCollisionBatchDispatchStatus.Completed,
|
||||||
|
reported);
|
||||||
|
}
|
||||||
if (receipt.Owner.PositionAuthorityVersion
|
if (receipt.Owner.PositionAuthorityVersion
|
||||||
!= receipt.OwnerPositionAuthorityVersion
|
!= receipt.OwnerPositionAuthorityVersion
|
||||||
|| _owners.TryGetValue(
|
|| _owners.TryGetValue(
|
||||||
|
|
@ -498,6 +510,14 @@ internal sealed class RuntimeCollisionReportingState : IDisposable
|
||||||
reported);
|
reported);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsExactSetPositionBatchOwnerHidden(
|
||||||
|
in SetPositionCollisionBatchReceipt receipt) =>
|
||||||
|
_entities.IsCurrent(receipt.Owner)
|
||||||
|
&& receipt.Owner.PositionAuthorityVersion
|
||||||
|
== receipt.OwnerPositionAuthorityVersion
|
||||||
|
&& ReferenceEquals(receipt.Owner.PhysicsBody, receipt.OwnerBody)
|
||||||
|
&& (receipt.OwnerBody.State & PhysicsStateFlags.Hidden) != 0;
|
||||||
|
|
||||||
internal bool DiscardSetPositionBatch(
|
internal bool DiscardSetPositionBatch(
|
||||||
in SetPositionCollisionBatchReceipt receipt) =>
|
in SetPositionCollisionBatchReceipt receipt) =>
|
||||||
receipt.IsValid
|
receipt.IsValid
|
||||||
|
|
|
||||||
|
|
@ -3199,8 +3199,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
||||||
&& record.PositionAuthorityVersion == positionAuthorityVersion
|
&& record.PositionAuthorityVersion == positionAuthorityVersion
|
||||||
&& record.SpatialAuthorityVersion == spatialAuthorityVersion
|
&& record.SpatialAuthorityVersion == spatialAuthorityVersion
|
||||||
&& ReferenceEquals(record.PhysicsBody, body)
|
&& ReferenceEquals(record.PhysicsBody, body)
|
||||||
&& body.InWorld
|
&& body.InWorld;
|
||||||
&& (body.State & PhysicsStateFlags.Hidden) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCollisionOwnerMutated(uint ownerId, ulong version)
|
private void OnCollisionOwnerMutated(uint ownerId, ulong version)
|
||||||
|
|
|
||||||
|
|
@ -392,8 +392,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
body,
|
body,
|
||||||
placementCommitVersion,
|
placementCommitVersion,
|
||||||
fullCellId,
|
fullCellId,
|
||||||
requireSpatialRoot: false)
|
requireSpatialRoot: false);
|
||||||
&& owner.IsCollisionReportingEligible(record, body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly RuntimePhysicsState _physics;
|
private readonly RuntimePhysicsState _physics;
|
||||||
|
|
@ -3389,21 +3388,21 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
body,
|
body,
|
||||||
canonicalCommitVersion,
|
canonicalCommitVersion,
|
||||||
committedCellId,
|
committedCellId,
|
||||||
requireSpatialRoot: false)
|
requireSpatialRoot: false))
|
||||||
|| !IsCollisionReportingEligible(record, body))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool reportingCurrent = _physics.HandleSetPositionCollisionReports(
|
bool reportingCurrent = !IsCollisionReportingEligible(record, body)
|
||||||
record,
|
|| _physics.HandleSetPositionCollisionReports(
|
||||||
operation.PositionAuthorityVersion,
|
record,
|
||||||
operation.SpatialAuthorityVersion,
|
operation.PositionAuthorityVersion,
|
||||||
operation.Command.GameTime,
|
operation.SpatialAuthorityVersion,
|
||||||
operation.PreviousContact,
|
operation.Command.GameTime,
|
||||||
operation.PreviousOnWalkable,
|
operation.PreviousContact,
|
||||||
collidedWithEnvironment,
|
operation.PreviousOnWalkable,
|
||||||
collidedObjectIds,
|
collidedWithEnvironment,
|
||||||
out _);
|
collidedObjectIds,
|
||||||
|
out _);
|
||||||
if (!reportingCurrent
|
if (!reportingCurrent
|
||||||
|| !IsCanonicalPlacementCommitCurrent(
|
|| !IsCanonicalPlacementCommitCurrent(
|
||||||
operation,
|
operation,
|
||||||
|
|
@ -3411,8 +3410,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
body,
|
body,
|
||||||
canonicalCommitVersion,
|
canonicalCommitVersion,
|
||||||
committedCellId,
|
committedCellId,
|
||||||
requireSpatialRoot: false)
|
requireSpatialRoot: false))
|
||||||
|| !IsCollisionReportingEligible(record, body))
|
|
||||||
return false;
|
return false;
|
||||||
body.FramesStationaryFall = result.FramesStationaryFall;
|
body.FramesStationaryFall = result.FramesStationaryFall;
|
||||||
if (IsVelocityCurrent(operation))
|
if (IsVelocityCurrent(operation))
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,33 @@ public sealed class ShadowSetPositionCommitTests
|
||||||
Assert.Single(registry.GetObjectsInCell(Cell9)).Position);
|
Assert.Single(registry.GetObjectsInCell(Cell9)).Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PreparedSuspendedMoveRetainsNewCanonicalPosition()
|
||||||
|
{
|
||||||
|
var registry = RegisteredSingle();
|
||||||
|
var moved = new Vector3(36f, 12f, 50f);
|
||||||
|
Assert.True(registry.TryPrepareSetPosition(
|
||||||
|
1u,
|
||||||
|
moved,
|
||||||
|
Quaternion.Identity,
|
||||||
|
Cell9,
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
PhysicsShadowCommitAction.Replace,
|
||||||
|
[Cell9],
|
||||||
|
provenShapeless: false,
|
||||||
|
suspendOwner: true,
|
||||||
|
out var prepared));
|
||||||
|
|
||||||
|
Assert.Single(registry.GetObjectsInCell(Cell1));
|
||||||
|
Assert.True(registry.TryApplySetPosition(prepared!, out _));
|
||||||
|
Assert.Empty(registry.GetObjectsInCell(Cell1));
|
||||||
|
Assert.Empty(registry.GetObjectsInCell(Cell9));
|
||||||
|
Assert.Equal(1, registry.SuspendedRegistrationCount);
|
||||||
|
Assert.Equal(moved, prepared!.OwnerState!.Registration.EntityWorldPos);
|
||||||
|
Assert.Equal(Cell9, prepared.OwnerState.Registration.SeedCellId);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PreparedCrossPrefixDispatchesMembershipThenMutationOnce()
|
public void PreparedCrossPrefixDispatchesMembershipThenMutationOnce()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1883,7 +1883,7 @@ public sealed class RuntimeCollisionReportingStateTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void HitGroundHiddenTransitionCannotResumeCollisionTracking()
|
public void HitGroundHiddenTransitionCommitsPlacementWithoutResumingCollisionTracking()
|
||||||
{
|
{
|
||||||
using var lifetime = Lifetime();
|
using var lifetime = Lifetime();
|
||||||
RuntimeEntityRecord owner = Entity(
|
RuntimeEntityRecord owner = Entity(
|
||||||
|
|
@ -1937,11 +1937,20 @@ public sealed class RuntimeCollisionReportingStateTests
|
||||||
PlacementCommand(owner, new Vector3(16f, 12f, 7f)));
|
PlacementCommand(owner, new Vector3(16f, 12f, 7f)));
|
||||||
|
|
||||||
Assert.True(hidden);
|
Assert.True(hidden);
|
||||||
Assert.Equal(RuntimeSetPositionStatus.Cancelled, outcome.Status);
|
Assert.Equal(
|
||||||
|
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||||
|
outcome.Status);
|
||||||
|
Assert.True(lifetime.Physics.SetPosition.TryPeekProjection(
|
||||||
|
out RuntimePlacementProjectionSnapshot projection));
|
||||||
Assert.True(owner.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden));
|
Assert.True(owner.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden));
|
||||||
|
Assert.Equal(Cell, owner.FullCellId);
|
||||||
|
Assert.Equal(projection.WorldPosition, owner.PhysicsBody!.Position);
|
||||||
|
Assert.True(owner.PhysicsBody.InWorld);
|
||||||
Assert.Empty(observer.Reports);
|
Assert.Empty(observer.Reports);
|
||||||
Assert.Equal(0, lifetime.Physics.CollisionReports.CaptureOwnership()
|
Assert.Equal(0, lifetime.Physics.CollisionReports.CaptureOwnership()
|
||||||
.TrackedObjectCount);
|
.TrackedObjectCount);
|
||||||
|
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||||
|
outcome.Projection));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -2010,7 +2019,7 @@ public sealed class RuntimeCollisionReportingStateTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CollisionReportHiddenTransitionStopsResponseAndShadowReflood()
|
public void CollisionReportHiddenTransitionPreservesCommittedPlacement()
|
||||||
{
|
{
|
||||||
using var lifetime = Lifetime();
|
using var lifetime = Lifetime();
|
||||||
RuntimeEntityRecord owner = Entity(
|
RuntimeEntityRecord owner = Entity(
|
||||||
|
|
@ -2023,7 +2032,6 @@ public sealed class RuntimeCollisionReportingStateTests
|
||||||
RegisterDynamicShadow(lifetime, owner);
|
RegisterDynamicShadow(lifetime, owner);
|
||||||
RegisterDynamicShadow(lifetime, target);
|
RegisterDynamicShadow(lifetime, target);
|
||||||
uint targetId = target.Key!.Value.LocalEntityId;
|
uint targetId = target.Key!.Value.LocalEntityId;
|
||||||
Vector3 initial = owner.PhysicsBody!.Position;
|
|
||||||
lifetime.Physics.Engine.TransitionCellCollisionTestHook =
|
lifetime.Physics.Engine.TransitionCellCollisionTestHook =
|
||||||
(transition, phase, _, _) =>
|
(transition, phase, _, _) =>
|
||||||
{
|
{
|
||||||
|
|
@ -2062,14 +2070,217 @@ public sealed class RuntimeCollisionReportingStateTests
|
||||||
PlacementCommand(owner, new Vector3(18f, 12f, 7f)));
|
PlacementCommand(owner, new Vector3(18f, 12f, 7f)));
|
||||||
|
|
||||||
Assert.True(hidden);
|
Assert.True(hidden);
|
||||||
Assert.Equal(RuntimeSetPositionStatus.Cancelled, outcome.Status);
|
Assert.Equal(
|
||||||
|
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||||
|
outcome.Status);
|
||||||
Assert.True(owner.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden));
|
Assert.True(owner.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden));
|
||||||
Assert.Equal(0, lifetime.Physics.CollisionReports.CaptureOwnership()
|
Assert.Equal(0, lifetime.Physics.CollisionReports.CaptureOwnership()
|
||||||
.TrackedObjectCount);
|
.TrackedObjectCount);
|
||||||
ShadowEntry shadow = Assert.Single(
|
Assert.Equal(Cell, owner.FullCellId);
|
||||||
lifetime.Physics.Engine.ShadowObjects.AllEntriesForDebug(),
|
Assert.Equal(new Vector3(18f, 12f, 7f), owner.PhysicsBody!.Position);
|
||||||
|
ShadowObjectRegistry shadows = lifetime.Physics.Engine.ShadowObjects;
|
||||||
|
uint ownerId = owner.Key!.Value.LocalEntityId;
|
||||||
|
ShadowEntry hiddenShadow = Assert.Single(
|
||||||
|
shadows.AllEntriesForDebug(),
|
||||||
|
entry => entry.EntityId == ownerId);
|
||||||
|
Assert.Equal(owner.PhysicsBody.Position, hiddenShadow.Position);
|
||||||
|
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||||
|
Assert.True(shadows.TryGetCollisionOwner(
|
||||||
|
ownerId,
|
||||||
|
out uint hiddenShadowState,
|
||||||
|
out _));
|
||||||
|
Assert.Equal((uint)owner.FinalPhysicsState, hiddenShadowState);
|
||||||
|
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||||
|
outcome.Projection));
|
||||||
|
|
||||||
|
Assert.True(lifetime.TryApplyState(
|
||||||
|
new SetState.Parsed(
|
||||||
|
owner.ServerGuid,
|
||||||
|
(uint)PhysicsStateFlags.ReportCollisions,
|
||||||
|
owner.Incarnation,
|
||||||
|
StateSequence: 3),
|
||||||
|
acknowledgeProjection: null,
|
||||||
|
out _,
|
||||||
|
out _));
|
||||||
|
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||||
|
ShadowEntry restored = Assert.Single(
|
||||||
|
shadows.AllEntriesForDebug(),
|
||||||
|
entry => entry.EntityId == ownerId);
|
||||||
|
Assert.Equal(owner.PhysicsBody.Position, restored.Position);
|
||||||
|
Assert.Equal((uint)owner.FinalPhysicsState, restored.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PreparedCollisionBatchStopsAfterCallbackHidesOwner()
|
||||||
|
{
|
||||||
|
using var lifetime = Lifetime();
|
||||||
|
RuntimeEntityRecord owner = Entity(
|
||||||
|
lifetime,
|
||||||
|
0x7000207Eu,
|
||||||
|
1,
|
||||||
|
PhysicsStateFlags.ReportCollisions);
|
||||||
|
RuntimeEntityRecord first = Entity(
|
||||||
|
lifetime, 0x7000207Fu, 1, PhysicsStateFlags.None);
|
||||||
|
RuntimeEntityRecord second = Entity(
|
||||||
|
lifetime, 0x70002080u, 1, PhysicsStateFlags.None);
|
||||||
|
RegisterDynamicShadow(lifetime, owner);
|
||||||
|
RegisterDynamicShadow(lifetime, first);
|
||||||
|
RegisterDynamicShadow(lifetime, second);
|
||||||
|
bool hidden = false;
|
||||||
|
var observer = new CollisionObserver(report =>
|
||||||
|
{
|
||||||
|
if (hidden
|
||||||
|
|| report.Kind is not RuntimeCollisionReportKind.ObjectCollision
|
||||||
|
|| report.Recipient != owner.Key)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hidden = lifetime.TryApplyState(
|
||||||
|
new SetState.Parsed(
|
||||||
|
owner.ServerGuid,
|
||||||
|
(uint)(PhysicsStateFlags.ReportCollisions
|
||||||
|
| PhysicsStateFlags.Hidden),
|
||||||
|
owner.Incarnation,
|
||||||
|
StateSequence: 2),
|
||||||
|
acknowledgeProjection: null,
|
||||||
|
out _,
|
||||||
|
out _);
|
||||||
|
});
|
||||||
|
using IDisposable subscription = lifetime.Physics.CollisionReports
|
||||||
|
.Subscribe(observer);
|
||||||
|
Assert.True(lifetime.Physics.CollisionReports.TryPrepareSetPositionBatch(
|
||||||
|
owner,
|
||||||
|
owner.PhysicsBody!,
|
||||||
|
physicsTime: 12d,
|
||||||
|
previousContact: false,
|
||||||
|
previousOnWalkable: false,
|
||||||
|
finalOnWalkable: false,
|
||||||
|
collidedWithEnvironment: false,
|
||||||
|
[
|
||||||
|
first.Key!.Value.LocalEntityId,
|
||||||
|
second.Key!.Value.LocalEntityId,
|
||||||
|
],
|
||||||
|
out var prepared));
|
||||||
|
Assert.True(lifetime.Physics.CollisionReports.TryInstallSetPositionBatch(
|
||||||
|
prepared!, out var receipt));
|
||||||
|
|
||||||
|
SetPositionCollisionBatchDispatchResult result = lifetime.Physics
|
||||||
|
.CollisionReports.DispatchSetPositionBatchResult(receipt);
|
||||||
|
|
||||||
|
Assert.True(hidden);
|
||||||
|
Assert.Equal(SetPositionCollisionBatchDispatchStatus.Completed,
|
||||||
|
result.Status);
|
||||||
|
RuntimeCollisionReport report = Assert.Single(
|
||||||
|
observer.Reports,
|
||||||
|
candidate => candidate.Kind
|
||||||
|
is RuntimeCollisionReportKind.ObjectCollision);
|
||||||
|
Assert.Equal(first.Key, report.Other);
|
||||||
|
RuntimeCollisionReportingOwnershipSnapshot ownership = lifetime.Physics
|
||||||
|
.CollisionReports.CaptureOwnership();
|
||||||
|
Assert.Equal(0, ownership.OwnerCount);
|
||||||
|
Assert.Equal(0, ownership.TrackedObjectCount);
|
||||||
|
Assert.Equal(0, ownership.ReversePeerCount);
|
||||||
|
Assert.Equal(0, ownership.PendingSetPositionDispatchCount);
|
||||||
|
ShadowObjectRegistry shadows = lifetime.Physics.Engine.ShadowObjects;
|
||||||
|
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||||
|
Assert.Contains(
|
||||||
|
shadows.AllEntriesForDebug(),
|
||||||
entry => entry.EntityId == owner.Key!.Value.LocalEntityId);
|
entry => entry.EntityId == owner.Key!.Value.LocalEntityId);
|
||||||
Assert.Equal(initial, shadow.Position);
|
Assert.True(shadows.TryGetCollisionOwner(
|
||||||
|
owner.Key!.Value.LocalEntityId,
|
||||||
|
out uint shadowState,
|
||||||
|
out _));
|
||||||
|
Assert.Equal((uint)owner.FinalPhysicsState, shadowState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NoDrawOwnerStillPublishesItsCollisionReport()
|
||||||
|
{
|
||||||
|
using var lifetime = Lifetime();
|
||||||
|
RuntimeEntityRecord owner = Entity(
|
||||||
|
lifetime,
|
||||||
|
0x7000207Au,
|
||||||
|
1,
|
||||||
|
PhysicsStateFlags.ReportCollisions | PhysicsStateFlags.NoDraw);
|
||||||
|
RuntimeEntityRecord target = Entity(
|
||||||
|
lifetime, 0x7000207Bu, 1, PhysicsStateFlags.None);
|
||||||
|
RegisterDynamicShadow(lifetime, owner);
|
||||||
|
RegisterDynamicShadow(lifetime, target);
|
||||||
|
uint targetId = target.Key!.Value.LocalEntityId;
|
||||||
|
lifetime.Physics.Engine.TransitionCellCollisionTestHook =
|
||||||
|
(transition, phase, _, _) =>
|
||||||
|
{
|
||||||
|
if (phase is TransitionCellCollisionPhase.Environment)
|
||||||
|
{
|
||||||
|
transition.CollisionInfo.CollideObjectGuids.Add(targetId);
|
||||||
|
transition.CollisionInfo.SetCollisionNormal(Vector3.UnitX);
|
||||||
|
}
|
||||||
|
return TransitionState.OK;
|
||||||
|
};
|
||||||
|
var observer = new CollisionObserver();
|
||||||
|
using IDisposable subscription = lifetime.Physics.CollisionReports
|
||||||
|
.Subscribe(observer);
|
||||||
|
|
||||||
|
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
|
||||||
|
owner,
|
||||||
|
owner.PositionAuthorityVersion,
|
||||||
|
PlacementCommand(owner, new Vector3(19f, 12f, 7f)));
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||||
|
outcome.Status);
|
||||||
|
RuntimeCollisionReport report = Assert.Single(
|
||||||
|
observer.Reports,
|
||||||
|
report => report.Kind is RuntimeCollisionReportKind.ObjectCollision);
|
||||||
|
Assert.Equal(owner.Key, report.Recipient);
|
||||||
|
Assert.Equal(target.Key, report.Other);
|
||||||
|
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||||
|
outcome.Projection));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NoDrawOwnerStillAllowsReciprocalPeerCollisionReport()
|
||||||
|
{
|
||||||
|
using var lifetime = Lifetime();
|
||||||
|
RuntimeEntityRecord owner = Entity(
|
||||||
|
lifetime, 0x7000207Cu, 1, PhysicsStateFlags.NoDraw);
|
||||||
|
RuntimeEntityRecord target = Entity(
|
||||||
|
lifetime,
|
||||||
|
0x7000207Du,
|
||||||
|
1,
|
||||||
|
PhysicsStateFlags.ReportCollisions);
|
||||||
|
RegisterDynamicShadow(lifetime, owner);
|
||||||
|
RegisterDynamicShadow(lifetime, target);
|
||||||
|
uint targetId = target.Key!.Value.LocalEntityId;
|
||||||
|
lifetime.Physics.Engine.TransitionCellCollisionTestHook =
|
||||||
|
(transition, phase, _, _) =>
|
||||||
|
{
|
||||||
|
if (phase is TransitionCellCollisionPhase.Environment)
|
||||||
|
{
|
||||||
|
transition.CollisionInfo.CollideObjectGuids.Add(targetId);
|
||||||
|
transition.CollisionInfo.SetCollisionNormal(Vector3.UnitX);
|
||||||
|
}
|
||||||
|
return TransitionState.OK;
|
||||||
|
};
|
||||||
|
var observer = new CollisionObserver();
|
||||||
|
using IDisposable subscription = lifetime.Physics.CollisionReports
|
||||||
|
.Subscribe(observer);
|
||||||
|
|
||||||
|
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
|
||||||
|
owner,
|
||||||
|
owner.PositionAuthorityVersion,
|
||||||
|
PlacementCommand(owner, new Vector3(20f, 12f, 7f)));
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||||
|
outcome.Status);
|
||||||
|
RuntimeCollisionReport report = Assert.Single(
|
||||||
|
observer.Reports,
|
||||||
|
report => report.Kind is RuntimeCollisionReportKind.ObjectCollision);
|
||||||
|
Assert.Equal(target.Key, report.Recipient);
|
||||||
|
Assert.Equal(owner.Key, report.Other);
|
||||||
|
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||||
|
outcome.Projection));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,42 @@ public sealed class RuntimeSetPositionStateTests
|
||||||
Assert.Equal(1, ownership.PreparedMoverCount);
|
Assert.Equal(1, ownership.PreparedMoverCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(PhysicsStateFlags.Hidden)]
|
||||||
|
[InlineData(PhysicsStateFlags.Hidden | PhysicsStateFlags.NoDraw)]
|
||||||
|
public void HiddenObjectCommitsResidenceWhileSuppressingCollisionReports(
|
||||||
|
PhysicsStateFlags suppressedState)
|
||||||
|
{
|
||||||
|
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
|
||||||
|
using var lifetime = new RuntimeEntityObjectLifetime(engine);
|
||||||
|
RuntimeEntityRecord record = CreateRecord(lifetime, 0x70001022u, 1);
|
||||||
|
PhysicsStateFlags state = PhysicsStateFlags.Gravity | suppressedState;
|
||||||
|
PhysicsBody body = AttachBody(lifetime, record, SourceCell, state);
|
||||||
|
var collisionObserver = new CollisionReportObserver();
|
||||||
|
using IDisposable collisionSubscription = lifetime.Physics
|
||||||
|
.CollisionReports.Subscribe(collisionObserver);
|
||||||
|
|
||||||
|
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
|
||||||
|
record,
|
||||||
|
record.PositionAuthorityVersion,
|
||||||
|
Command(Request(SourceCell, new Vector3(12f, 18f, 7f))));
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||||
|
outcome.Status);
|
||||||
|
Assert.True(body.InWorld);
|
||||||
|
Assert.Equal(SourceCell, record.FullCellId);
|
||||||
|
Assert.Equal(new Vector3(12f, 18f, 7f), body.Position);
|
||||||
|
Assert.True(lifetime.Physics.IsSpatialRoot(record));
|
||||||
|
Assert.Empty(collisionObserver.Reports);
|
||||||
|
RuntimeCollisionReportingOwnershipSnapshot reporting = lifetime
|
||||||
|
.Physics.CollisionReports.CaptureOwnership();
|
||||||
|
Assert.Equal(0, reporting.OwnerCount);
|
||||||
|
Assert.Equal(0, reporting.PendingSetPositionDispatchCount);
|
||||||
|
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||||
|
outcome.Projection));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PublicPlacementChannelObservesRetriesAndAcknowledgesExactToken()
|
public void PublicPlacementChannelObservesRetriesAndAcknowledgesExactToken()
|
||||||
{
|
{
|
||||||
|
|
@ -2582,6 +2618,15 @@ public sealed class RuntimeSetPositionStateTests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class CollisionReportObserver
|
||||||
|
: IRuntimeCollisionReportObserver
|
||||||
|
{
|
||||||
|
internal List<RuntimeCollisionReport> Reports { get; } = [];
|
||||||
|
|
||||||
|
public void OnCollisionReport(in RuntimeCollisionReport report) =>
|
||||||
|
Reports.Add(report);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class EntityObserver : IRuntimeEntityObjectObserver
|
private sealed class EntityObserver : IRuntimeEntityObjectObserver
|
||||||
{
|
{
|
||||||
internal List<RuntimeEntityDelta> Deltas { get; } = [];
|
internal List<RuntimeEntityDelta> Deltas { get; } = [];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue