feat(runtime): atomically replace collision generations

This commit is contained in:
Erik 2026-08-01 17:33:34 +02:00
parent 99bf1751bb
commit 9b0f59bd1b
21 changed files with 2435 additions and 408 deletions

View file

@ -2,9 +2,13 @@ using System.Collections.Immutable;
using System.Numerics;
using System.Reflection;
using AcDream.App.Streaming;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Core.World;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Types;
@ -19,6 +23,91 @@ public sealed class LandblockPhysicsPublisherTests
private static readonly float[] HeightTable =
Enumerable.Range(0, 256).Select(index => (float)index).ToArray();
[Fact]
public void ReplacementYieldsAtHeldWithdrawAndPlaceWithoutPostEngineReseal()
{
using var lifetime = new RuntimeEntityObjectLifetime(
new PhysicsDataCache());
RuntimePhysicsState physics = lifetime.Physics;
var publisher = new LandblockPhysicsPublisher(physics, HeightTable);
Publish(publisher, Build(FirstLandblock));
const uint guid = 0x70004101u;
const uint cell = 0xA9B40001u;
Vector3 position = new(10f, 10f, 0f);
RuntimeEntityRecord record = lifetime.RegisterEntity(
RuntimeSpawn(guid, cell, position)).Canonical!;
lifetime.Entities.SetFinalPhysicsState(record, PhysicsStateFlags.Gravity);
lifetime.Entities.SetFullCell(record, cell, FirstLandblock);
var body = new PhysicsBody
{
Position = position,
Orientation = Quaternion.Identity,
LastUpdateTime = 1d,
State = PhysicsStateFlags.Gravity,
TransientState = TransientStateFlags.Active,
};
body.SnapToCell(cell, position, position);
lifetime.Entities.SetPhysicsBody(record, body);
record.ObjectClock.Activate();
physics.AcknowledgeSpatialProjection(record, spatial: true);
RuntimePlacementProjectionToken seeded = SeedRuntimePlacement(
physics,
record,
cell,
position);
Assert.True(physics.SetPosition.AcknowledgeProjection(seeded));
LandblockPhysicsPublication receipt = Begin(
publisher,
Build(FirstLandblock));
Assert.False(publisher.CompletePublication(receipt));
Assert.False(receipt.EngineMutationCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.Equal(RuntimePlacementProjectionKind.Withdraw, withdrawal.Kind);
Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawal.Token));
Assert.False(publisher.CompletePublication(receipt));
Assert.True(receipt.EngineMutationCommitted);
Assert.True(receipt.SealCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot placement));
Assert.Equal(RuntimePlacementProjectionKind.Place, placement.Kind);
Assert.False(publisher.CompletePublication(receipt));
Assert.True(receipt.SealCommitted);
Assert.True(receipt.EngineMutationCommitted);
Assert.True(physics.SetPosition.AcknowledgeProjection(placement.Token));
Assert.True(publisher.CompletePublication(receipt));
Assert.True(receipt.CompletionCommitted);
LandblockPhysicsPublication cancelled = Begin(
publisher,
Build(FirstLandblock));
Assert.False(publisher.CompletePublication(cancelled));
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot cancelWithdrawal));
Assert.True(physics.SetPosition.AcknowledgeProjection(
cancelWithdrawal.Token));
Assert.False(publisher.CompletePublication(cancelled));
Assert.True(cancelled.EngineMutationCommitted);
Assert.True(cancelled.SealCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot cancelPlacement));
Assert.False(cancelled.TryCancel());
Assert.True(cancelled.CancellationRequested);
Assert.True(cancelled.SealCommitted);
Assert.True(physics.SetPosition.AcknowledgeProjection(
cancelPlacement.Token));
Assert.True(cancelled.TryCancel());
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void Constructor_ClonesHeightTableAndRejectsIncompleteInput()
{
@ -821,6 +910,151 @@ public sealed class LandblockPhysicsPublisherTests
LandblockBuild build) =>
publisher.BeginPublication(RenderReceipt(build));
private static RuntimePlacementProjectionToken SeedRuntimePlacement(
RuntimePhysicsState physics,
RuntimeEntityRecord record,
uint cell,
Vector3 position)
{
Type coreAssemblyMarker = typeof(PhysicsEngine);
Type requestType = coreAssemblyMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsSetPositionRequest",
throwOnError: true)!;
Type flagsType = coreAssemblyMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsSetPositionFlags",
throwOnError: true)!;
Type placementClassType = coreAssemblyMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsPlacementClass",
throwOnError: true)!;
object request = Activator.CreateInstance(
requestType,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
binder: null,
args:
[
position,
Quaternion.Identity,
cell,
position,
ImmutableArray<FlatCollisionSphere>.Empty,
1f,
0.4f,
0.4f,
PhysicsStateFlags.None,
ObjectInfoState.None,
0u,
Enum.ToObject(placementClassType, 0),
Enum.ToObject(flagsType, 0x011u),
Vector3.Zero,
0f,
0f,
0u,
cell,
],
culture: null)!;
Type runtimeAssemblyMarker = typeof(RuntimePhysicsState);
Type commandType = runtimeAssemblyMarker.Assembly.GetType(
"AcDream.Runtime.Physics.RuntimeSetPositionCommand",
throwOnError: true)!;
Type kindType = runtimeAssemblyMarker.Assembly.GetType(
"AcDream.Runtime.Physics.RuntimeSetPositionOperationKind",
throwOnError: true)!;
object command = Activator.CreateInstance(
commandType,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
binder: null,
args:
[
request,
Enum.ToObject(kindType, 2),
10d,
0UL,
0f,
0f,
default(RuntimePortalPlacementAuthority),
],
culture: null)!;
MethodInfo apply = physics.SetPosition.GetType().GetMethod(
"Apply",
BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new MissingMethodException("Runtime SetPosition.Apply");
object outcome = apply.Invoke(
physics.SetPosition,
[record, record.PositionAuthorityVersion, command])!;
return (RuntimePlacementProjectionToken)(outcome.GetType().GetProperty(
"Projection",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
?.GetValue(outcome)
?? throw new MissingMemberException("Runtime placement projection"));
}
private static WorldSession.EntitySpawn RuntimeSpawn(
uint guid,
uint cell,
Vector3 position)
{
var serverPosition = new CreateObject.ServerPosition(
cell,
position.X,
position.Y,
position.Z,
1f,
0f,
0f,
0f);
var timestamps = new PhysicsTimestamps(
Position: 1,
Movement: 1,
State: 1,
Vector: 1,
Teleport: 0,
ServerControlledMove: 1,
ForcePosition: 0,
ObjDesc: 1,
Instance: 1);
var spawnPhysics = new PhysicsSpawnData(
RawState: (uint)PhysicsStateFlags.Gravity,
Position: serverPosition,
Movement: null,
AnimationFrame: null,
SetupTableId: 0x02000001u,
MotionTableId: 0x09000001u,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: null,
Children: null,
Scale: null,
Friction: null,
Elasticity: null,
Translucency: null,
Velocity: null,
Acceleration: null,
AngularVelocity: null,
DefaultScriptType: null,
DefaultScriptIntensity: null,
Timestamps: timestamps);
return new WorldSession.EntitySpawn(
guid,
serverPosition,
0x02000001u,
Array.Empty<CreateObject.AnimPartChange>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.SubPaletteSwap>(),
null,
null,
"app-collision-publication-fixture",
null,
null,
0x09000001u,
PhysicsState: (uint)PhysicsStateFlags.Gravity,
InstanceSequence: 1,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: 1,
Physics: spawnPhysics);
}
private static LandblockRenderPublication RenderReceipt(LandblockBuild build)
{
var publisher = new LandblockRenderPublisher(

View file

@ -9,6 +9,106 @@ namespace AcDream.App.Tests.Streaming;
public sealed class LandblockRetirementCoordinatorTests
{
[Fact]
public void PendingPhysicsStageYieldsBeforeTerrainAndRemainsRetryable()
{
const uint landblockId = 0x2020FFFFu;
var ticket = new LandblockRetirementTicket(
new GpuLandblockRetirement(
landblockId,
LandblockRetirementKind.Full,
Array.Empty<WorldEntity>()),
LandblockRetirementStage.Physics
| LandblockRetirementStage.Terrain);
int physicsPolls = 0;
int terrainPolls = 0;
Assert.Equal(
LandblockRetirementOperationResult.Pending,
ticket.RunOnceStep(
LandblockRetirementStage.Physics,
() =>
{
physicsPolls++;
return false;
}));
Assert.Equal(LandblockRetirementStage.Physics, ticket.NextIncompleteStage);
Assert.Equal(1, physicsPolls);
Assert.Equal(0, terrainPolls);
Assert.Equal(
LandblockRetirementOperationResult.Progressed,
ticket.RunOnceStep(
LandblockRetirementStage.Physics,
() =>
{
physicsPolls++;
return true;
}));
Assert.Equal(LandblockRetirementStage.Terrain, ticket.NextIncompleteStage);
Assert.True(ticket.RunOnce(
LandblockRetirementStage.Terrain,
() => terrainPolls++));
Assert.True(ticket.IsComplete);
Assert.Equal(2, physicsPolls);
Assert.Equal(1, terrainPolls);
}
[Fact]
public void BudgetedPendingPhysicsPollsExactlyOncePerAdvance()
{
const uint landblockId = 0x2023FFFFu;
var state = StateWith(landblockId);
int physicsPolls = 0;
int terrainPolls = 0;
bool acknowledgePhysics = false;
LandblockRetirementOperationResult AdvancePresentation(
LandblockRetirementTicket ticket) =>
ticket.NextIncompleteStage switch
{
LandblockRetirementStage.Physics => ticket.RunOnceStep(
LandblockRetirementStage.Physics,
() =>
{
physicsPolls++;
return acknowledgePhysics;
}),
LandblockRetirementStage.Terrain => ticket.RunOnceStep(
LandblockRetirementStage.Terrain,
() => terrainPolls++),
{ } stage => ticket.RunOnceStep(stage, () => { }),
};
LandblockRetirementCoordinator coordinator =
LandblockRetirementCoordinator.CreateBudgeted(
state,
AdvancePresentation,
ticket =>
{
while (!ticket.IsComplete)
_ = AdvancePresentation(ticket);
});
coordinator.BeginFull(landblockId);
var first = new StreamingWorkMeter(Budget(maxEntityOperations: 64));
coordinator.Advance(first);
first.FinishFrame();
Assert.Equal(1, physicsPolls);
Assert.Equal(0, terrainPolls);
Assert.Equal(0, first.Snapshot.FailureCount);
Assert.Equal(1, coordinator.PendingCount);
acknowledgePhysics = true;
var second = new StreamingWorkMeter(Budget(maxEntityOperations: 64));
coordinator.Advance(second);
second.FinishFrame();
Assert.Equal(2, physicsPolls);
Assert.Equal(1, terrainPolls);
Assert.Equal(0, second.Snapshot.FailureCount);
Assert.Equal(0, coordinator.PendingCount);
}
[Fact]
public void EntityStageFailure_DetachesImmediately_AndResumesAtFailedEntity()
{

View file

@ -1,6 +1,8 @@
using System.Buffers.Binary;
using System.Collections.Immutable;
using System.Net;
using System.Numerics;
using System.Reflection;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -605,7 +607,7 @@ public sealed class HeadlessSessionHostTests
using var lifetime = new RuntimeEntityObjectLifetime();
RuntimePhysicsState physics = lifetime.Physics;
const uint landblockId = 0xA9B4FFFFu;
_ = HeadlessCollisionGenerationTransaction.Execute(
CompleteCollisionGeneration(
physics,
landblockId,
afterAdmission: null,
@ -616,7 +618,7 @@ public sealed class HeadlessSessionHostTests
CollisionAssets(landblockId, 10f)));
Assert.Throws<FixtureCollisionPublicationException>(() =>
HeadlessCollisionGenerationTransaction.Execute(
CompleteCollisionGeneration(
physics,
landblockId,
_ => throw new FixtureCollisionPublicationException(),
@ -629,13 +631,137 @@ public sealed class HeadlessSessionHostTests
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void CollisionTransactionYieldsTheFirstNonterminalRuntimePoll()
{
using var lifetime = new RuntimeEntityObjectLifetime();
RuntimePhysicsState physics = lifetime.Physics;
const uint landblockId = 0xA9B4FFFFu;
HeadlessCollisionGenerationTransaction transaction =
HeadlessCollisionGenerationTransaction.Begin(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 10f)));
HeadlessCollisionGenerationAdvance advance;
do
{
advance = transaction.Advance();
Assert.True(advance.Progressed);
}
while (!advance.YieldToCaller);
Assert.False(advance.Completed);
Assert.False(advance.WaitingForProjectionAcknowledgement);
Assert.False(transaction.CompletionCommitted);
do
{
advance = transaction.Advance();
Assert.True(advance.Progressed);
}
while (!advance.Completed && !advance.YieldToCaller);
if (!advance.Completed)
advance = transaction.Advance();
Assert.True(advance.Completed);
Assert.True(transaction.EngineMutationCommitted);
Assert.True(transaction.CompletionCommitted);
Assert.Equal(0, physics.CaptureOwnership().CollisionAdmissionCount);
}
[Fact]
public void CollisionTransactionRetainsPostEngineCancellationUntilPlaceAck()
{
using var lifetime = new RuntimeEntityObjectLifetime();
RuntimePhysicsState physics = lifetime.Physics;
const uint landblockId = 0xA9B4FFFFu;
CompleteCollisionGeneration(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 10f)));
const uint guid = 0x70004201u;
const uint cell = 0xA9B40001u;
Vector3 position = new(10f, 10f, 0f);
RuntimeEntityRecord record = lifetime.RegisterEntity(
Spawn(guid)).Canonical!;
lifetime.Entities.SetFinalPhysicsState(record, PhysicsStateFlags.Gravity);
lifetime.Entities.SetFullCell(record, cell, landblockId);
var body = new PhysicsBody
{
Position = position,
Orientation = Quaternion.Identity,
LastUpdateTime = 1d,
State = PhysicsStateFlags.Gravity,
TransientState = TransientStateFlags.Active,
};
body.SnapToCell(cell, position, position);
lifetime.Entities.SetPhysicsBody(record, body);
record.ObjectClock.Activate();
physics.AcknowledgeSpatialProjection(record, spatial: true);
RuntimePlacementProjectionToken seeded = SeedRuntimePlacement(
physics,
record,
cell,
position);
Assert.True(physics.SetPosition.AcknowledgeProjection(seeded));
HeadlessCollisionGenerationTransaction transaction =
HeadlessCollisionGenerationTransaction.Begin(
physics,
landblockId,
afterAdmission: null,
(admission, prepared) =>
physics.StageCollisionAssets(
admission,
prepared,
CollisionAssets(landblockId, 20f)));
HeadlessCollisionGenerationAdvance advance;
do
{
advance = transaction.Advance();
}
while (!advance.WaitingForProjectionAcknowledgement);
Assert.False(transaction.EngineMutationCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawal.Token));
do
{
advance = transaction.Advance();
}
while (!advance.WaitingForProjectionAcknowledgement);
Assert.True(transaction.EngineMutationCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot placement));
Assert.False(transaction.TryCancel());
Assert.True(physics.SetPosition.AcknowledgeProjection(placement.Token));
Assert.True(transaction.TryCancel());
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void CollisionTransactionCancelsStagingFaultWithoutWithdrawingActiveWorld()
{
using var lifetime = new RuntimeEntityObjectLifetime();
RuntimePhysicsState physics = lifetime.Physics;
const uint landblockId = 0xA9B4FFFFu;
_ = HeadlessCollisionGenerationTransaction.Execute(
CompleteCollisionGeneration(
physics,
landblockId,
afterAdmission: null,
@ -646,7 +772,7 @@ public sealed class HeadlessSessionHostTests
CollisionAssets(landblockId, 10f)));
Assert.Throws<FixtureCollisionPublicationException>(() =>
HeadlessCollisionGenerationTransaction.Execute(
CompleteCollisionGeneration(
physics,
landblockId,
afterAdmission: null,
@ -714,6 +840,107 @@ public sealed class HeadlessSessionHostTests
runtime.MovementOwner.Controller = controller;
}
private static void CompleteCollisionGeneration(
RuntimePhysicsState physics,
uint landblockId,
Action<RuntimeCollisionAdmission>? afterAdmission,
Action<RuntimeCollisionAdmission,
PreparedLandblockCollisionGeneration> stage)
{
HeadlessCollisionGenerationTransaction transaction =
HeadlessCollisionGenerationTransaction.Begin(
physics,
landblockId,
afterAdmission,
stage);
while (true)
{
HeadlessCollisionGenerationAdvance advance = transaction.Advance();
if (advance.Completed)
return;
Assert.True(advance.Progressed);
Assert.False(advance.WaitingForProjectionAcknowledgement);
}
}
private static RuntimePlacementProjectionToken SeedRuntimePlacement(
RuntimePhysicsState physics,
RuntimeEntityRecord record,
uint cell,
Vector3 position)
{
Type coreMarker = typeof(PhysicsEngine);
Type requestType = coreMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsSetPositionRequest",
throwOnError: true)!;
Type flagsType = coreMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsSetPositionFlags",
throwOnError: true)!;
Type placementClassType = coreMarker.Assembly.GetType(
"AcDream.Core.Physics.PhysicsPlacementClass",
throwOnError: true)!;
object request = Activator.CreateInstance(
requestType,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
binder: null,
args:
[
position,
Quaternion.Identity,
cell,
position,
ImmutableArray<FlatCollisionSphere>.Empty,
1f,
0.4f,
0.4f,
PhysicsStateFlags.None,
ObjectInfoState.None,
0u,
Enum.ToObject(placementClassType, 0),
Enum.ToObject(flagsType, 0x011u),
Vector3.Zero,
0f,
0f,
0u,
cell,
],
culture: null)!;
Type runtimeMarker = typeof(RuntimePhysicsState);
Type commandType = runtimeMarker.Assembly.GetType(
"AcDream.Runtime.Physics.RuntimeSetPositionCommand",
throwOnError: true)!;
Type kindType = runtimeMarker.Assembly.GetType(
"AcDream.Runtime.Physics.RuntimeSetPositionOperationKind",
throwOnError: true)!;
object command = Activator.CreateInstance(
commandType,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
binder: null,
args:
[
request,
Enum.ToObject(kindType, 2),
10d,
0UL,
0f,
0f,
default(RuntimePortalPlacementAuthority),
],
culture: null)!;
MethodInfo apply = physics.SetPosition.GetType().GetMethod(
"Apply",
BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new MissingMethodException("Runtime SetPosition.Apply");
object outcome = apply.Invoke(
physics.SetPosition,
[record, record.PositionAuthorityVersion, command])!;
return (RuntimePlacementProjectionToken)(outcome.GetType().GetProperty(
"Projection",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
?.GetValue(outcome)
?? throw new MissingMemberException("Runtime placement projection"));
}
private static RuntimeLandblockCollisionAssets CollisionAssets(
uint landblockId,
float terrainHeight)

View file

@ -66,7 +66,33 @@ public sealed class RuntimeLocalPlayerPhysicsPublicationStateTests
.IsExactPreparedPlacementCurrent(
fixture.Record,
fixture.Placement,
fixture.Command));
fixture.Command));
}
[Fact]
public void DormantOwnershipPreflightFailurePublishesNoCanonicalOwner()
{
using var fixture = new Fixture();
RuntimeLocalPlayerPhysicsPublicationToken publication =
fixture.Prepare();
Assert.True(fixture.Lifetime.Physics.SetPosition.Cancel(
fixture.Record,
publishWithdrawal: false));
var unownedBody = new PhysicsBody();
Assert.Throws<InvalidOperationException>(() => fixture.Lifetime.Physics
.SetPosition.PrepareDormantLocalActivationOwnership(
fixture.Record,
unownedBody,
publication.Placement));
Assert.Null(fixture.Record.PhysicsBody);
Assert.Null(fixture.Movement.Controller);
Assert.False(unownedBody.InWorld);
Assert.False(fixture.Lifetime.Physics.IsSpatialRoot(fixture.Record));
Assert.Equal(1, fixture.Owner.CaptureOwnership().CandidateCount);
Assert.Equal(0, fixture.Owner.CaptureOwnership()
.PendingActivationCount);
}
[Fact]
@ -2467,32 +2493,48 @@ public sealed class RuntimeLocalPlayerPhysicsPublicationStateTests
RuntimeCollisionAdmission admission,
PreparedLandblockCollisionGeneration prepared)
{
while (true)
bool engineCommitted = false;
for (int poll = 0; poll < 10_000; poll++)
{
while (!physics.AdvanceCollisionRetainedOwnerCapture(
admission,
prepared).Completed)
if (!engineCommitted)
{
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);
}
while (!seal.Completed && !seal.Restarted);
if (!seal.Completed)
continue;
}
foreach (uint ownerId in prepared.RetainedOwnerIds)
RuntimeCollisionGenerationCommit result =
physics.CommitCollisionGeneration(admission, prepared);
if (result.Completed)
return result;
while (physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot projection))
{
physics.RefreshCollisionRetainedOwner(
admission,
prepared,
ownerId);
Assert.True(physics.SetPosition.AcknowledgeProjection(
projection.Token));
}
RuntimeCollisionSealStep seal;
do
{
seal = physics.AdvanceCollisionGenerationSeal(
admission,
prepared);
}
while (!seal.Completed && !seal.Restarted);
if (seal.Completed)
break;
engineCommitted = result.EngineCommitted;
}
return physics.CommitCollisionGeneration(admission, prepared);
throw new InvalidOperationException(
"Collision generation did not complete its Runtime mutation transaction.");
}
private static RuntimeLandblockCollisionAssets CollisionAssets(

View file

@ -0,0 +1,479 @@
using System.Numerics;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
namespace AcDream.Runtime.Tests.Physics;
public sealed partial class RuntimeCollisionPrefixQuiescenceTests
{
[Fact]
public void ActivationWaitsForExactWithdrawAndPlaceReceipts()
{
using var fixture = new Fixture(bindGeneration: true);
RuntimeEntityRecord record = fixture.Add(
0x70003101u,
1,
CellP,
new Vector3(11f, 40f, 0f));
RuntimeSetPositionOutcome seeded = fixture.Place(
record,
CellP,
new Vector3(11.5f, 40f, 0f));
Assert.True(fixture.Lifetime.Physics.SetPosition
.AcknowledgeProjection(seeded.Projection));
RuntimePhysicsState physics = fixture.Lifetime.Physics;
RuntimeCollisionAdmission admission =
physics.BeginCollisionAdmission(PrefixP);
using PreparedLandblockCollisionGeneration prepared =
PrepareSealedMutation(physics, admission, PrefixP);
int committedNotifications = 0;
physics.CollisionGenerationCommitted += _ => committedNotifications++;
RuntimeCollisionGenerationCommit first =
physics.CommitCollisionGeneration(admission, prepared);
Assert.False(first.EngineCommitted);
Assert.False(first.Completed);
Assert.False(physics.IsSpatialRoot(record));
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawn));
Assert.Equal(RuntimePlacementProjectionKind.Withdraw, withdrawn.Kind);
Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawn.Token));
_ = SealMutation(physics, admission, prepared);
RuntimeCollisionGenerationCommit transferred =
physics.CommitCollisionGeneration(admission, prepared);
Assert.True(transferred.EngineCommitted);
Assert.False(transferred.Completed);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot restored));
Assert.Equal(RuntimePlacementProjectionKind.Place, restored.Kind);
Assert.True(physics.SetPosition.AcknowledgeProjection(restored.Token));
RuntimeCollisionGenerationCommit completed =
physics.CommitCollisionGeneration(admission, prepared);
Assert.True(completed.EngineCommitted);
Assert.True(completed.Completed);
Assert.True(physics.IsSpatialRoot(record));
Assert.Equal(1, committedNotifications);
Assert.Equal(0, physics.CaptureOwnership().CollisionPrefixMutationCount);
Assert.Equal(0, physics.CaptureOwnership().CollisionPrefixQuiescenceCount);
}
[Fact]
public void DemotionParksOnlyIndoorResidentsAndLeavesOutdoorPresentationLive()
{
using var fixture = new Fixture(bindGeneration: true);
RuntimeEntityRecord outdoor = fixture.Add(
0x70003102u,
1,
CellP,
new Vector3(12f, 41f, 0f));
RuntimeEntityRecord indoor = fixture.Add(
0x70003103u,
1,
PrefixP | 0x0100u,
new Vector3(13f, 41f, 0f));
RuntimeCollisionMutationResult first =
fixture.Lifetime.Physics.DemoteCollisionToTerrain(PrefixP);
Assert.False(first.Completed);
Assert.True(fixture.Lifetime.Physics.IsSpatialRoot(outdoor));
Assert.False(fixture.Lifetime.Physics.IsSpatialRoot(indoor));
Assert.True(fixture.Lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.Equal(indoor.Key, withdrawal.Token.Entity);
Assert.True(fixture.Lifetime.Physics.SetPosition
.AcknowledgeProjection(withdrawal.Token));
RuntimeCollisionMutationResult completed =
fixture.Lifetime.Physics.DemoteCollisionToTerrain(PrefixP);
Assert.True(completed.Completed);
Assert.True(completed.Ready);
Assert.True(fixture.Lifetime.Physics.IsSpatialRoot(outdoor));
Assert.False(fixture.Lifetime.Physics.IsSpatialRoot(indoor));
Assert.False(fixture.Lifetime.Physics.SetPosition.TryPeekProjection(out _));
}
[Fact]
public void WithdrawalLeavesResidentUnboundUntilLaterGenerationWakesIt()
{
using var fixture = new Fixture(bindGeneration: true);
RuntimeEntityRecord record = fixture.Add(
0x70003104u,
1,
CellP,
new Vector3(14f, 42f, 0f));
RuntimeSetPositionOutcome seeded = fixture.Place(
record,
CellP,
new Vector3(14.5f, 42f, 0f));
Assert.True(fixture.Lifetime.Physics.SetPosition
.AcknowledgeProjection(seeded.Projection));
RuntimePhysicsState physics = fixture.Lifetime.Physics;
RuntimeCollisionMutationResult withdrawal =
physics.WithdrawCollision(PrefixP);
Assert.False(withdrawal.Completed);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot removed));
Assert.True(physics.SetPosition.AcknowledgeProjection(removed.Token));
withdrawal = physics.WithdrawCollision(PrefixP);
Assert.True(withdrawal.Completed);
Assert.False(withdrawal.Ready);
Assert.False(physics.IsSpatialRoot(record));
RuntimeCollisionAdmission admission =
physics.BeginCollisionAdmission(PrefixP);
using PreparedLandblockCollisionGeneration prepared =
PrepareSealedMutation(physics, admission, PrefixP);
RuntimeCollisionGenerationCommit pending =
physics.CommitCollisionGeneration(admission, prepared);
Assert.False(pending.Completed);
_ = SealMutation(physics, admission, prepared);
RuntimeCollisionGenerationCommit completed =
physics.CommitCollisionGeneration(admission, prepared);
Assert.True(completed.EngineCommitted);
Assert.False(completed.Completed);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot restored));
Assert.Equal(RuntimePlacementProjectionKind.Place, restored.Kind);
Assert.True(physics.SetPosition.AcknowledgeProjection(restored.Token));
completed = physics.CommitCollisionGeneration(admission, prepared);
Assert.True(completed.Completed);
Assert.True(physics.IsSpatialRoot(record));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void SessionResetClearsPreAndPostEngineMutationDebt(bool postEngine)
{
using var fixture = new Fixture(bindGeneration: true);
RuntimeEntityRecord record = fixture.Add(
0x70003105u,
1,
CellP,
new Vector3(15f, 43f, 0f));
RuntimeSetPositionOutcome seeded = fixture.Place(
record,
CellP,
new Vector3(15.5f, 43f, 0f));
Assert.True(fixture.Lifetime.Physics.SetPosition
.AcknowledgeProjection(seeded.Projection));
RuntimePhysicsState physics = fixture.Lifetime.Physics;
RuntimeCollisionAdmission admission =
physics.BeginCollisionAdmission(PrefixP);
using PreparedLandblockCollisionGeneration prepared =
PrepareSealedMutation(physics, admission, PrefixP);
Assert.False(physics.CommitCollisionGeneration(
admission,
prepared).Completed);
if (postEngine)
{
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.True(physics.SetPosition.AcknowledgeProjection(
withdrawal.Token));
_ = SealMutation(physics, admission, prepared);
RuntimeCollisionGenerationCommit transferred =
physics.CommitCollisionGeneration(admission, prepared);
Assert.True(transferred.EngineCommitted);
Assert.False(transferred.Completed);
}
_ = fixture.Lifetime.BeginSessionClear();
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CommittedCollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionPrefixQuiescenceCount);
Assert.Equal(0, ownership.PendingCollisionPrefixProjectionCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void CancelledActivationCannotCommitWhileRestoreAckIsPending()
{
using var fixture = new Fixture(bindGeneration: true);
RuntimePhysicsState physics = fixture.Lifetime.Physics;
RuntimeCollisionAdmission baselineAdmission =
physics.BeginCollisionAdmission(PrefixP);
using (PreparedLandblockCollisionGeneration baseline =
PrepareSealedMutation(physics, baselineAdmission, PrefixP))
{
Assert.False(physics.CommitCollisionGeneration(
baselineAdmission,
baseline).Completed);
_ = SealMutation(physics, baselineAdmission, baseline);
Assert.True(physics.CommitCollisionGeneration(
baselineAdmission,
baseline).Completed);
}
RuntimeEntityRecord record = fixture.Add(
0x70003106u,
1,
CellP,
new Vector3(16f, 44f, 0f));
RuntimeSetPositionOutcome seeded = fixture.Place(
record,
CellP,
new Vector3(16.5f, 44f, 0f));
Assert.True(physics.SetPosition.AcknowledgeProjection(
seeded.Projection));
RuntimeCollisionAdmission admission =
physics.BeginCollisionAdmission(PrefixP);
using PreparedLandblockCollisionGeneration prepared =
PrepareSealedMutation(physics, admission, PrefixP);
RuntimeCollisionGenerationCommit parked =
physics.CommitCollisionGeneration(admission, prepared);
Assert.False(parked.EngineCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawal.Token));
Assert.False(physics.CancelCollisionGeneration(admission, prepared));
RuntimeCollisionGenerationCommit forbidden =
physics.CommitCollisionGeneration(admission, prepared);
Assert.False(forbidden.EngineCommitted);
Assert.False(forbidden.Completed);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot restored));
Assert.Equal(RuntimePlacementProjectionKind.Place, restored.Kind);
Assert.True(physics.SetPosition.AcknowledgeProjection(restored.Token));
Assert.True(physics.CancelCollisionGeneration(admission, prepared));
Assert.True(physics.IsSpatialRoot(record));
Assert.True(physics.Engine.IsLandblockTerrainResident(PrefixP));
Assert.Equal(0, physics.CaptureOwnership().CollisionPrefixMutationCount);
Assert.Equal(0, physics.CaptureOwnership().CollisionPrefixQuiescenceCount);
Assert.Equal(0, physics.CaptureOwnership().CollisionAdmissionCount);
}
[Fact]
public void SupersededAdmissionCancellationRestoresExactBaselineGeneration()
{
using var fixture = new Fixture(bindGeneration: true);
RuntimePhysicsState physics = fixture.Lifetime.Physics;
RuntimeCollisionAdmission baseline =
physics.BeginCollisionAdmission(PrefixP);
using (PreparedLandblockCollisionGeneration preparedBaseline =
PrepareSealedMutation(physics, baseline, PrefixP))
{
Assert.False(physics.CommitCollisionGeneration(
baseline,
preparedBaseline).Completed);
Assert.True(physics.CommitCollisionGeneration(
baseline,
preparedBaseline).Completed);
}
RuntimeEntityRecord record = fixture.Add(
0x70003107u,
1,
CellP,
new Vector3(17f, 45f, 0f));
RuntimeSetPositionOutcome seeded = fixture.Place(
record,
CellP,
new Vector3(17.5f, 45f, 0f));
Assert.True(physics.SetPosition.AcknowledgeProjection(
seeded.Projection));
RuntimeCollisionAdmission admissionA =
physics.BeginCollisionAdmission(PrefixP);
using PreparedLandblockCollisionGeneration preparedA =
PrepareSealedMutation(physics, admissionA, PrefixP);
RuntimeCollisionAdmission admissionB =
physics.BeginCollisionAdmission(PrefixP);
Assert.Equal(baseline.Generation, admissionB.PreviousGeneration);
using PreparedLandblockCollisionGeneration preparedB =
PrepareSealedMutation(physics, admissionB, PrefixP);
RuntimeCollisionGenerationCommit parked =
physics.CommitCollisionGeneration(admissionB, preparedB);
Assert.False(parked.EngineCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.False(physics.CancelCollisionGeneration(admissionB, preparedB));
Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawal.Token));
Assert.False(physics.CancelCollisionGeneration(admissionB, preparedB));
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot restored));
Assert.Equal(RuntimePlacementProjectionKind.Place, restored.Kind);
Assert.True(physics.SetPosition.AcknowledgeProjection(restored.Token));
Assert.True(physics.CancelCollisionGeneration(admissionB, preparedB));
Assert.True(physics.IsSpatialRoot(record));
Assert.Equal(restored.Token.ExactCellId, record.FullCellId);
Assert.True(physics.Engine.IsLandblockTerrainResident(PrefixP));
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionPrefixQuiescenceCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void CancellationAfterEngineCommitFinishesExactPlaceAckWithoutRollback()
{
using var fixture = new Fixture(bindGeneration: true);
RuntimePhysicsState physics = fixture.Lifetime.Physics;
RuntimeCollisionAdmission baseline =
physics.BeginCollisionAdmission(PrefixP);
using (PreparedLandblockCollisionGeneration preparedBaseline =
PrepareSealedMutation(physics, baseline, PrefixP))
{
Assert.False(physics.CommitCollisionGeneration(
baseline,
preparedBaseline).Completed);
Assert.True(physics.CommitCollisionGeneration(
baseline,
preparedBaseline).Completed);
}
RuntimeEntityRecord record = fixture.Add(
0x70003108u,
1,
CellP,
new Vector3(18f, 46f, 0f));
RuntimeSetPositionOutcome seeded = fixture.Place(
record,
CellP,
new Vector3(18.5f, 46f, 0f));
Assert.True(physics.SetPosition.AcknowledgeProjection(
seeded.Projection));
RuntimeCollisionAdmission admission =
physics.BeginCollisionAdmission(PrefixP);
using PreparedLandblockCollisionGeneration prepared =
PrepareSealedMutation(physics, admission, PrefixP);
int committedNotifications = 0;
physics.CollisionGenerationCommitted += _ => committedNotifications++;
Assert.False(physics.CommitCollisionGeneration(
admission,
prepared).Completed);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawal.Token));
_ = SealMutation(physics, admission, prepared);
RuntimeCollisionGenerationCommit transferred =
physics.CommitCollisionGeneration(admission, prepared);
Assert.True(transferred.EngineCommitted);
Assert.False(transferred.Completed);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot restored));
Assert.False(physics.CancelCollisionGeneration(admission, prepared));
Assert.True(physics.Engine.IsLandblockTerrainResident(PrefixP));
Assert.Equal(0, committedNotifications);
Assert.True(physics.SetPosition.AcknowledgeProjection(restored.Token));
Assert.True(physics.CancelCollisionGeneration(admission, prepared));
Assert.True(physics.IsSpatialRoot(record));
Assert.True(physics.Engine.IsLandblockTerrainResident(PrefixP));
Assert.Equal(1, committedNotifications);
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionPrefixQuiescenceCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
}
[Fact]
public void ColdFirstGenerationCancellationReleasesToUnavailableWithoutDebt()
{
// The injected-engine constructor is an internal fixture seam. It can
// begin with collision resident before Runtime has assigned generation
// one; production Runtime constructs an empty PhysicsEngine.
using var fixture = new Fixture(bindGeneration: true);
RuntimePhysicsState physics = fixture.Lifetime.Physics;
RuntimeEntityRecord record = fixture.Add(
0x70003109u,
1,
CellP,
new Vector3(19f, 47f, 0f));
RuntimeSetPositionOutcome seeded = fixture.Place(
record,
CellP,
new Vector3(19.5f, 47f, 0f));
Assert.True(physics.SetPosition.AcknowledgeProjection(
seeded.Projection));
RuntimeCollisionAdmission admission =
physics.BeginCollisionAdmission(PrefixP);
Assert.Equal(0UL, admission.PreviousGeneration);
using PreparedLandblockCollisionGeneration prepared =
PrepareSealedMutation(physics, admission, PrefixP);
RuntimeCollisionGenerationCommit parked =
physics.CommitCollisionGeneration(admission, prepared);
Assert.False(parked.EngineCommitted);
Assert.True(physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot withdrawal));
Assert.True(physics.SetPosition.AcknowledgeProjection(withdrawal.Token));
Assert.True(physics.CancelCollisionGeneration(admission, prepared));
Assert.True(physics.Engine.IsLandblockTerrainResident(PrefixP));
Assert.False(physics.IsSpatialRoot(record));
RuntimePhysicsOwnershipSnapshot ownership = physics.CaptureOwnership();
Assert.Equal(0, ownership.CollisionPrefixMutationCount);
Assert.Equal(0, ownership.CollisionPrefixQuiescenceCount);
Assert.Equal(0, ownership.PendingCollisionPrefixProjectionCount);
Assert.Equal(0, ownership.CollisionAdmissionCount);
RuntimeSetPositionOwnershipSnapshot placement =
physics.SetPosition.CaptureOwnership();
Assert.Equal(1, placement.UnboundDeferredCellCount);
Assert.Equal(0, placement.DeferredBucketCount);
}
private static PreparedLandblockCollisionGeneration PrepareSealedMutation(
RuntimePhysicsState physics,
RuntimeCollisionAdmission admission,
uint landblock)
{
PreparedLandblockCollisionGeneration prepared =
physics.PrepareCollisionGeneration(admission);
physics.StageCollisionAssets(
admission,
prepared,
new RuntimeLandblockCollisionAssets(
landblock,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
0f,
0f,
0u));
_ = SealMutation(physics, admission, prepared);
return prepared;
}
private static uint[] SealMutation(
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);
}
while (!seal.Completed && !seal.Restarted);
if (seal.Completed)
return [.. prepared.RetainedOwnerIds];
}
}
}

View file

@ -10,7 +10,7 @@ using DatReaderWriter.Types;
namespace AcDream.Runtime.Tests.Physics;
public sealed class RuntimeCollisionPrefixQuiescenceTests
public sealed partial class RuntimeCollisionPrefixQuiescenceTests
{
private const uint PrefixP = 0xA9B40000u;
private const uint CellP = PrefixP | 0x0001u;
@ -68,7 +68,10 @@ public sealed class RuntimeCollisionPrefixQuiescenceTests
Assert.True(fixture.Lifetime.Physics
.IsCollisionPrefixMutationPermissionCurrent(permission));
Assert.True(fixture.Lifetime.Physics
.CompleteCollisionPrefixQuiescence(permission));
.CancelCollisionPrefixQuiescence(
token,
successorGeneration: 2UL,
successorReady: false));
Assert.False(fixture.Lifetime.Physics
.IsCollisionPrefixMutationPermissionCurrent(permission));
}

View file

@ -0,0 +1,44 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using AcDream.Core.Physics;
namespace AcDream.Runtime.Tests.Physics;
public sealed class RuntimePhysicsOwnershipBoundaryTests
{
[Fact]
public void PhysicsWorldRootMutatorsAreRuntimeOnlyProductionInternals()
{
string[] rootMutators =
[
nameof(PhysicsEngine.AddLandblock),
nameof(PhysicsEngine.RemoveLandblock),
nameof(PhysicsEngine.DemoteLandblockToTerrain),
nameof(PhysicsEngine.Clear),
];
Type engine = typeof(PhysicsEngine);
MethodInfo[] publicMethods = engine.GetMethods(
BindingFlags.Instance | BindingFlags.Public);
MethodInfo[] internalMethods = engine.GetMethods(
BindingFlags.Instance | BindingFlags.NonPublic);
foreach (string name in rootMutators)
{
Assert.DoesNotContain(
publicMethods,
method => method.Name == name);
Assert.Contains(
internalMethods,
method => method.Name == name);
}
string[] friends = engine.Assembly
.GetCustomAttributes<InternalsVisibleToAttribute>()
.Select(attribute => attribute.AssemblyName.Split(',')[0])
.ToArray();
Assert.Contains("AcDream.Runtime", friends);
Assert.DoesNotContain("AcDream.App", friends);
Assert.DoesNotContain("acdream-headless", friends);
Assert.Contains("AcDream.App.Tests", friends);
Assert.Contains("AcDream.Headless.Tests", friends);
}
}

View file

@ -336,7 +336,8 @@ public sealed class RuntimePhysicsStateTests
CommitPrepared(first.Physics, newer, prepared));
RuntimeCollisionAcknowledgement withdrawn =
first.Physics.WithdrawCollision(0xA9B4FFFFu);
CompleteWithdrawal(first.Physics, 0xA9B4FFFFu)
.Acknowledgement;
Assert.True(withdrawn.WasResident);
Assert.True(withdrawn.Generation > completed.Generation);
Assert.Equal(0, first.Physics.Engine.LandblockCount);
@ -465,7 +466,8 @@ public sealed class RuntimePhysicsStateTests
uint owner = Assert.Single(SealPrepared(physics, admission, prepared));
physics.Engine.ShadowObjects.UpdatePhysicsState(owner, 0x14u);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
Assert.Equal(15f, physics.Engine.SampleTerrainZ(1f, 1f));
@ -561,6 +563,11 @@ public sealed class RuntimePhysicsStateTests
Assert.True(sealSteps > ownerCount);
Assert.True(workUnits > ownerCount);
Assert.False(physics.CommitCollisionGeneration(
admission,
prepared).Committed);
_ = SealPrepared(physics, admission, prepared);
_ = GC.GetAllocatedBytesForCurrentThread();
long before = GC.GetAllocatedBytesForCurrentThread();
RuntimeCollisionGenerationCommit commit =
@ -662,7 +669,8 @@ public sealed class RuntimePhysicsStateTests
}
while (!seal.Completed);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
Assert.Equal(15f, physics.Engine.SampleTerrainZ(1f, 1f));
@ -742,7 +750,8 @@ public sealed class RuntimePhysicsStateTests
break;
}
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
ShadowEntry[] entries = physics.Engine.ShadowObjects
@ -841,7 +850,8 @@ public sealed class RuntimePhysicsStateTests
}
while (!seal.Completed);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
ShadowEntry[] entries = physics.Engine.ShadowObjects
@ -885,6 +895,10 @@ public sealed class RuntimePhysicsStateTests
ShadowObjectRegistry shadowFacade = physics.Engine.ShadowObjects;
int notifications = 0;
physics.CollisionGenerationCommitted += _ => notifications++;
Assert.False(physics.CommitCollisionGeneration(
admission,
prepared).Committed);
_ = SealPrepared(physics, admission, prepared);
_ = GC.GetAllocatedBytesForCurrentThread();
long before = GC.GetAllocatedBytesForCurrentThread();
RuntimeCollisionGenerationCommit commit =
@ -1049,6 +1063,10 @@ public sealed class RuntimePhysicsStateTests
PhysicsDataCache cacheFacade = physics.DataCache;
CellGraph graphFacade = physics.DataCache.CellGraph;
ShadowObjectRegistry shadowFacade = physics.Engine.ShadowObjects;
Assert.False(physics.CommitCollisionGeneration(
admission,
prepared).Committed);
_ = SealPrepared(physics, admission, prepared);
_ = GC.GetAllocatedBytesForCurrentThread();
long before = GC.GetAllocatedBytesForCurrentThread();
RuntimeCollisionGenerationCommit commit =
@ -1106,9 +1124,15 @@ public sealed class RuntimePhysicsStateTests
_ = SealPrepared(physics, firstAdmission, first);
_ = SealPrepared(physics, secondAdmission, second);
Assert.False(physics.CommitCollisionGeneration(
firstAdmission,
first).Committed);
_ = SealPrepared(physics, firstAdmission, first);
_ = GC.GetAllocatedBytesForCurrentThread();
long firstBefore = GC.GetAllocatedBytesForCurrentThread();
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
firstAdmission,
first).Committed);
long firstAllocated =
@ -1118,10 +1142,15 @@ public sealed class RuntimePhysicsStateTests
Assert.True(physics.Engine.IsLandblockTerrainResident(firstLandblock));
Assert.False(physics.Engine.IsLandblockTerrainResident(secondLandblock));
_ = SealPrepared(physics, secondAdmission, second);
Assert.False(physics.CommitCollisionGeneration(
secondAdmission,
second).Committed);
_ = SealPrepared(physics, secondAdmission, second);
_ = GC.GetAllocatedBytesForCurrentThread();
long secondBefore = GC.GetAllocatedBytesForCurrentThread();
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
secondAdmission,
second).Committed);
long secondAllocated =
@ -1172,7 +1201,8 @@ public sealed class RuntimePhysicsStateTests
_ = SealPrepared(physics, firstAdmission, first);
_ = SealPrepared(physics, secondAdmission, second);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
firstAdmission,
first).Committed);
physics.Engine.ShadowObjects.UpdatePosition(
@ -1185,7 +1215,8 @@ public sealed class RuntimePhysicsStateTests
seedCellId: 0x01010001u);
_ = SealPrepared(physics, secondAdmission, second);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
secondAdmission,
second).Committed);
Assert.Equal(12f, Assert.Single(
@ -1256,11 +1287,13 @@ public sealed class RuntimePhysicsStateTests
_ = SealPrepared(physics, northAdmission, northPrepared);
_ = SealPrepared(physics, southAdmission, southPrepared);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
northAdmission,
northPrepared).Committed);
_ = SealPrepared(physics, southAdmission, southPrepared);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
southAdmission,
southPrepared).Committed);
Assert.True(physics.Engine.ShadowObjects.HasOwnerRowsInLandblock(
@ -1394,7 +1427,8 @@ public sealed class RuntimePhysicsStateTests
physics.Engine.UpdatePlayerCurrCell(secondCell);
ObjCell oldRootCell = physics.DataCache.CellGraph.CurrCell!;
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
@ -1454,7 +1488,8 @@ public sealed class RuntimePhysicsStateTests
destination,
seedCellId: 0x02020001u);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
Assert.False(physics.Engine.ShadowObjects.HasOwnerRowsInLandblock(
@ -1521,7 +1556,8 @@ public sealed class RuntimePhysicsStateTests
seedCellId: 0x02020001u,
isStatic: false);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
ShadowEntry entry = Assert.Single(
@ -1692,8 +1728,8 @@ public sealed class RuntimePhysicsStateTests
preparedThird,
CollisionAssets(replacement));
Assert.True(physics.DemoteCollisionToTerrain(demoted).WasResident);
Assert.True(physics.WithdrawCollision(withdrawn).WasResident);
Assert.True(CompleteDemotion(physics, demoted).WasResident);
Assert.True(CompleteWithdrawal(physics, withdrawn).WasResident);
Assert.True(CommitPrepared(
physics,
third,
@ -1745,7 +1781,8 @@ public sealed class RuntimePhysicsStateTests
}
_ = SealPrepared(physics, retiredAdmission, retiredPrepared);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
retiredAdmission,
retiredPrepared).Committed);
if (beginRebase)
@ -1762,9 +1799,9 @@ public sealed class RuntimePhysicsStateTests
}
if (withdraw)
Assert.True(physics.WithdrawCollision(retired).WasResident);
Assert.True(CompleteWithdrawal(physics, retired).WasResident);
else
Assert.True(physics.DemoteCollisionToTerrain(retired).WasResident);
Assert.True(CompleteDemotion(physics, retired).WasResident);
Assert.True(CommitPrepared(
physics,
@ -1986,6 +2023,11 @@ public sealed class RuntimePhysicsStateTests
while (!seal.Completed);
Assert.Equal(ownerCount, worked);
Assert.False(physics.CommitCollisionGeneration(
admission,
prepared).Committed);
_ = SealPrepared(physics, admission, prepared);
_ = GC.GetAllocatedBytesForCurrentThread();
long before = GC.GetAllocatedBytesForCurrentThread();
RuntimeCollisionGenerationCommit commit =
@ -2050,7 +2092,8 @@ public sealed class RuntimePhysicsStateTests
survivingTarget,
seedCellId: 0x02020001u,
isStatic: false);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
survivingAdmission,
surviving).Committed);
Assert.Contains(
@ -2097,7 +2140,8 @@ public sealed class RuntimePhysicsStateTests
0f,
target,
seedCellId: 0x01010001u);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
Assert.True(physics.Engine.ShadowObjects.HasOwnerRowsInLandblock(
@ -2146,7 +2190,8 @@ public sealed class RuntimePhysicsStateTests
outside,
seedCellId: 0x02020001u);
physics.Engine.ShadowObjects.UpdatePhysicsState(ownerId, 0x55u);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
ShadowEntry entry = Assert.Single(
@ -2217,7 +2262,7 @@ public sealed class RuntimePhysicsStateTests
replacementAdmission,
replacementPrepared);
Assert.True(physics.WithdrawCollision(retired).WasResident);
Assert.True(CompleteWithdrawal(physics, retired).WasResident);
Assert.False(physics.CommitCollisionGeneration(
replacementAdmission,
replacementPrepared).Committed);
@ -2226,7 +2271,8 @@ public sealed class RuntimePhysicsStateTests
physics,
replacementAdmission,
replacementPrepared);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
replacementAdmission,
replacementPrepared).Committed);
Assert.False(physics.Engine.IsLandblockTerrainResident(retired));
@ -2254,7 +2300,8 @@ public sealed class RuntimePhysicsStateTests
uint ordinal = index + 0x1000u;
uint x = ordinal & 0xFFu;
uint y = (ordinal >> 8) & 0xFFu;
_ = physics.WithdrawCollision(
_ = CompleteWithdrawal(
physics,
(x << 24) | (y << 16) | 0xFFFFu);
}
Assert.False(physics.CommitCollisionGeneration(
@ -2272,7 +2319,8 @@ public sealed class RuntimePhysicsStateTests
Assert.True(++steps < 100_000);
}
while (!seal.Completed);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
}
@ -2380,7 +2428,8 @@ public sealed class RuntimePhysicsStateTests
Assert.InRange(seal.WorkUnits, 0, 1);
}
while (!seal.Completed);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
Assert.Empty(physics.Engine.ShadowObjects.AllEntriesForDebug());
@ -2448,7 +2497,8 @@ public sealed class RuntimePhysicsStateTests
seedCellId: 0x01010001u,
isStatic: false);
physics.Engine.ShadowObjects.Deregister(77u);
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
Assert.Empty(physics.Engine.ShadowObjects.AllEntriesForDebug());
@ -2484,7 +2534,8 @@ public sealed class RuntimePhysicsStateTests
seedCellId: 0x01010001u,
isStatic: false);
}
Assert.True(physics.CommitCollisionGeneration(
Assert.True(CompleteSealedCommit(
physics,
admission,
prepared).Committed);
Assert.Equal(
@ -2903,7 +2954,55 @@ public sealed class RuntimePhysicsStateTests
PreparedLandblockCollisionGeneration prepared)
{
_ = SealPrepared(physics, admission, prepared);
return physics.CommitCollisionGeneration(admission, prepared);
return CompleteSealedCommit(physics, admission, prepared);
}
private static RuntimeCollisionGenerationCommit CompleteSealedCommit(
RuntimePhysicsState physics,
RuntimeCollisionAdmission admission,
PreparedLandblockCollisionGeneration prepared)
{
for (int poll = 0; poll < 10_000; poll++)
{
RuntimeCollisionGenerationCommit commit =
physics.CommitCollisionGeneration(admission, prepared);
if (commit.Committed)
return commit;
if (!commit.EngineCommitted)
_ = SealPrepared(physics, admission, prepared);
}
throw new InvalidOperationException(
"Collision generation did not complete its Runtime mutation transaction.");
}
private static RuntimeCollisionMutationResult CompleteDemotion(
RuntimePhysicsState physics,
uint landblockId)
{
for (int poll = 0; poll < 10_000; poll++)
{
RuntimeCollisionMutationResult result =
physics.DemoteCollisionToTerrain(landblockId);
if (result.Completed)
return result;
}
throw new InvalidOperationException(
"Collision demotion did not complete its Runtime mutation transaction.");
}
private static RuntimeCollisionMutationResult CompleteWithdrawal(
RuntimePhysicsState physics,
uint landblockId)
{
for (int poll = 0; poll < 10_000; poll++)
{
RuntimeCollisionMutationResult result =
physics.WithdrawCollision(landblockId);
if (result.Completed)
return result;
}
throw new InvalidOperationException(
"Collision withdrawal did not complete its Runtime mutation transaction.");
}
private static uint[] SealPrepared(

View file

@ -1750,8 +1750,6 @@ public sealed class RuntimeSetPositionStateTests
RuntimePlacementProjectionSnapshot placed = observer.Deltas[^1].Placement;
Assert.Equal(RuntimePlacementProjectionKind.Place, placed.Kind);
Assert.Equal(DestinationIndoorCell, placed.Token.ExactCellId);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
placed.Token));
RuntimePhysicsOwnershipSnapshot final = lifetime.Physics.CaptureOwnership();
Assert.Equal(0, final.DeferredSetPositionBucketCount);
Assert.Equal(0, final.UnboundDeferredSetPositionCellCount);
@ -1872,10 +1870,6 @@ public sealed class RuntimeSetPositionStateTests
Assert.All(observer.Deltas, delta => Assert.Equal(
RuntimePlacementProjectionKind.Place,
delta.Placement.Kind));
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
observer.Deltas[0].Placement.Token));
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
observer.Deltas[1].Placement.Token));
}
[Theory]
@ -2425,32 +2419,48 @@ public sealed class RuntimeSetPositionStateTests
RuntimeCollisionAdmission admission,
PreparedLandblockCollisionGeneration prepared)
{
while (true)
bool engineCommitted = false;
for (int poll = 0; poll < 10_000; poll++)
{
while (!physics.AdvanceCollisionRetainedOwnerCapture(
admission,
prepared).Completed)
if (!engineCommitted)
{
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);
}
while (!seal.Completed && !seal.Restarted);
if (!seal.Completed)
continue;
}
foreach (uint ownerId in prepared.RetainedOwnerIds)
RuntimeCollisionGenerationCommit result =
physics.CommitCollisionGeneration(admission, prepared);
if (result.Completed)
return result;
while (physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot projection))
{
physics.RefreshCollisionRetainedOwner(
admission,
prepared,
ownerId);
Assert.True(physics.SetPosition.AcknowledgeProjection(
projection.Token));
}
RuntimeCollisionSealStep seal;
do
{
seal = physics.AdvanceCollisionGenerationSeal(
admission,
prepared);
}
while (!seal.Completed && !seal.Restarted);
if (seal.Completed)
break;
engineCommitted = result.EngineCommitted;
}
return physics.CommitCollisionGeneration(admission, prepared);
throw new InvalidOperationException(
"Collision generation did not complete its Runtime mutation transaction.");
}
private static void AddSyntheticCell(