fix(physics): seal collision generations before activation
This commit is contained in:
parent
be94bc9b06
commit
d94145e6b8
15 changed files with 1556 additions and 410 deletions
|
|
@ -976,6 +976,43 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
new Dictionary<uint, GfxObj>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReloadedSeamAtomicallyRestoresAdjacentStaticAndClearsRepairMarker()
|
||||
{
|
||||
var fixture = Fixture();
|
||||
CacheCylinderSetup(fixture.Cache);
|
||||
WorldEntity neighbor = CylinderEntity(
|
||||
0x80AAB401u,
|
||||
new Vector3(192.25f, 12f, 0f));
|
||||
Publish(fixture.Publisher, Build(FirstLandblock));
|
||||
Publish(fixture.Publisher, Build(AdjacentLandblock, [neighbor]));
|
||||
Assert.True(fixture.Engine.ShadowObjects.HasOwnerRowsInLandblock(
|
||||
neighbor.Id,
|
||||
FirstLandblock));
|
||||
|
||||
fixture.Publisher.RemoveLandblock(FirstLandblock);
|
||||
|
||||
Assert.Equal(1, fixture.Engine.ShadowObjects.WithdrawnPrefixMarkerCount);
|
||||
Assert.False(fixture.Engine.ShadowObjects.HasOwnerRowsInLandblock(
|
||||
neighbor.Id,
|
||||
FirstLandblock));
|
||||
LandblockPhysicsPublication pending = Begin(
|
||||
fixture.Publisher,
|
||||
Build(FirstLandblock));
|
||||
Assert.Equal(1, fixture.Engine.ShadowObjects.WithdrawnPrefixMarkerCount);
|
||||
|
||||
fixture.Publisher.CompletePublication(pending);
|
||||
|
||||
Assert.Equal(0, fixture.Engine.ShadowObjects.WithdrawnPrefixMarkerCount);
|
||||
Assert.True(fixture.Engine.ShadowObjects.HasOwnerRowsInLandblock(
|
||||
neighbor.Id,
|
||||
FirstLandblock));
|
||||
Assert.True(fixture.Engine.ShadowObjects.HasOwnerRowsInLandblock(
|
||||
neighbor.Id,
|
||||
AdjacentLandblock));
|
||||
Assert.Equal(1, fixture.Engine.ShadowObjects.RetainedRegistrationCount);
|
||||
}
|
||||
|
||||
private static LandblockCollisionBuild FlatCellClosure(
|
||||
PhysicsDatBundle bundle,
|
||||
uint envCellId)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using AcDream.Headless.Platform;
|
|||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Physics;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
|
|
@ -373,6 +374,72 @@ public sealed class HeadlessSessionHostTests
|
|||
Assert.Equal(2, collision.CenterCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollisionTransactionCancelsPostAdmissionFaultWithoutWithdrawingActiveWorld()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimePhysicsState physics = lifetime.Physics;
|
||||
const uint landblockId = 0xA9B4FFFFu;
|
||||
_ = HeadlessCollisionGenerationTransaction.Execute(
|
||||
physics,
|
||||
landblockId,
|
||||
afterAdmission: null,
|
||||
(admission, prepared) =>
|
||||
physics.StageCollisionAssets(
|
||||
admission,
|
||||
prepared,
|
||||
CollisionAssets(landblockId, 10f)));
|
||||
|
||||
Assert.Throws<FixtureCollisionPublicationException>(() =>
|
||||
HeadlessCollisionGenerationTransaction.Execute(
|
||||
physics,
|
||||
landblockId,
|
||||
_ => throw new FixtureCollisionPublicationException(),
|
||||
(_, _) => throw new InvalidOperationException(
|
||||
"Staging must not run after the injected admission fault.")));
|
||||
|
||||
Assert.Equal(10f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
|
||||
Assert.Equal(1, ownership.LandblockCount);
|
||||
Assert.Equal(0, ownership.CollisionAdmissionCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollisionTransactionCancelsStagingFaultWithoutWithdrawingActiveWorld()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimePhysicsState physics = lifetime.Physics;
|
||||
const uint landblockId = 0xA9B4FFFFu;
|
||||
_ = HeadlessCollisionGenerationTransaction.Execute(
|
||||
physics,
|
||||
landblockId,
|
||||
afterAdmission: null,
|
||||
(admission, prepared) =>
|
||||
physics.StageCollisionAssets(
|
||||
admission,
|
||||
prepared,
|
||||
CollisionAssets(landblockId, 10f)));
|
||||
|
||||
Assert.Throws<FixtureCollisionPublicationException>(() =>
|
||||
HeadlessCollisionGenerationTransaction.Execute(
|
||||
physics,
|
||||
landblockId,
|
||||
afterAdmission: null,
|
||||
(admission, prepared) =>
|
||||
{
|
||||
physics.StageCollisionAssets(
|
||||
admission,
|
||||
prepared,
|
||||
CollisionAssets(landblockId, 25f));
|
||||
throw new FixtureCollisionPublicationException();
|
||||
}));
|
||||
|
||||
Assert.Equal(10f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
|
||||
Assert.Equal(1, ownership.LandblockCount);
|
||||
Assert.Equal(0, ownership.CollisionAdmissionCount);
|
||||
}
|
||||
|
||||
private static HeadlessSessionDescriptor Descriptor(
|
||||
HeadlessCredentialProviderKind provider =
|
||||
HeadlessCredentialProviderKind.Environment,
|
||||
|
|
@ -422,6 +489,25 @@ public sealed class HeadlessSessionHostTests
|
|||
runtime.MovementOwner.Controller = controller;
|
||||
}
|
||||
|
||||
private static RuntimeLandblockCollisionAssets CollisionAssets(
|
||||
uint landblockId,
|
||||
float terrainHeight)
|
||||
{
|
||||
var heights = new byte[81];
|
||||
var table = new float[256];
|
||||
table[0] = terrainHeight;
|
||||
return new RuntimeLandblockCollisionAssets(
|
||||
landblockId,
|
||||
new TerrainSurface(heights, table),
|
||||
Array.Empty<CellSurface>(),
|
||||
Array.Empty<PortalPlane>(),
|
||||
0f,
|
||||
0f,
|
||||
0u);
|
||||
}
|
||||
|
||||
private sealed class FixtureCollisionPublicationException : Exception;
|
||||
|
||||
private static void AddFlatLandblock(PhysicsEngine engine)
|
||||
{
|
||||
var heights = new byte[81];
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ public sealed class RuntimePhysicsStateTests
|
|||
prepared,
|
||||
CollisionAssets(0xA9B4FFFFu));
|
||||
RuntimeCollisionGenerationCommit commit =
|
||||
first.Physics.CommitCollisionGeneration(newer, prepared);
|
||||
CommitPrepared(first.Physics, newer, prepared);
|
||||
RuntimeCollisionAcknowledgement completed = commit.Acknowledgement;
|
||||
|
||||
Assert.True(commit.Committed);
|
||||
|
|
@ -332,7 +332,7 @@ public sealed class RuntimePhysicsStateTests
|
|||
0,
|
||||
first.Physics.CaptureOwnership().CollisionAdmissionCount);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
first.Physics.CommitCollisionGeneration(newer, prepared));
|
||||
CommitPrepared(first.Physics, newer, prepared));
|
||||
|
||||
RuntimeCollisionAcknowledgement withdrawn =
|
||||
first.Physics.WithdrawCollision(0xA9B4FFFFu);
|
||||
|
|
@ -355,7 +355,7 @@ public sealed class RuntimePhysicsStateTests
|
|||
firstAdmission,
|
||||
first,
|
||||
CollisionAssets(0xA9B4FFFFu, terrainHeight: 10f));
|
||||
Assert.True(physics.CommitCollisionGeneration(
|
||||
Assert.True(CommitPrepared(physics,
|
||||
firstAdmission,
|
||||
first).Committed);
|
||||
}
|
||||
|
|
@ -375,7 +375,7 @@ public sealed class RuntimePhysicsStateTests
|
|||
Assert.Equal(0, notifications);
|
||||
|
||||
RuntimeCollisionGenerationCommit committed =
|
||||
physics.CommitCollisionGeneration(
|
||||
CommitPrepared(physics,
|
||||
replacementAdmission,
|
||||
replacement);
|
||||
|
||||
|
|
@ -401,7 +401,11 @@ public sealed class RuntimePhysicsStateTests
|
|||
RuntimeCollisionAdmission current =
|
||||
physics.BeginCollisionAdmission(0xA9B4FFFFu);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
physics.CommitCollisionGeneration(stale, preparedStale));
|
||||
CommitPrepared(physics, stale, preparedStale));
|
||||
physics.CancelCollisionGeneration(stale, preparedStale);
|
||||
Assert.Equal(
|
||||
1,
|
||||
physics.CaptureOwnership().CollisionAdmissionCount);
|
||||
Assert.False(physics.Engine.IsLandblockTerrainResident(0xA9B4FFFFu));
|
||||
|
||||
using PreparedLandblockCollisionGeneration preparedCurrent =
|
||||
|
|
@ -410,14 +414,14 @@ public sealed class RuntimePhysicsStateTests
|
|||
current,
|
||||
preparedCurrent,
|
||||
CollisionAssets(0xA9B4FFFFu, terrainHeight: 20f));
|
||||
Assert.True(physics.CommitCollisionGeneration(
|
||||
Assert.True(CommitPrepared(physics,
|
||||
current,
|
||||
preparedCurrent).Committed);
|
||||
Assert.Equal(20f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovingDynamicOwnerDuringStagingMustRefreshBeforeCommit()
|
||||
public void WithdrawnOwnerStateChangeRejectsSealUntilRefreshed()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimePhysicsState physics = lifetime.Physics;
|
||||
|
|
@ -430,7 +434,7 @@ public sealed class RuntimePhysicsStateTests
|
|||
initialAdmission,
|
||||
initial,
|
||||
CollisionAssets(0x0101FFFFu, terrainHeight: 5f));
|
||||
Assert.True(physics.CommitCollisionGeneration(
|
||||
Assert.True(CommitPrepared(physics,
|
||||
initialAdmission,
|
||||
initial).Committed);
|
||||
}
|
||||
|
|
@ -445,6 +449,9 @@ public sealed class RuntimePhysicsStateTests
|
|||
0x0101FFFFu,
|
||||
seedCellId: 0x01010001u,
|
||||
isStatic: false);
|
||||
physics.Engine.ShadowObjects.RemoveLandblock(0x0101FFFFu);
|
||||
Assert.Equal(0, physics.Engine.ShadowObjects.TotalRegistered);
|
||||
Assert.Equal(1, physics.Engine.ShadowObjects.RetainedRegistrationCount);
|
||||
|
||||
RuntimeCollisionAdmission admission =
|
||||
physics.BeginCollisionAdmission(0x0101FFFFu);
|
||||
|
|
@ -454,33 +461,125 @@ public sealed class RuntimePhysicsStateTests
|
|||
admission,
|
||||
prepared,
|
||||
CollisionAssets(0x0101FFFFu, terrainHeight: 15f));
|
||||
uint owner = Assert.Single(physics.CaptureCollisionDynamicOwners(
|
||||
admission,
|
||||
prepared));
|
||||
physics.RefreshCollisionDynamicOwner(admission, prepared, owner);
|
||||
physics.Engine.ShadowObjects.UpdatePosition(
|
||||
owner,
|
||||
new Vector3(30f, 10f, 0f),
|
||||
Quaternion.Identity,
|
||||
0f,
|
||||
0f,
|
||||
0x0101FFFFu,
|
||||
seedCellId: 0x01010009u);
|
||||
uint owner = Assert.Single(SealPrepared(physics, admission, prepared));
|
||||
physics.Engine.ShadowObjects.UpdatePhysicsState(owner, 0x14u);
|
||||
|
||||
RuntimeCollisionGenerationCommit rejected =
|
||||
physics.CommitCollisionGeneration(admission, prepared);
|
||||
Assert.False(rejected.Committed);
|
||||
Assert.Equal(owner, Assert.Single(rejected.DirtyDynamicOwnerIds));
|
||||
Assert.Equal(5f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
|
||||
physics.RefreshCollisionDynamicOwner(admission, prepared, owner);
|
||||
Assert.Equal(owner, Assert.Single(SealPrepared(
|
||||
physics,
|
||||
admission,
|
||||
prepared)));
|
||||
Assert.True(physics.CommitCollisionGeneration(
|
||||
admission,
|
||||
prepared).Committed);
|
||||
Assert.Equal(15f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
Assert.Contains(
|
||||
physics.Engine.ShadowObjects.GetObjectsInCell(0x01010009u),
|
||||
entry => entry.EntityId == owner);
|
||||
ShadowEntry restored = Assert.Single(
|
||||
physics.Engine.ShadowObjects.AllEntriesForDebug());
|
||||
Assert.Equal(owner, restored.EntityId);
|
||||
Assert.Equal(0x14u, restored.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DenseReplacementSealsOneWorkUnitPerStepAndActivatesWithoutAllocation()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimePhysicsState physics = lifetime.Physics;
|
||||
const uint landblockId = 0x0101FFFFu;
|
||||
RuntimeCollisionAdmission initialAdmission =
|
||||
physics.BeginCollisionAdmission(landblockId);
|
||||
using (PreparedLandblockCollisionGeneration initial =
|
||||
physics.PrepareCollisionGeneration(initialAdmission))
|
||||
{
|
||||
physics.StageCollisionAssets(
|
||||
initialAdmission,
|
||||
initial,
|
||||
CollisionAssets(landblockId, terrainHeight: 5f));
|
||||
Assert.True(CommitPrepared(
|
||||
physics,
|
||||
initialAdmission,
|
||||
initial).Committed);
|
||||
}
|
||||
|
||||
const int ownerCount = 256;
|
||||
for (uint index = 0; index < ownerCount; index++)
|
||||
{
|
||||
physics.Engine.ShadowObjects.Register(
|
||||
entityId: 1000u + index,
|
||||
gfxObjId: 0x01000001u,
|
||||
worldPos: new Vector3(
|
||||
8f + (index % 16u) * 0.25f,
|
||||
8f + (index / 16u) * 0.25f,
|
||||
0f),
|
||||
rotation: Quaternion.Identity,
|
||||
radius: 0.5f,
|
||||
worldOffsetX: 0f,
|
||||
worldOffsetY: 0f,
|
||||
landblockId,
|
||||
seedCellId: 0x01010001u,
|
||||
isStatic: false);
|
||||
}
|
||||
|
||||
RuntimeCollisionAdmission admission =
|
||||
physics.BeginCollisionAdmission(landblockId);
|
||||
using PreparedLandblockCollisionGeneration prepared =
|
||||
physics.PrepareCollisionGeneration(admission);
|
||||
physics.StageCollisionAssets(
|
||||
admission,
|
||||
prepared,
|
||||
CollisionAssets(landblockId, terrainHeight: 15f));
|
||||
|
||||
int captureSteps = 0;
|
||||
RuntimeCollisionOwnerCaptureStep capture;
|
||||
do
|
||||
{
|
||||
capture = physics.AdvanceCollisionRetainedOwnerCapture(
|
||||
admission,
|
||||
prepared);
|
||||
captureSteps++;
|
||||
}
|
||||
while (!capture.Completed);
|
||||
Assert.True(captureSteps >= ownerCount);
|
||||
Assert.Equal(ownerCount, prepared.RetainedOwnerIds.Count);
|
||||
foreach (uint ownerId in prepared.RetainedOwnerIds)
|
||||
{
|
||||
physics.RefreshCollisionRetainedOwner(
|
||||
admission,
|
||||
prepared,
|
||||
ownerId);
|
||||
}
|
||||
|
||||
int sealSteps = 0;
|
||||
int workUnits = 0;
|
||||
RuntimeCollisionSealStep seal;
|
||||
do
|
||||
{
|
||||
seal = physics.AdvanceCollisionGenerationSeal(
|
||||
admission,
|
||||
prepared);
|
||||
sealSteps++;
|
||||
Assert.InRange(seal.WorkUnits, 0, 1);
|
||||
workUnits += seal.WorkUnits;
|
||||
}
|
||||
while (!seal.Completed);
|
||||
Assert.False(seal.Restarted);
|
||||
Assert.True(sealSteps > ownerCount);
|
||||
Assert.True(workUnits > ownerCount);
|
||||
|
||||
_ = GC.GetAllocatedBytesForCurrentThread();
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
RuntimeCollisionGenerationCommit commit =
|
||||
physics.CommitCollisionGeneration(admission, prepared);
|
||||
long allocated =
|
||||
GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
|
||||
Assert.True(commit.Committed);
|
||||
Assert.Equal(0L, allocated);
|
||||
Assert.Equal(15f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
Assert.Equal(ownerCount, physics.Engine.ShadowObjects.TotalRegistered);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -496,7 +595,7 @@ public sealed class RuntimePhysicsStateTests
|
|||
admission,
|
||||
prepared,
|
||||
CollisionAssets(0x0101FFFFu, terrainHeight: 12f));
|
||||
Assert.Empty(physics.CaptureCollisionDynamicOwners(admission, prepared));
|
||||
Assert.Empty(SealPrepared(physics, admission, prepared));
|
||||
|
||||
physics.Engine.ShadowObjects.Register(
|
||||
77u,
|
||||
|
|
@ -512,16 +611,17 @@ public sealed class RuntimePhysicsStateTests
|
|||
RuntimeCollisionGenerationCommit spawned =
|
||||
physics.CommitCollisionGeneration(admission, prepared);
|
||||
Assert.False(spawned.Committed);
|
||||
Assert.Equal(77u, Assert.Single(spawned.DirtyDynamicOwnerIds));
|
||||
|
||||
physics.RefreshCollisionDynamicOwner(admission, prepared, 77u);
|
||||
Assert.Equal(77u, Assert.Single(SealPrepared(
|
||||
physics,
|
||||
admission,
|
||||
prepared)));
|
||||
physics.Engine.ShadowObjects.Deregister(77u);
|
||||
RuntimeCollisionGenerationCommit deleted =
|
||||
physics.CommitCollisionGeneration(admission, prepared);
|
||||
Assert.False(deleted.Committed);
|
||||
Assert.Equal(77u, Assert.Single(deleted.DirtyDynamicOwnerIds));
|
||||
|
||||
physics.RefreshCollisionDynamicOwner(admission, prepared, 77u);
|
||||
Assert.Empty(SealPrepared(physics, admission, prepared));
|
||||
Assert.True(physics.CommitCollisionGeneration(
|
||||
admission,
|
||||
prepared).Committed);
|
||||
|
|
@ -546,7 +646,7 @@ public sealed class RuntimePhysicsStateTests
|
|||
admission,
|
||||
prepared,
|
||||
CollisionAssets(0x0101FFFFu));
|
||||
_ = lifetime.Physics.CommitCollisionGeneration(admission, prepared);
|
||||
_ = CommitPrepared(lifetime.Physics, admission, prepared);
|
||||
lifetime.Physics.Engine.ShadowObjects.Register(
|
||||
entityId: record.LocalEntityId!.Value,
|
||||
gfxObjId: 0x01000001u,
|
||||
|
|
@ -930,6 +1030,49 @@ public sealed class RuntimePhysicsStateTests
|
|||
Assert.Equal(record.SpatialAuthorityVersion, observed.SpatialAuthorityVersion);
|
||||
}
|
||||
|
||||
private static RuntimeCollisionGenerationCommit CommitPrepared(
|
||||
RuntimePhysicsState physics,
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration prepared)
|
||||
{
|
||||
_ = SealPrepared(physics, admission, prepared);
|
||||
return physics.CommitCollisionGeneration(admission, prepared);
|
||||
}
|
||||
|
||||
private static uint[] SealPrepared(
|
||||
RuntimePhysicsState physics,
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration prepared)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (!physics.AdvanceCollisionRetainedOwnerCapture(
|
||||
admission,
|
||||
prepared).Completed)
|
||||
{
|
||||
}
|
||||
foreach (uint ownerId in prepared.RetainedOwnerIds)
|
||||
{
|
||||
physics.RefreshCollisionRetainedOwner(
|
||||
admission,
|
||||
prepared,
|
||||
ownerId);
|
||||
}
|
||||
RuntimeCollisionSealStep seal;
|
||||
do
|
||||
{
|
||||
seal = physics.AdvanceCollisionGenerationSeal(
|
||||
admission,
|
||||
prepared);
|
||||
Assert.InRange(seal.WorkUnits, 0, 1);
|
||||
}
|
||||
while (!seal.Completed && !seal.Restarted);
|
||||
if (seal.Completed)
|
||||
break;
|
||||
}
|
||||
return [.. prepared.RetainedOwnerIds];
|
||||
}
|
||||
|
||||
private static RuntimeLandblockCollisionAssets CollisionAssets(
|
||||
uint landblockId,
|
||||
float terrainHeight = 0f)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue