refactor(runtime): extract the live object frame

This commit is contained in:
Erik 2026-07-22 00:42:26 +02:00
parent 99a3e819c4
commit 4e4aac2c5a
27 changed files with 1217 additions and 371 deletions

View file

@ -1,9 +1,11 @@
using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Streaming;
using AcDream.App.Update;
using AcDream.App.World;
using AcDream.Content.Vfx;
using AcDream.Core.Physics;
using AcDream.Core.World;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Options;
@ -25,10 +27,11 @@ public sealed class RecallTeleportAnimationTests
{
var calls = new List<string>();
var frame = new RetailLiveFrameCoordinator(
_ => calls.Add("objects"),
() => calls.Add("network"),
() => calls.Add("commands"),
() => calls.Add("spatial"));
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
new GpuWorldState(),
new TestLiveSessionFramePhase(() => calls.Add("network")),
new TestPostNetworkCommandFramePhase(() => calls.Add("commands")),
new TestLiveSpatialReconcilePhase(() => calls.Add("spatial")));
frame.Tick(1f / 60f);
@ -39,25 +42,96 @@ public sealed class RecallTeleportAnimationTests
public void LiveFrame_InvalidHostDeltaCannotBlockNetworkDispatch()
{
var deltas = new List<float>();
int networkDispatches = 0;
var calls = new List<string>();
var frame = new RetailLiveFrameCoordinator(
deltas.Add,
() => networkDispatches++,
() => { },
() => { });
new TestLiveObjectFramePhase(delta =>
{
deltas.Add(delta);
calls.Add("objects");
}),
new GpuWorldState(),
new TestLiveSessionFramePhase(() => calls.Add("network")),
new TestPostNetworkCommandFramePhase(() => calls.Add("commands")),
new TestLiveSpatialReconcilePhase(() => calls.Add("spatial")));
frame.Tick(float.NaN);
frame.Tick(float.PositiveInfinity);
frame.Tick(-1f);
Assert.Equal([0f, 0f, 0f], deltas);
Assert.Equal(3, networkDispatches);
Assert.Equal(
Enumerable.Repeat(new[] { "objects", "network", "commands", "spatial" }, 3)
.SelectMany(static frameCalls => frameCalls),
calls);
Assert.Equal(0.0, UpdateFrameClock.NormalizeDeltaSeconds(double.NaN));
Assert.Equal(0.0, UpdateFrameClock.NormalizeDeltaSeconds(double.NegativeInfinity));
Assert.Equal(0.0, UpdateFrameClock.NormalizeDeltaSeconds(double.PositiveInfinity));
Assert.Equal(0.0, UpdateFrameClock.NormalizeDeltaSeconds(double.MaxValue));
}
[Fact]
public void LiveFrame_CommitsTheInboundMutationBatchBeforeCommandsAndReconcile()
{
const uint landblock = 0x0101FFFFu;
var calls = new List<string>();
var world = new GpuWorldState();
long before = world.VisibilityCommitCount;
var frame = new RetailLiveFrameCoordinator(
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
world,
new TestLiveSessionFramePhase(() =>
{
calls.Add("network");
world.PlaceLiveEntityProjection(landblock, Entity(1));
Assert.Equal(before, world.VisibilityCommitCount);
}),
new TestPostNetworkCommandFramePhase(() =>
{
calls.Add("commands");
Assert.Equal(before + 1, world.VisibilityCommitCount);
}),
new TestLiveSpatialReconcilePhase(() =>
{
calls.Add("spatial");
Assert.Equal(before + 1, world.VisibilityCommitCount);
}));
frame.Tick(1f / 60f);
Assert.Equal(["objects", "network", "commands", "spatial"], calls);
Assert.Equal(before + 1, world.VisibilityCommitCount);
Assert.Equal(1, world.PendingLiveEntityCount);
}
[Fact]
public void LiveFrame_SessionFailureDisposesMutationBatchAndSuppressesLaterPhases()
{
const uint landblock = 0x0101FFFFu;
var calls = new List<string>();
var world = new GpuWorldState();
long before = world.VisibilityCommitCount;
var frame = new RetailLiveFrameCoordinator(
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
world,
new TestLiveSessionFramePhase(() =>
{
calls.Add("network");
world.PlaceLiveEntityProjection(landblock, Entity(2));
Assert.Equal(before, world.VisibilityCommitCount);
throw new InvalidOperationException("inbound failure");
}),
new TestPostNetworkCommandFramePhase(() => calls.Add("commands")),
new TestLiveSpatialReconcilePhase(() => calls.Add("spatial")));
InvalidOperationException error = Assert.Throws<InvalidOperationException>(
() => frame.Tick(1f / 60f));
Assert.Equal("inbound failure", error.Message);
Assert.Equal(["objects", "network"], calls);
Assert.Equal(before + 1, world.VisibilityCommitCount);
Assert.Equal(1, world.PendingLiveEntityCount);
}
[Fact]
public void InstalledRecall_HiddenEnterWorldPublishesFinishedCyclicPoseWithoutAdvancingTime()
{
@ -108,4 +182,14 @@ public sealed class RecallTeleportAnimationTests
Quaternion.Normalize(recallTail[index].Orientation),
Quaternion.Normalize(finishedPose[index].Orientation))) < 0.999999f);
}
private static WorldEntity Entity(uint id) => new()
{
Id = id,
ServerGuid = 0x70000000u + id,
SourceGfxObjOrSetupId = 0x02000001u,
Position = Vector3.Zero,
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty<MeshRef>(),
};
}