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