perf(streaming): reserve destination reveal capacity
Join destination scheduling to the canonical reveal generation, protect its share across every typed frame-budget dimension, and prevent stale work from clearing a replacement reservation. Remove forced incomplete materialization and project retail's centered portal wait cue while the authored tunnel remains active. Tests: Release build clean; 91 focused reservation/reveal tests; full solution 8,158 passed, 5 skipped. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
98f1ac8934
commit
2ff8f844b0
33 changed files with 870 additions and 230 deletions
|
|
@ -1024,14 +1024,14 @@ public sealed class LandblockPresentationPipelineTests
|
|||
MaxGpuUploadBytes: 1_000_000,
|
||||
MaxGlRetireOperations: 1_000,
|
||||
DestinationReserveFraction: 0.75f));
|
||||
controller.PriorityLandblockId = priorityId;
|
||||
controller.BeginDestinationReservation(1, priorityId, 0);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => controller.Tick(0x70, 0x70));
|
||||
Assert.Empty(outbox);
|
||||
Assert.False(state.IsLoaded(laterId));
|
||||
Assert.True(state.IsLoaded(unloadId));
|
||||
|
||||
controller.PriorityLandblockId = 0;
|
||||
controller.EndDestinationReservation(1);
|
||||
controller.Tick(0x70, 0x70);
|
||||
|
||||
Assert.True(state.IsLoaded(priorityId));
|
||||
|
|
|
|||
|
|
@ -46,22 +46,23 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
harness.Controller.OfferDestination(
|
||||
destination,
|
||||
teleportTimestampAdvanced: true);
|
||||
Assert.Empty(harness.Streaming.PriorityWrites);
|
||||
Assert.Empty(harness.Streaming.Reservations);
|
||||
|
||||
harness.Controller.OnTeleportStarted(7);
|
||||
|
||||
Assert.Equal(1, harness.Input.EndCount);
|
||||
Assert.Equal(1, harness.Mode.EnterPortalCount);
|
||||
Assert.Equal(Matrix4x4.Identity, harness.Presentation.BeginProjection);
|
||||
Assert.Equal((0x2021FFFFu, LocalPlayerTeleportController.NearRingRadius),
|
||||
Assert.Single(harness.Streaming.PriorityWrites));
|
||||
Assert.Equal(
|
||||
(1L, 0x20210001u, WorldRevealReadinessBarrier.OutdoorNeighborhoodRadius),
|
||||
Assert.Single(harness.Streaming.Reservations));
|
||||
Assert.True(harness.Reveal.Snapshot.IsActive);
|
||||
Assert.Equal(WorldRevealKind.Portal, harness.Reveal.Snapshot.Kind);
|
||||
|
||||
harness.Controller.OfferDestination(
|
||||
destination,
|
||||
teleportTimestampAdvanced: false);
|
||||
Assert.Single(harness.Streaming.PriorityWrites);
|
||||
Assert.Single(harness.Streaming.Reservations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -89,7 +90,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
Assert.True(harness.Controller.IsActive);
|
||||
Assert.Equal(0x20210001u, harness.Controller.ActiveDestinationCell);
|
||||
Assert.NotNull(harness.Presentation.BeginProjection);
|
||||
Assert.Single(harness.Streaming.PriorityWrites);
|
||||
Assert.Single(harness.Streaming.Reservations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -105,14 +106,14 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
teleportTimestampAdvanced: true);
|
||||
|
||||
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
|
||||
Assert.Empty(harness.Streaming.PriorityWrites);
|
||||
Assert.Empty(harness.Streaming.Reservations);
|
||||
|
||||
harness.Mode.Controller = new PlayerMovementController(new PhysicsEngine());
|
||||
harness.Mode.Controller.SetPosition(Vector3.Zero, 0x20210001u, Vector3.Zero);
|
||||
harness.Controller.Tick(0.016f);
|
||||
|
||||
Assert.Equal(0x20210001u, harness.Controller.ActiveDestinationCell);
|
||||
Assert.Single(harness.Streaming.PriorityWrites);
|
||||
Assert.Single(harness.Streaming.Reservations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -158,7 +159,6 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
Assert.Empty(harness.Streaming.Recenters);
|
||||
Assert.Equal(new Vector3(20f, 30f, 4f), harness.Placement.Position);
|
||||
Assert.Equal(0x20210123u, harness.Placement.CellId);
|
||||
Assert.False(harness.Placement.Forced);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -188,7 +188,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadinessHold_UsesWallClockAndForcesExactlyAtTenSeconds()
|
||||
public void ReadinessHold_NeverRevealsIncompleteWorldAndShowsWaitCueAfterFiveSeconds()
|
||||
{
|
||||
var harness = new Harness(worldReady: false);
|
||||
harness.Controller.OnTeleportStarted(5);
|
||||
|
|
@ -197,15 +197,18 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
teleportTimestampAdvanced: true);
|
||||
harness.Presentation.EmitPlaceWhenReady = true;
|
||||
|
||||
harness.Controller.Tick(9.5f);
|
||||
harness.Controller.Tick(4.9f);
|
||||
Assert.False(Assert.Single(harness.Presentation.WorldReadyValues));
|
||||
Assert.False(harness.Presentation.WaitCueValues[^1]);
|
||||
Assert.Equal(default, harness.Placement.Position);
|
||||
|
||||
harness.Controller.Tick(0.5f);
|
||||
harness.Controller.Tick(0.1f);
|
||||
harness.Controller.Tick(30f);
|
||||
|
||||
Assert.True(harness.Presentation.WorldReadyValues[^1]);
|
||||
Assert.True(harness.Placement.Forced);
|
||||
Assert.Equal(new Vector3(1f, 2f, 3f), harness.Placement.Position);
|
||||
Assert.All(harness.Presentation.WorldReadyValues, Assert.False);
|
||||
Assert.True(harness.Presentation.WaitCueValues[^1]);
|
||||
Assert.Equal(default, harness.Placement.Position);
|
||||
Assert.True(harness.Reveal.WaitCueShown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -223,7 +226,6 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
harness.Controller.Tick(0.016f);
|
||||
|
||||
Assert.True(Index(order, "placement") < Index(order, "[world-reveal] event=materialized"));
|
||||
Assert.True(Index(order, "[world-reveal] event=materialized") < Index(order, "priority-clear"));
|
||||
Assert.Equal(1, harness.Reveal.PortalMaterializationCount);
|
||||
}
|
||||
|
||||
|
|
@ -249,6 +251,8 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
Assert.False(harness.Controller.IsActive);
|
||||
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
|
||||
Assert.Equal(1, harness.Session.LoginCompleteCount);
|
||||
Assert.True(harness.Reveal.Snapshot.Completed);
|
||||
Assert.False(harness.Reveal.Snapshot.Cancelled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -261,7 +265,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
teleportTimestampAdvanced: true);
|
||||
|
||||
harness.Controller.OnTeleportStarted(11);
|
||||
harness.Controller.Tick(LocalPlayerTeleportController.MaximumWorldHoldSeconds);
|
||||
harness.Controller.Tick(30f);
|
||||
|
||||
Assert.Equal(default, harness.Placement.Position);
|
||||
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
|
||||
|
|
@ -318,7 +322,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
harness.Controller.OfferDestination(
|
||||
Position(0x20210001u, 20, 1f, 2f, 3f),
|
||||
teleportTimestampAdvanced: true);
|
||||
harness.Streaming.PriorityClearCount = 0;
|
||||
harness.Streaming.ReservationEnds.Clear();
|
||||
harness.Placement.OnPlace = () => harness.Controller.OnTeleportStarted(21);
|
||||
harness.Presentation.Enqueue(TeleportAnimEvent.Place);
|
||||
|
||||
|
|
@ -327,7 +331,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
Assert.True(harness.Controller.IsActive);
|
||||
Assert.Equal(0u, harness.Controller.ActiveDestinationCell);
|
||||
Assert.Equal(0, harness.Reveal.PortalMaterializationCount);
|
||||
Assert.Equal(1, harness.Streaming.PriorityClearCount);
|
||||
Assert.Single(harness.Streaming.ReservationEnds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -418,7 +422,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
var position = new Vector3(12f, 24f, 6f);
|
||||
Quaternion rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.5f);
|
||||
|
||||
placement.Place(position, cell, rotation, forced: false);
|
||||
placement.Place(position, cell, rotation);
|
||||
|
||||
Assert.Equal(entity.Position, controllerSlot.Controller.Position);
|
||||
Assert.Equal(entity.ParentCellId, controllerSlot.Controller.CellId);
|
||||
|
|
@ -560,7 +564,8 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
prepareCompositeTextures: (_, _) => { },
|
||||
invalidateCompositeTextures: () => { },
|
||||
isSpawnClaimUnhydratable: _ => false,
|
||||
log: order.Add);
|
||||
log: order.Add,
|
||||
streaming: Streaming);
|
||||
Controller = new LocalPlayerTeleportController(
|
||||
Authority,
|
||||
Input,
|
||||
|
|
@ -619,7 +624,9 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class FakeStreaming : ILocalPlayerTeleportStreamingOperations
|
||||
private sealed class FakeStreaming
|
||||
: ILocalPlayerTeleportStreamingOperations,
|
||||
IWorldRevealStreamingScheduler
|
||||
{
|
||||
private readonly List<string> _order;
|
||||
|
||||
|
|
@ -636,9 +643,10 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
public bool RecenterPending;
|
||||
public bool ResetResult = true;
|
||||
public bool LastResetWasSessionEnding;
|
||||
public int PriorityClearCount;
|
||||
public readonly List<(int X, int Y, bool Sealed)> Recenters = new();
|
||||
public readonly List<(uint Landblock, int Radius)> PriorityWrites = new();
|
||||
public readonly List<(long Generation, uint Cell, int Radius)>
|
||||
Reservations = new();
|
||||
public readonly List<long> ReservationEnds = new();
|
||||
|
||||
public bool BeginRecenter(int x, int y, bool isSealedDungeon)
|
||||
{
|
||||
|
|
@ -655,13 +663,17 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
public bool IsSealedDungeon(uint cellId) =>
|
||||
(cellId & 0xFFFFu) >= 0x0100u;
|
||||
|
||||
public void SetPriority(uint landblockId, int radius) =>
|
||||
PriorityWrites.Add((landblockId, radius));
|
||||
public void BeginDestinationReservation(
|
||||
long revealGeneration,
|
||||
uint destinationCell,
|
||||
int requiredRenderRadius) =>
|
||||
Reservations.Add(
|
||||
(revealGeneration, destinationCell, requiredRenderRadius));
|
||||
|
||||
public void ClearPriority()
|
||||
public void EndDestinationReservation(long revealGeneration)
|
||||
{
|
||||
PriorityClearCount++;
|
||||
_order.Add("priority-clear");
|
||||
ReservationEnds.Add(revealGeneration);
|
||||
_order.Add("reservation-end");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -674,16 +686,14 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
public Vector3 Position;
|
||||
public uint CellId;
|
||||
public Quaternion Rotation;
|
||||
public bool Forced;
|
||||
public Action? OnPlace;
|
||||
|
||||
public void Place(Vector3 position, uint cellId, Quaternion rotation, bool forced)
|
||||
public void Place(Vector3 position, uint cellId, Quaternion rotation)
|
||||
{
|
||||
_order.Add("placement");
|
||||
Position = position;
|
||||
CellId = cellId;
|
||||
Rotation = rotation;
|
||||
Forced = forced;
|
||||
OnPlace?.Invoke();
|
||||
}
|
||||
}
|
||||
|
|
@ -731,6 +741,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
public Matrix4x4? BeginProjection;
|
||||
public bool EmitPlaceWhenReady;
|
||||
public readonly List<bool> WorldReadyValues = new();
|
||||
public readonly List<bool> WaitCueValues = new();
|
||||
public int DisposeFailuresRemaining;
|
||||
public int DisposeCount;
|
||||
|
||||
|
|
@ -760,6 +771,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
public void TickTunnel(float deltaSeconds) => _order.Add("tunnel-tick");
|
||||
public void EnterTunnel() => IsPortalViewportVisible = true;
|
||||
public void ExitTunnel() => IsPortalViewportVisible = false;
|
||||
public void SetWaitCue(bool visible) => WaitCueValues.Add(visible);
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ public sealed class StreamingCompletionQueueTests
|
|||
result,
|
||||
estimate,
|
||||
priority,
|
||||
priority == StreamingCompletionPriority.Destination ? 1L : 0L,
|
||||
result.Generation,
|
||||
sequence,
|
||||
Stopwatch.GetTimestamp());
|
||||
|
|
|
|||
|
|
@ -117,6 +117,110 @@ public sealed class StreamingWorkBudgetTests
|
|||
meter.Snapshot.LastLimit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveReveal_ProtectsDestinationShareAcrossEveryCountAndByteDimension()
|
||||
{
|
||||
var meter = new StreamingWorkMeter(
|
||||
Budget(
|
||||
milliseconds: 100,
|
||||
completions: 4,
|
||||
cpuBytes: 100,
|
||||
entityOperations: 8,
|
||||
gpuBytes: 100,
|
||||
retireOperations: 4),
|
||||
static () => 0,
|
||||
timestampFrequency: 1_000,
|
||||
destinationReservationActive: true);
|
||||
|
||||
StreamingWorkCost unreservedShare = new(
|
||||
CompletionAdmissions: 1,
|
||||
AdoptedCpuBytes: 25,
|
||||
EntityOperations: 2,
|
||||
GpuUploadBytes: 25,
|
||||
GlRetireOperations: 1);
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Admitted,
|
||||
meter.TryReserve(unreservedShare, "ordinary"));
|
||||
meter.Complete();
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Yielded,
|
||||
meter.TryReserve(
|
||||
new StreamingWorkCost(CompletionAdmissions: 1),
|
||||
"ordinary-over-reserve"));
|
||||
|
||||
using (meter.EnterLane(StreamingWorkLane.Destination))
|
||||
{
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Admitted,
|
||||
meter.TryReserve(
|
||||
new StreamingWorkCost(
|
||||
CompletionAdmissions: 3,
|
||||
AdoptedCpuBytes: 75,
|
||||
EntityOperations: 6,
|
||||
GpuUploadBytes: 75,
|
||||
GlRetireOperations: 3),
|
||||
"destination"));
|
||||
meter.Complete();
|
||||
}
|
||||
|
||||
StreamingWorkMeterSnapshot snapshot = meter.Snapshot;
|
||||
Assert.Equal(unreservedShare, snapshot.NonDestinationUsed);
|
||||
Assert.Equal(3, snapshot.DestinationUsed.CompletionAdmissions);
|
||||
Assert.Equal(100, snapshot.Used.AdoptedCpuBytes);
|
||||
Assert.Equal(100, snapshot.Used.GpuUploadBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InactiveReveal_DoesNotApplyDestinationReservation()
|
||||
{
|
||||
var meter = new StreamingWorkMeter(
|
||||
Budget(completions: 4),
|
||||
static () => 0,
|
||||
timestampFrequency: 1_000,
|
||||
destinationReservationActive: false);
|
||||
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Admitted,
|
||||
meter.TryReserve(
|
||||
new StreamingWorkCost(CompletionAdmissions: 4),
|
||||
"ordinary"));
|
||||
meter.Complete();
|
||||
|
||||
Assert.Equal(4, meter.Snapshot.NonDestinationUsed.CompletionAdmissions);
|
||||
Assert.Equal(0, meter.Snapshot.DestinationUsed.CompletionAdmissions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveReveal_ProtectsDestinationWallClockAfterOrdinaryShareIsSpent()
|
||||
{
|
||||
long now = 0;
|
||||
var meter = new StreamingWorkMeter(
|
||||
Budget(milliseconds: 100),
|
||||
() => now,
|
||||
timestampFrequency: 1_000,
|
||||
destinationReservationActive: true);
|
||||
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Admitted,
|
||||
meter.TryReserve(default, "ordinary"));
|
||||
now = 25;
|
||||
meter.Complete();
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Yielded,
|
||||
meter.TryReserve(default, "ordinary-after-25ms"));
|
||||
|
||||
using (meter.EnterLane(StreamingWorkLane.Destination))
|
||||
{
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Admitted,
|
||||
meter.TryReserve(default, "destination"));
|
||||
now = 99;
|
||||
meter.Complete();
|
||||
}
|
||||
|
||||
Assert.Equal(2, meter.Snapshot.CompletedOperations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeterUsesInjectedMonotonicClockAndRecordsOverrunAndFailure()
|
||||
{
|
||||
|
|
@ -427,21 +531,32 @@ public sealed class StreamingWorkBudgetTests
|
|||
cpuBytes: 1_000,
|
||||
gpuBytes: 44,
|
||||
retireOperations: 1));
|
||||
controller.PriorityLandblockId = center;
|
||||
controller.BeginDestinationReservation(1, center, 0);
|
||||
|
||||
controller.Tick(32, 32);
|
||||
|
||||
Assert.Equal([center], applied);
|
||||
Assert.Equal(1, controller.WorkDiagnostics.DeferredCompletions);
|
||||
Assert.Equal(1, controller.WorkDiagnostics.LastFrame.YieldCount);
|
||||
Assert.Equal(2, controller.WorkDiagnostics.LastFrame.YieldCount);
|
||||
Assert.Equal(44, controller.WorkDiagnostics.LastFrame.Used.GpuUploadBytes);
|
||||
Assert.Equal(0, controller.WorkDiagnostics.LastFrame.Used.GlRetireOperations);
|
||||
|
||||
controller.Tick(32, 32);
|
||||
|
||||
Assert.Equal([center, ordinary], applied);
|
||||
Assert.Equal(0, controller.WorkDiagnostics.DeferredCompletions);
|
||||
Assert.Equal(0, controller.WorkDiagnostics.LastFrame.Used.GlRetireOperations);
|
||||
|
||||
for (int i = 0;
|
||||
i < 4
|
||||
&& (controller.WorkDiagnostics.DeferredCompletions != 0
|
||||
|| source.BacklogCount != 0);
|
||||
i++)
|
||||
{
|
||||
controller.Tick(32, 32);
|
||||
}
|
||||
|
||||
Assert.Equal(0, controller.WorkDiagnostics.DeferredCompletions);
|
||||
Assert.Equal(0, source.BacklogCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ public sealed class WorldRevealCoordinatorTests
|
|||
public int InvalidateCount { get; private set; }
|
||||
public int PrepareCount { get; private set; }
|
||||
|
||||
public WorldRevealCoordinator Build(List<string>? logs = null) => new(
|
||||
public WorldRevealCoordinator Build(
|
||||
List<string>? logs = null,
|
||||
IWorldRevealStreamingScheduler? streaming = null) => new(
|
||||
isRenderNeighborhoodReady: (_, _) => RenderReady,
|
||||
isSpawnCellReady: _ => CollisionReady,
|
||||
isTerrainNeighborhoodReady: (_, _) => CollisionReady,
|
||||
|
|
@ -25,7 +27,8 @@ public sealed class WorldRevealCoordinatorTests
|
|||
CompositesReady = false;
|
||||
},
|
||||
isSpawnClaimUnhydratable: _ => false,
|
||||
log: logs is null ? null : new Action<string>(logs.Add));
|
||||
log: logs is null ? null : new Action<string>(logs.Add),
|
||||
streaming: streaming);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -34,7 +37,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
var state = new State { CompositesReady = true };
|
||||
WorldRevealCoordinator coordinator = state.Build();
|
||||
|
||||
long generation = coordinator.Begin(WorldRevealKind.Login);
|
||||
long generation = coordinator.Begin(WorldRevealKind.Login, 0x11340021u);
|
||||
|
||||
Assert.Equal(1, generation);
|
||||
Assert.Equal(1, state.InvalidateCount);
|
||||
|
|
@ -52,7 +55,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
CollisionReady = true,
|
||||
};
|
||||
WorldRevealCoordinator coordinator = state.Build();
|
||||
coordinator.Begin(WorldRevealKind.Login);
|
||||
coordinator.Begin(WorldRevealKind.Login, outdoorCell);
|
||||
|
||||
WorldRevealReadinessSnapshot first = coordinator.PrepareAndEvaluate(outdoorCell);
|
||||
state.CompositesReady = true;
|
||||
|
|
@ -76,7 +79,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
CompositesReady = true,
|
||||
};
|
||||
WorldRevealCoordinator coordinator = state.Build(logs);
|
||||
coordinator.Begin(WorldRevealKind.Portal);
|
||||
coordinator.Begin(WorldRevealKind.Portal, outdoorCell);
|
||||
state.CompositesReady = true;
|
||||
|
||||
Assert.True(coordinator.Evaluate(outdoorCell).IsReady);
|
||||
|
|
@ -103,7 +106,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
CompositesReady = true,
|
||||
};
|
||||
WorldRevealCoordinator coordinator = state.Build();
|
||||
coordinator.Begin(WorldRevealKind.Portal);
|
||||
coordinator.Begin(WorldRevealKind.Portal, 0x11340021u);
|
||||
coordinator.Cancel();
|
||||
|
||||
coordinator.PrepareAndEvaluate(0x11340021u);
|
||||
|
|
@ -137,8 +140,8 @@ public sealed class WorldRevealCoordinatorTests
|
|||
isSpawnClaimUnhydratable: _ => false,
|
||||
quiescence: quiescence);
|
||||
|
||||
Assert.Equal(1, coordinator.Begin(WorldRevealKind.Login));
|
||||
Assert.Equal(2, coordinator.Begin(WorldRevealKind.Portal));
|
||||
Assert.Equal(1, coordinator.Begin(WorldRevealKind.Login, 0x11340021u));
|
||||
Assert.Equal(2, coordinator.Begin(WorldRevealKind.Portal, 0x11340021u));
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.Equal(2, availability.QuiescedGeneration);
|
||||
Assert.Equal(1, audio.SuspendCalls);
|
||||
|
|
@ -149,6 +152,30 @@ public sealed class WorldRevealCoordinatorTests
|
|||
Assert.Equal(1, audio.ResumeCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RevealGeneration_OwnsExactDestinationReservationUntilCompletion()
|
||||
{
|
||||
var state = new State();
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
WorldRevealCoordinator coordinator = state.Build(streaming: scheduler);
|
||||
|
||||
long first = coordinator.Begin(
|
||||
WorldRevealKind.Portal,
|
||||
0x11340021u);
|
||||
long second = coordinator.Begin(
|
||||
WorldRevealKind.Portal,
|
||||
0x20210123u);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
(first, 0x11340021u, 1),
|
||||
(second, 0x20210123u, 0),
|
||||
],
|
||||
scheduler.Begins);
|
||||
coordinator.Complete();
|
||||
Assert.Equal([second], scheduler.Ends);
|
||||
}
|
||||
|
||||
private sealed class RecordingAudioQuiescence : AcDream.App.Audio.IWorldAudioQuiescence
|
||||
{
|
||||
public int SuspendCalls { get; private set; }
|
||||
|
|
@ -157,4 +184,21 @@ public sealed class WorldRevealCoordinatorTests
|
|||
public void SuspendWorldAudio() => SuspendCalls++;
|
||||
public void ResumeWorldAudio() => ResumeCalls++;
|
||||
}
|
||||
|
||||
private sealed class RecordingDestinationScheduler
|
||||
: IWorldRevealStreamingScheduler
|
||||
{
|
||||
public List<(long Generation, uint Cell, int Radius)> Begins { get; } = [];
|
||||
public List<long> Ends { get; } = [];
|
||||
|
||||
public void BeginDestinationReservation(
|
||||
long revealGeneration,
|
||||
uint destinationCell,
|
||||
int requiredRenderRadius) =>
|
||||
Begins.Add(
|
||||
(revealGeneration, destinationCell, requiredRenderRadius));
|
||||
|
||||
public void EndDestinationReservation(long revealGeneration) =>
|
||||
Ends.Add(revealGeneration);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
using AcDream.App.UI;
|
||||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
public sealed class PortalWaitNoticeControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Notice_IsCenteredOverlayAndDisposesFromRetainedRoot()
|
||||
{
|
||||
var root = new UiRoot
|
||||
{
|
||||
Width = 1280f,
|
||||
Height = 720f,
|
||||
};
|
||||
|
||||
using (var controller = new PortalWaitNoticeController(root))
|
||||
{
|
||||
UiText text = Assert.IsType<UiText>(Assert.Single(root.Children));
|
||||
Assert.False(text.Visible);
|
||||
Assert.True(text.Centered);
|
||||
Assert.True(text.ClickThrough);
|
||||
Assert.Equal(root.Width, text.Width);
|
||||
Assert.Equal(root.Height, text.Height);
|
||||
|
||||
controller.Set("In Portal Space - Please Wait...");
|
||||
|
||||
Assert.True(text.Visible);
|
||||
UiText.Line line = Assert.Single(text.LinesProvider!());
|
||||
Assert.Equal("In Portal Space - Please Wait...", line.Text);
|
||||
|
||||
controller.Set(null);
|
||||
Assert.False(text.Visible);
|
||||
}
|
||||
|
||||
Assert.Empty(root.Children);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ public class StreamingControllerPriorityApplyTests
|
|||
generation);
|
||||
|
||||
[Fact]
|
||||
public void PriorityLandblock_WinsExecutionOrderAfterBoundedAdmission()
|
||||
public void DestinationReservation_WinsExecutionOrderAfterBoundedAdmission()
|
||||
{
|
||||
uint priority = StreamingRegion.EncodeLandblockIdForTest(169, 180);
|
||||
var outbox = new Queue<LandblockStreamResult>(new LandblockStreamResult[]
|
||||
|
|
@ -48,7 +48,7 @@ public class StreamingControllerPriorityApplyTests
|
|||
workBudgetOptions: GenerousBudget())
|
||||
{ MaxCompletionsPerFrame = 4 };
|
||||
|
||||
ctrl.PriorityLandblockId = priority;
|
||||
ctrl.BeginDestinationReservation(1, priority, 0);
|
||||
ctrl.Tick(169, 180);
|
||||
|
||||
Assert.NotEmpty(applied);
|
||||
|
|
@ -87,10 +87,10 @@ public class StreamingControllerPriorityApplyTests
|
|||
nearRadius: 4,
|
||||
farRadius: 12)
|
||||
{ MaxCompletionsPerFrame = 4 };
|
||||
ctrl.PriorityLandblockId = priority;
|
||||
ctrl.BeginDestinationReservation(1, priority, 0);
|
||||
|
||||
ctrl.Tick(169, 180); // priority + some others
|
||||
ctrl.PriorityLandblockId = 0u;
|
||||
ctrl.EndDestinationReservation(1);
|
||||
ctrl.Tick(169, 180); // drains the deferred remainder
|
||||
ctrl.Tick(169, 180);
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ public class StreamingControllerPriorityApplyTests
|
|||
[Fact]
|
||||
public void PriorityNeverArrives_noThrow_noLoss_noDoubleApply()
|
||||
{
|
||||
// While a PriorityLandblockId is set but the matching completion never
|
||||
// While a reveal reservation is active but its matching completion never
|
||||
// arrives (e.g. the load failed), ordinary results still enter the typed
|
||||
// Near/Far queues and advance under the same budget without loss.
|
||||
uint priority = StreamingRegion.EncodeLandblockIdForTest(169, 180);
|
||||
|
|
@ -134,7 +134,7 @@ public class StreamingControllerPriorityApplyTests
|
|||
{ MaxCompletionsPerFrame = 4 };
|
||||
|
||||
// Set a priority id that will NEVER appear in the outbox.
|
||||
ctrl.PriorityLandblockId = priority;
|
||||
ctrl.BeginDestinationReservation(1, priority, 0);
|
||||
|
||||
// Tick several times — should not throw and every non-priority completion
|
||||
// must still be applied without duplication.
|
||||
|
|
@ -160,6 +160,30 @@ public class StreamingControllerPriorityApplyTests
|
|||
MaxGlRetireOperations: 10_000,
|
||||
DestinationReserveFraction: 0.75f);
|
||||
|
||||
[Fact]
|
||||
public void DestinationReservation_StaleGenerationCannotClearReplacement()
|
||||
{
|
||||
var controller = new StreamingController(
|
||||
enqueueLoad: (_, _) => { },
|
||||
enqueueUnload: _ => { },
|
||||
drainCompletions: _ => Array.Empty<LandblockStreamResult>(),
|
||||
applyTerrain: (_, _) => { },
|
||||
state: new GpuWorldState(),
|
||||
nearRadius: 1,
|
||||
farRadius: 2);
|
||||
|
||||
controller.BeginDestinationReservation(1, 0x11340021u, 1);
|
||||
controller.BeginDestinationReservation(2, 0x20210123u, 0);
|
||||
controller.EndDestinationReservation(1);
|
||||
|
||||
Assert.Equal(2, controller.ActiveRevealGeneration);
|
||||
Assert.Equal(0x2021FFFFu, controller.DestinationLandblockId);
|
||||
Assert.Equal(0, controller.DestinationRadius);
|
||||
|
||||
controller.EndDestinationReservation(2);
|
||||
Assert.Equal(0, controller.ActiveRevealGeneration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeferredCompaction_ApplyFailureRetainsExactResultWithoutReplayingCommittedPrefix()
|
||||
{
|
||||
|
|
@ -199,8 +223,8 @@ public class StreamingControllerPriorityApplyTests
|
|||
farRadius: 12)
|
||||
{
|
||||
MaxCompletionsPerFrame = 3,
|
||||
PriorityLandblockId = priority,
|
||||
};
|
||||
ctrl.BeginDestinationReservation(1, priority, 0);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => ctrl.Tick(169, 180));
|
||||
Assert.Equal([first], applied);
|
||||
|
|
@ -215,7 +239,6 @@ public class StreamingControllerPriorityApplyTests
|
|||
[Fact]
|
||||
public void ForceReloadWindow_DiscardsBufferedCompletionsFromOldWindow()
|
||||
{
|
||||
uint priority = StreamingRegion.EncodeLandblockIdForTest(169, 180);
|
||||
var outbox = new Queue<LandblockStreamResult>(
|
||||
Enumerable.Range(0, 6)
|
||||
.Select(i => (LandblockStreamResult)LoadedOf(
|
||||
|
|
@ -243,8 +266,7 @@ public class StreamingControllerPriorityApplyTests
|
|||
MaxGpuUploadBytes: 1_000_000,
|
||||
MaxGlRetireOperations: 1_000,
|
||||
DestinationReserveFraction: 0.75f))
|
||||
{ MaxCompletionsPerFrame = 4, PriorityLandblockId = priority };
|
||||
|
||||
{ MaxCompletionsPerFrame = 4 };
|
||||
ctrl.Tick(169, 180);
|
||||
Assert.Equal(4, applied.Count);
|
||||
applied.Clear();
|
||||
|
|
@ -452,7 +474,8 @@ public class StreamingControllerPriorityApplyTests
|
|||
state: state,
|
||||
nearRadius: 0,
|
||||
farRadius: 0,
|
||||
onLandblockLoaded: loadedCallbacks.Add);
|
||||
onLandblockLoaded: loadedCallbacks.Add,
|
||||
workBudgetOptions: GenerousBudget());
|
||||
|
||||
ctrl.Tick(10, 10);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue