feat(runtime): atomically replace collision generations
This commit is contained in:
parent
99bf1751bb
commit
9b0f59bd1b
21 changed files with 2435 additions and 408 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue