acdream/tests/AcDream.Runtime.Tests/Gameplay/RuntimeLocalPlayerMovementStateTests.cs

247 lines
8.6 KiB
C#

using System.Numerics;
using AcDream.Core.Physics;
using AcDream.Runtime.Gameplay;
namespace AcDream.Runtime.Tests.Gameplay;
public sealed class RuntimeLocalPlayerMovementStateTests
{
[Fact]
public void ViewProjectsTheExactCanonicalControllerAndAutorunOwner()
{
var controller = new PlayerMovementController(new PhysicsEngine())
{
LocalEntityId = 0x50000001u,
};
controller.SetPosition(
new Vector3(11f, 12f, 13f),
0xA9B40001u,
new Vector3(11f, 12f, 13f));
using var movement = new RuntimeLocalPlayerMovementState
{
Controller = controller,
};
Assert.Same(movement, movement.View);
Assert.True(movement.Execute(RuntimeMovementCommand.ToggleRunLock));
RuntimeMovementSnapshot snapshot = movement.View.Snapshot;
Assert.True(snapshot.HasController);
Assert.Equal(0x50000001u, snapshot.LocalEntityId);
Assert.Equal(controller.CellPosition, snapshot.Position);
Assert.Equal(controller.BodyVelocity, snapshot.Velocity);
Assert.Equal(controller.IsAirborne, snapshot.IsAirborne);
Assert.Equal(controller.SimTimeSeconds, snapshot.SimulationTimeSeconds);
Assert.True(snapshot.AutoRunActive);
Assert.Equal(movement.Revision, snapshot.Revision);
}
[Fact]
public void GraphicalAndDirectCommandsMutateOneAutorunLatch()
{
using var movement = new RuntimeLocalPlayerMovementState();
var input = new MovementInput(
Forward: true,
Run: true);
Assert.True(movement.Execute(RuntimeMovementCommand.ToggleRunLock));
movement.SetCommandInput(input);
Assert.True(movement.AutoRunActive);
Assert.True(movement.View.Snapshot.AutoRunActive);
Assert.True(movement.HasCommandInput);
Assert.Equal(input, movement.CommandInput);
Assert.Equal(input, movement.View.Snapshot.CommandInput);
Assert.True(movement.Execute(RuntimeMovementCommand.Stop));
Assert.False(movement.AutoRunActive);
Assert.False(movement.View.Snapshot.AutoRunActive);
Assert.False(movement.HasCommandInput);
Assert.False(movement.View.Snapshot.HasCommandInput);
}
[Fact]
public void CommandInputIsDeduplicatedAndResetWithSessionIntent()
{
using var movement = new RuntimeLocalPlayerMovementState();
var input = new MovementInput(
StrafeLeft: true,
Run: true);
movement.SetCommandInput(input);
long revision = movement.Revision;
movement.SetCommandInput(input);
Assert.Equal(revision, movement.Revision);
Assert.True(movement.Snapshot.HasCommandInput);
movement.ResetInputIntent();
Assert.False(movement.HasCommandInput);
Assert.Equal(default, movement.CommandInput);
Assert.Equal(revision + 1, movement.Revision);
}
[Theory]
[InlineData(RuntimeMovementCommand.Ready, MotionCommand.Ready)]
[InlineData(RuntimeMovementCommand.Crouch, MotionCommand.Crouch)]
[InlineData(RuntimeMovementCommand.Sit, MotionCommand.Sitting)]
[InlineData(RuntimeMovementCommand.Sleep, MotionCommand.Sleeping)]
public void StanceCommandsUseCanonicalControllerAndEmitOneMovementEdge(
RuntimeMovementCommand command,
uint expectedMotion)
{
var controller = new PlayerMovementController(new PhysicsEngine());
using var movement = new RuntimeLocalPlayerMovementState
{
Controller = controller,
};
movement.SetCommandInput(
new MovementInput(Forward: true, Run: true));
Assert.True(movement.Execute(command));
Assert.False(movement.HasCommandInput);
Assert.Equal(
expectedMotion,
controller.Motion.RawState.ForwardCommand);
MovementResult first = controller.Update(
1f / 60f,
default);
MovementResult second = controller.Update(
1f / 60f,
default);
Assert.True(first.ShouldSendMovementEvent);
Assert.False(second.ShouldSendMovementEvent);
if (command == RuntimeMovementCommand.Ready)
{
Assert.Null(first.ForwardCommand);
}
else
{
Assert.Equal(expectedMotion, first.ForwardCommand);
}
}
[Fact]
public void ConcurrentRuntimeInstancesHaveIndependentMovementState()
{
using var first = new RuntimeLocalPlayerMovementState();
using var second = new RuntimeLocalPlayerMovementState();
var firstController = new PlayerMovementController(new PhysicsEngine())
{
LocalEntityId = 1u,
};
var secondController = new PlayerMovementController(new PhysicsEngine())
{
LocalEntityId = 2u,
};
first.Controller = firstController;
second.Controller = secondController;
first.Execute(RuntimeMovementCommand.ToggleRunLock);
firstController.SetPosition(Vector3.One, 0xA9B40001u, Vector3.One);
secondController.SetPosition(
new Vector3(2f),
0xA9B50001u,
new Vector3(2f));
Assert.True(first.Snapshot.AutoRunActive);
Assert.False(second.Snapshot.AutoRunActive);
Assert.Equal(1u, first.Snapshot.LocalEntityId);
Assert.Equal(2u, second.Snapshot.LocalEntityId);
Assert.NotEqual(first.Snapshot.Position, second.Snapshot.Position);
}
[Fact]
public void MotionPreparationPublishesOnlyTheConstructionSeam()
{
using var movement = new RuntimeLocalPlayerMovementState();
IRuntimeLocalPlayerMotionSource source = movement;
var committed = new PlayerMovementController(new PhysicsEngine());
var candidate = new PlayerMovementController(new PhysicsEngine());
movement.Controller = committed;
bool drained = false;
using (movement.BeginMotionPreparation(
candidate,
() =>
{
drained = true;
Assert.Same(committed.Motion, source.Motion);
}))
{
Assert.True(drained);
Assert.Same(committed, movement.Controller);
Assert.Same(candidate.Motion, source.Motion);
}
Assert.Same(committed.Motion, source.Motion);
}
[Fact]
public void TerminalDisposalConvergesWhenPreparationLeaseUnwindsLater()
{
var movement = new RuntimeLocalPlayerMovementState();
var controller = new PlayerMovementController(new PhysicsEngine());
IDisposable preparation = movement.BeginMotionPreparation(controller);
movement.Controller = controller;
movement.Execute(RuntimeMovementCommand.ToggleRunLock);
movement.Dispose();
preparation.Dispose();
RuntimeLocalMovementOwnershipSnapshot ownership =
movement.CaptureOwnership();
Assert.True(ownership.IsConverged);
Assert.Throws<ObjectDisposedException>(
() => movement.Execute(RuntimeMovementCommand.ToggleRunLock));
}
[Fact]
public void TypedSkillOptionsPreserveCompleteValuesAndFallbackPerField()
{
Assert.Equal(
new PlayerMovementConstructionOptions(321, 654),
PlayerMovementConstructionOptions.From(
new RuntimeMovementSkillSnapshot(321, 654, 1)));
Assert.Equal(
new PlayerMovementConstructionOptions(
PlayerMovementConstructionOptions.FallbackRunSkill,
654),
PlayerMovementConstructionOptions.From(
new RuntimeMovementSkillSnapshot(-1, 654, 1)));
Assert.Equal(
new PlayerMovementConstructionOptions(
321,
PlayerMovementConstructionOptions.FallbackJumpSkill),
PlayerMovementConstructionOptions.From(
new RuntimeMovementSkillSnapshot(321, -1, 1)));
}
[Fact]
public void WarmMovementSnapshotsAndOwnershipCaptureAllocateNothing()
{
using var movement = new RuntimeLocalPlayerMovementState();
movement.Controller = new PlayerMovementController(new PhysicsEngine())
{
LocalEntityId = 7u,
};
for (int i = 0; i < 1_000; i++)
{
_ = movement.Snapshot;
_ = movement.CaptureOwnership();
}
long before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < 100_000; i++)
{
_ = movement.Snapshot;
_ = movement.CaptureOwnership();
}
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
Assert.Equal(0L, allocated);
}
}