feat(teleport A): StreamingController priority-apply for the teleport destination LB

Adds PriorityLandblockId (uint, default 0) + _deferredApply buffer.
DrainAndApply now: (1) applies up to budget from the deferred buffer,
(2) when PriorityLandblockId != 0, hunts the worker outbox in chunks
applying the priority LB immediately on match and buffering any
non-priority items drained past it for later frames,
(3) falls back to normal drain when no priority is set or not found.

Extracts ApplyResult(result) + ResultLandblockId(result) helpers so
both the priority and normal paths share identical side-effects.
No existing behaviour changes on the non-priority path.

21 streaming tests pass (19 existing + 2 new priority-apply tests).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-22 12:57:27 +02:00
parent b869128df3
commit 1f6baa6cfc
2 changed files with 228 additions and 28 deletions

View file

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using AcDream.App.Streaming;
using AcDream.Core.Terrain;
using AcDream.Core.World;
using DatReaderWriter.DBObjs;
using Xunit;
namespace AcDream.Core.Tests.Streaming;
public class StreamingControllerPriorityApplyTests
{
private static LandblockStreamResult.Loaded LoadedOf(uint canonicalId)
=> new(canonicalId, LandblockStreamTier.Near,
new LoadedLandblock(canonicalId, new LandBlock(), Array.Empty<WorldEntity>()),
new LandblockMeshData(Array.Empty<TerrainVertex>(), Array.Empty<uint>()));
[Fact]
public void PriorityLandblock_isApplied_evenWhenBeyondPerFrameCap()
{
uint priority = StreamingRegion.EncodeLandblockIdForTest(169, 180);
var outbox = new Queue<LandblockStreamResult>(new LandblockStreamResult[]
{
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 0)),
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 1)),
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 2)),
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 3)),
LoadedOf(priority),
});
var applied = new List<uint>();
var state = new GpuWorldState();
var ctrl = new StreamingController(
enqueueLoad: (_, _) => { },
enqueueUnload: _ => { },
drainCompletions: max =>
{
var batch = new List<LandblockStreamResult>();
while (batch.Count < max && outbox.Count > 0) batch.Add(outbox.Dequeue());
return batch;
},
applyTerrain: (lb, _) => applied.Add(lb.LandblockId),
state: state, nearRadius: 4, farRadius: 12)
{ MaxCompletionsPerFrame = 4 };
ctrl.PriorityLandblockId = priority;
ctrl.Tick(169, 180);
Assert.Contains(priority, applied); // priority applied THIS tick
Assert.True(applied.Count <= 5); // did not blindly flush the whole outbox
}
[Fact]
public void Deferred_nonPriority_completions_applyOnLaterFrames_withoutLoss()
{
// After the priority is found, the non-priority items drained past it must
// still be applied (over subsequent ticks), never dropped.
uint priority = StreamingRegion.EncodeLandblockIdForTest(169, 180);
var outbox = new Queue<LandblockStreamResult>(new LandblockStreamResult[]
{
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 0)),
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 1)),
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 2)),
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 3)),
LoadedOf(StreamingRegion.EncodeLandblockIdForTest(0, 4)),
LoadedOf(priority),
});
var applied = new List<uint>();
var state = new GpuWorldState();
var ctrl = new StreamingController(
(_, _) => { }, _ => { },
max => { var b = new List<LandblockStreamResult>(); while (b.Count < max && outbox.Count > 0) b.Add(outbox.Dequeue()); return b; },
(lb, _) => applied.Add(lb.LandblockId),
state, 4, 12) { MaxCompletionsPerFrame = 4 };
ctrl.PriorityLandblockId = priority;
ctrl.Tick(169, 180); // priority + some others
ctrl.PriorityLandblockId = 0u;
ctrl.Tick(169, 180); // drains the deferred remainder
ctrl.Tick(169, 180);
Assert.Contains(priority, applied);
Assert.Equal(6, applied.Count); // all six applied, none lost
}
}