fix(physics): activate collision generations atomically
This commit is contained in:
parent
3e0f3b6206
commit
be94bc9b06
18 changed files with 1402 additions and 80 deletions
|
|
@ -119,7 +119,11 @@ public sealed class RuntimePhysicsOwnershipTests
|
|||
collisionPublisher,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"AdmitCollisionAssets(",
|
||||
"StageCollisionAssets(",
|
||||
collisionPublisher,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"CommitCollisionGeneration(",
|
||||
collisionPublisher,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
Assert.Equal(1, diagnostics.CellSurfaceCount);
|
||||
Assert.Equal(1, diagnostics.PortalPlaneCount);
|
||||
Assert.Equal(1, diagnostics.BuildingCount);
|
||||
Assert.Null(fixture.Cache.CellGraph.GetVisible(envCellId));
|
||||
Assert.Empty(fixture.Cache.BuildingIds);
|
||||
|
||||
fixture.Publisher.CompletePublication(receipt);
|
||||
|
||||
Assert.NotNull(fixture.Cache.CellGraph.GetVisible(envCellId));
|
||||
BuildingPhysics building = Assert.Single(
|
||||
fixture.Cache.BuildingIds.Select(id => fixture.Cache.GetBuilding(id)!));
|
||||
|
|
@ -502,6 +507,11 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
LandblockPhysicsPublication secondReceipt = Begin(
|
||||
fixture.Publisher,
|
||||
Build(FirstLandblock, [moved]));
|
||||
ShadowEntry duringPreparation = Assert.Single(
|
||||
fixture.Engine.ShadowObjects.AllEntriesForDebug());
|
||||
Assert.Equal(
|
||||
first.Position + new Vector3(0f, 0f, 0.6f),
|
||||
duringPreparation.Position);
|
||||
fixture.Publisher.CompletePublication(secondReceipt);
|
||||
|
||||
Assert.Equal(1, fixture.Engine.LandblockCount);
|
||||
|
|
@ -513,6 +523,27 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
Assert.Equal(2, fixture.Publisher.Diagnostics.CompleteCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelledPublicationDisposesOnlyItsPrivateCollisionGeneration()
|
||||
{
|
||||
var fixture = Fixture();
|
||||
Publish(fixture.Publisher, Build(FirstLandblock));
|
||||
Assert.True(fixture.Engine.IsLandblockTerrainResident(FirstLandblock));
|
||||
|
||||
LandblockPhysicsPublication pending = Begin(
|
||||
fixture.Publisher,
|
||||
Build(AdjacentLandblock));
|
||||
Assert.False(pending.PreparedGeneration.IsDisposed);
|
||||
|
||||
pending.Dispose();
|
||||
|
||||
Assert.True(pending.PreparedGeneration.IsDisposed);
|
||||
Assert.True(fixture.Engine.IsLandblockTerrainResident(FirstLandblock));
|
||||
Assert.False(fixture.Engine.IsLandblockTerrainResident(AdjacentLandblock));
|
||||
Assert.Throws<ObjectDisposedException>(() =>
|
||||
fixture.Publisher.CompletePublication(pending));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NearReapply_RemovesOmittedStaticAcrossSeamAndPreservesNeighborOwner()
|
||||
{
|
||||
|
|
@ -760,6 +791,17 @@ public sealed class LandblockPhysicsPublisherTests
|
|||
Assert.DoesNotContain("ShadowObjects.RefloodLandblock", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_physicsEngine.DemoteLandblockToTerrain", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_physicsEngine.RemoveLandblock", source, StringComparison.Ordinal);
|
||||
|
||||
string publisherSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Streaming",
|
||||
"LandblockPhysicsPublisher.cs"));
|
||||
Assert.DoesNotContain("_physicsDataCache.RemoveCellsForLandblock", publisherSource, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_physicsDataCache.RemoveBuildingsForLandblock", publisherSource, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_physicsEngine.ShadowObjects.Reflood", publisherSource, StringComparison.Ordinal);
|
||||
Assert.Contains("CommitCollisionGeneration(", publisherSource, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static void Publish(
|
||||
|
|
|
|||
|
|
@ -311,27 +311,28 @@ public sealed class RuntimePhysicsStateTests
|
|||
first.Physics.BeginCollisionAdmission(0xA9B4FFFFu);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
first.Physics.AdmitCollisionAssets(
|
||||
admission,
|
||||
CollisionAssets(0xA9B4FFFFu)));
|
||||
first.Physics.PrepareCollisionGeneration(admission));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
second.Physics.AdmitCollisionAssets(
|
||||
newer,
|
||||
CollisionAssets(0xA9B4FFFFu)));
|
||||
second.Physics.PrepareCollisionGeneration(newer));
|
||||
|
||||
first.Physics.AdmitCollisionAssets(
|
||||
using PreparedLandblockCollisionGeneration prepared =
|
||||
first.Physics.PrepareCollisionGeneration(newer);
|
||||
first.Physics.StageCollisionAssets(
|
||||
newer,
|
||||
prepared,
|
||||
CollisionAssets(0xA9B4FFFFu));
|
||||
RuntimeCollisionAcknowledgement completed =
|
||||
first.Physics.CompleteCollisionAdmission(newer);
|
||||
RuntimeCollisionGenerationCommit commit =
|
||||
first.Physics.CommitCollisionGeneration(newer, prepared);
|
||||
RuntimeCollisionAcknowledgement completed = commit.Acknowledgement;
|
||||
|
||||
Assert.True(commit.Committed);
|
||||
Assert.True(completed.WasResident);
|
||||
Assert.Equal(1, first.Physics.Engine.LandblockCount);
|
||||
Assert.Equal(
|
||||
0,
|
||||
first.Physics.CaptureOwnership().CollisionAdmissionCount);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
first.Physics.CompleteCollisionAdmission(newer));
|
||||
first.Physics.CommitCollisionGeneration(newer, prepared));
|
||||
|
||||
RuntimeCollisionAcknowledgement withdrawn =
|
||||
first.Physics.WithdrawCollision(0xA9B4FFFFu);
|
||||
|
|
@ -340,6 +341,193 @@ public sealed class RuntimePhysicsStateTests
|
|||
Assert.Equal(0, first.Physics.Engine.LandblockCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollisionGenerationKeepsPreviousWorldVisibleUntilOneCommitNotification()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimePhysicsState physics = lifetime.Physics;
|
||||
RuntimeCollisionAdmission firstAdmission =
|
||||
physics.BeginCollisionAdmission(0xA9B4FFFFu);
|
||||
using (PreparedLandblockCollisionGeneration first =
|
||||
physics.PrepareCollisionGeneration(firstAdmission))
|
||||
{
|
||||
physics.StageCollisionAssets(
|
||||
firstAdmission,
|
||||
first,
|
||||
CollisionAssets(0xA9B4FFFFu, terrainHeight: 10f));
|
||||
Assert.True(physics.CommitCollisionGeneration(
|
||||
firstAdmission,
|
||||
first).Committed);
|
||||
}
|
||||
|
||||
int notifications = 0;
|
||||
physics.CollisionGenerationCommitted += _ => notifications++;
|
||||
RuntimeCollisionAdmission replacementAdmission =
|
||||
physics.BeginCollisionAdmission(0xA9B4FFFFu);
|
||||
using PreparedLandblockCollisionGeneration replacement =
|
||||
physics.PrepareCollisionGeneration(replacementAdmission);
|
||||
physics.StageCollisionAssets(
|
||||
replacementAdmission,
|
||||
replacement,
|
||||
CollisionAssets(0xA9B4FFFFu, terrainHeight: 25f));
|
||||
|
||||
Assert.Equal(10f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
Assert.Equal(0, notifications);
|
||||
|
||||
RuntimeCollisionGenerationCommit committed =
|
||||
physics.CommitCollisionGeneration(
|
||||
replacementAdmission,
|
||||
replacement);
|
||||
|
||||
Assert.True(committed.Committed);
|
||||
Assert.Equal(25f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
Assert.Equal(1, notifications);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CollisionGenerationRejectsStaleReplacementWithoutMutatingActiveWorld()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimePhysicsState physics = lifetime.Physics;
|
||||
RuntimeCollisionAdmission stale =
|
||||
physics.BeginCollisionAdmission(0xA9B4FFFFu);
|
||||
using PreparedLandblockCollisionGeneration preparedStale =
|
||||
physics.PrepareCollisionGeneration(stale);
|
||||
physics.StageCollisionAssets(
|
||||
stale,
|
||||
preparedStale,
|
||||
CollisionAssets(0xA9B4FFFFu, terrainHeight: 10f));
|
||||
|
||||
RuntimeCollisionAdmission current =
|
||||
physics.BeginCollisionAdmission(0xA9B4FFFFu);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
physics.CommitCollisionGeneration(stale, preparedStale));
|
||||
Assert.False(physics.Engine.IsLandblockTerrainResident(0xA9B4FFFFu));
|
||||
|
||||
using PreparedLandblockCollisionGeneration preparedCurrent =
|
||||
physics.PrepareCollisionGeneration(current);
|
||||
physics.StageCollisionAssets(
|
||||
current,
|
||||
preparedCurrent,
|
||||
CollisionAssets(0xA9B4FFFFu, terrainHeight: 20f));
|
||||
Assert.True(physics.CommitCollisionGeneration(
|
||||
current,
|
||||
preparedCurrent).Committed);
|
||||
Assert.Equal(20f, physics.Engine.SampleTerrainZ(1f, 1f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovingDynamicOwnerDuringStagingMustRefreshBeforeCommit()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimePhysicsState physics = lifetime.Physics;
|
||||
RuntimeCollisionAdmission initialAdmission =
|
||||
physics.BeginCollisionAdmission(0x0101FFFFu);
|
||||
using (PreparedLandblockCollisionGeneration initial =
|
||||
physics.PrepareCollisionGeneration(initialAdmission))
|
||||
{
|
||||
physics.StageCollisionAssets(
|
||||
initialAdmission,
|
||||
initial,
|
||||
CollisionAssets(0x0101FFFFu, terrainHeight: 5f));
|
||||
Assert.True(physics.CommitCollisionGeneration(
|
||||
initialAdmission,
|
||||
initial).Committed);
|
||||
}
|
||||
physics.Engine.ShadowObjects.Register(
|
||||
42u,
|
||||
0x01000001u,
|
||||
new Vector3(10f, 10f, 0f),
|
||||
Quaternion.Identity,
|
||||
0.5f,
|
||||
0f,
|
||||
0f,
|
||||
0x0101FFFFu,
|
||||
seedCellId: 0x01010001u,
|
||||
isStatic: false);
|
||||
|
||||
RuntimeCollisionAdmission admission =
|
||||
physics.BeginCollisionAdmission(0x0101FFFFu);
|
||||
using PreparedLandblockCollisionGeneration prepared =
|
||||
physics.PrepareCollisionGeneration(admission);
|
||||
physics.StageCollisionAssets(
|
||||
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);
|
||||
|
||||
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.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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpawnAndDeleteDuringStagingAreBothGenerationGated()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimePhysicsState physics = lifetime.Physics;
|
||||
RuntimeCollisionAdmission admission =
|
||||
physics.BeginCollisionAdmission(0x0101FFFFu);
|
||||
using PreparedLandblockCollisionGeneration prepared =
|
||||
physics.PrepareCollisionGeneration(admission);
|
||||
physics.StageCollisionAssets(
|
||||
admission,
|
||||
prepared,
|
||||
CollisionAssets(0x0101FFFFu, terrainHeight: 12f));
|
||||
Assert.Empty(physics.CaptureCollisionDynamicOwners(admission, prepared));
|
||||
|
||||
physics.Engine.ShadowObjects.Register(
|
||||
77u,
|
||||
0x01000001u,
|
||||
new Vector3(10f, 10f, 0f),
|
||||
Quaternion.Identity,
|
||||
0.5f,
|
||||
0f,
|
||||
0f,
|
||||
0x0101FFFFu,
|
||||
seedCellId: 0x01010001u,
|
||||
isStatic: false);
|
||||
RuntimeCollisionGenerationCommit spawned =
|
||||
physics.CommitCollisionGeneration(admission, prepared);
|
||||
Assert.False(spawned.Committed);
|
||||
Assert.Equal(77u, Assert.Single(spawned.DirtyDynamicOwnerIds));
|
||||
|
||||
physics.RefreshCollisionDynamicOwner(admission, prepared, 77u);
|
||||
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.True(physics.CommitCollisionGeneration(
|
||||
admission,
|
||||
prepared).Committed);
|
||||
Assert.Empty(physics.Engine.ShadowObjects.AllEntriesForDebug());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TerminalDisposalClearsLandblocksShadowsAndWorksets()
|
||||
{
|
||||
|
|
@ -352,10 +540,13 @@ public sealed class RuntimePhysicsStateTests
|
|||
|
||||
RuntimeCollisionAdmission admission =
|
||||
lifetime.Physics.BeginCollisionAdmission(0x0101FFFFu);
|
||||
lifetime.Physics.AdmitCollisionAssets(
|
||||
using PreparedLandblockCollisionGeneration prepared =
|
||||
lifetime.Physics.PrepareCollisionGeneration(admission);
|
||||
lifetime.Physics.StageCollisionAssets(
|
||||
admission,
|
||||
prepared,
|
||||
CollisionAssets(0x0101FFFFu));
|
||||
_ = lifetime.Physics.CompleteCollisionAdmission(admission);
|
||||
_ = lifetime.Physics.CommitCollisionGeneration(admission, prepared);
|
||||
lifetime.Physics.Engine.ShadowObjects.Register(
|
||||
entityId: record.LocalEntityId!.Value,
|
||||
gfxObjId: 0x01000001u,
|
||||
|
|
@ -740,10 +931,12 @@ public sealed class RuntimePhysicsStateTests
|
|||
}
|
||||
|
||||
private static RuntimeLandblockCollisionAssets CollisionAssets(
|
||||
uint landblockId)
|
||||
uint landblockId,
|
||||
float terrainHeight = 0f)
|
||||
{
|
||||
var heights = new byte[81];
|
||||
var table = new float[256];
|
||||
table[0] = terrainHeight;
|
||||
return new RuntimeLandblockCollisionAssets(
|
||||
landblockId,
|
||||
new TerrainSurface(heights, table),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue