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>
85 lines
3.5 KiB
C#
85 lines
3.5 KiB
C#
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
|
|
}
|
|
}
|