feat(streaming): enforce typed completion queues

Replace the flat deferred list, priority scan, unload bypass, and count-only execution cap with exact destination/control/unload/Near/Far FIFOs behind one typed frame meter. Price worker results before adoption, retain exact retry identity, reject stale generations without payload retention, and publish queue pressure through lifecycle diagnostics.

Tests: dotnet build AcDream.slnx -c Release --no-restore; dotnet test AcDream.slnx -c Release --no-restore (8138 passed, 5 skipped)
This commit is contained in:
Erik 2026-07-24 17:48:24 +02:00
parent ac45cb1bd7
commit b8f6317fe1
15 changed files with 1261 additions and 336 deletions

View file

@ -1004,7 +1004,15 @@ public sealed class LandblockPresentationPipelineTests
state,
nearRadius: 0,
farRadius: 2,
removeTerrain: id => calls.Add($"unload:{id:X8}"));
removeTerrain: id => calls.Add($"unload:{id:X8}"),
workBudgetOptions: new StreamingWorkBudgetOptions(
MaxUpdateMilliseconds: 100,
MaxCompletionAdmissions: 3,
MaxAdoptedCpuBytes: 1_000_000,
MaxEntityOperations: 1_000,
MaxGpuUploadBytes: 1_000_000,
MaxGlRetireOperations: 1_000,
DestinationReserveFraction: 0.75f));
controller.PriorityLandblockId = priorityId;
Assert.Throws<InvalidOperationException>(() => controller.Tick(0x70, 0x70));
@ -1021,8 +1029,8 @@ public sealed class LandblockPresentationPipelineTests
Assert.Equal(
[
$"publish:{priorityId:X8}",
$"publish:{laterId:X8}",
$"unload:{unloadId:X8}",
$"publish:{laterId:X8}",
],
calls);
}

View file

@ -0,0 +1,207 @@
using System.Diagnostics;
using AcDream.App.Streaming;
namespace AcDream.App.Tests.Streaming;
public sealed class StreamingCompletionQueueTests
{
[Fact]
public void PrioritySelectionIsStableFifoAndSkipsOnlyBlockedHead()
{
var queue = new StreamingCompletionQueue();
StreamingQueuedCompletion farFirst = Completion(
new LandblockStreamResult.Failed(1, "far-first"),
StreamingCompletionPriority.Far,
sequence: 1);
StreamingQueuedCompletion nearBlocked = Completion(
new LandblockStreamResult.Failed(2, "near-blocked"),
StreamingCompletionPriority.Near,
sequence: 2);
StreamingQueuedCompletion nearTail = Completion(
new LandblockStreamResult.Failed(3, "near-tail"),
StreamingCompletionPriority.Near,
sequence: 3);
StreamingQueuedCompletion destinationFirst = Completion(
new LandblockStreamResult.Failed(4, "destination-first"),
StreamingCompletionPriority.Destination,
sequence: 4);
StreamingQueuedCompletion destinationTail = Completion(
new LandblockStreamResult.Failed(5, "destination-tail"),
StreamingCompletionPriority.Destination,
sequence: 5);
queue.Enqueue(farFirst);
queue.Enqueue(nearBlocked);
queue.Enqueue(nearTail);
queue.Enqueue(destinationFirst);
queue.Enqueue(destinationTail);
Assert.True(queue.TryPeekNext(
static _ => false,
out StreamingQueuedCompletion? selected));
Assert.Equal(destinationFirst.Sequence, selected?.Sequence);
Assert.Same(destinationFirst.Result, selected?.Result);
queue.RemoveHead(destinationFirst);
Assert.True(queue.TryPeekNext(
result => ReferenceEquals(result, destinationTail.Result)
|| ReferenceEquals(result, nearBlocked.Result),
out selected));
Assert.Equal(farFirst.Sequence, selected?.Sequence);
Assert.Same(farFirst.Result, selected?.Result);
Assert.Throws<InvalidOperationException>(() =>
queue.RemoveHead(nearTail));
Assert.True(queue.TryPeekNext(
static _ => false,
out selected));
Assert.Equal(destinationTail.Sequence, selected?.Sequence);
Assert.Same(destinationTail.Result, selected?.Result);
queue.RemoveHead(destinationTail);
Assert.True(queue.TryPeekNext(
static _ => false,
out selected));
Assert.Equal(nearBlocked.Sequence, selected?.Sequence);
Assert.Same(nearBlocked.Result, selected?.Result);
}
[Fact]
public void RetainedBytesAndPriorityFactsTrackExactQueueLifetime()
{
var queue = new StreamingCompletionQueue();
StreamingQueuedCompletion destination = Completion(
new LandblockStreamResult.Failed(10, "destination"),
StreamingCompletionPriority.Destination,
sequence: 1,
adoptedBytes: 12);
StreamingQueuedCompletion unload = Completion(
new LandblockStreamResult.Unloaded(11),
StreamingCompletionPriority.Unload,
sequence: 2,
adoptedBytes: 20);
StreamingQueuedCompletion near = Completion(
new LandblockStreamResult.Failed(12, "near"),
StreamingCompletionPriority.Near,
sequence: 3,
adoptedBytes: 30);
queue.Enqueue(destination);
queue.Enqueue(unload);
queue.Enqueue(near);
StreamingCompletionQueueSnapshot snapshot = queue.CaptureSnapshot();
Assert.Equal(3, snapshot.Count);
Assert.Equal(62, snapshot.RetainedCpuBytes);
Assert.Equal(1, snapshot.Destination);
Assert.Equal(1, snapshot.Unload);
Assert.Equal(1, snapshot.Near);
Assert.True(snapshot.OldestAgeMilliseconds >= 0);
queue.RemoveHead(destination);
snapshot = queue.CaptureSnapshot();
Assert.Equal(2, snapshot.Count);
Assert.Equal(50, snapshot.RetainedCpuBytes);
queue.Clear();
snapshot = queue.CaptureSnapshot();
Assert.Equal(0, snapshot.Count);
Assert.Equal(0, snapshot.RetainedCpuBytes);
Assert.Equal(0, snapshot.OldestAgeMilliseconds);
}
[Fact]
public void ExactReferenceRemovalDoesNotConflateEqualRecords()
{
var queue = new StreamingCompletionQueue();
var firstResult =
new LandblockStreamResult.Failed(20, "same", Generation: 7);
var equalButDistinctResult =
new LandblockStreamResult.Failed(20, "same", Generation: 7);
StreamingQueuedCompletion first = Completion(
firstResult,
StreamingCompletionPriority.Control,
sequence: 1,
adoptedBytes: 11);
StreamingQueuedCompletion second = Completion(
equalButDistinctResult,
StreamingCompletionPriority.Control,
sequence: 2,
adoptedBytes: 13);
queue.Enqueue(first);
queue.Enqueue(second);
int removed = queue.RemoveResults(
[equalButDistinctResult],
static _ => true);
Assert.Equal(1, removed);
Assert.Equal(1, queue.Count);
Assert.Equal(11, queue.RetainedCpuBytes);
Assert.True(queue.TryPeekNext(
static _ => false,
out StreamingQueuedCompletion? selected));
Assert.Equal(first.Sequence, selected?.Sequence);
Assert.Same(first.Result, selected?.Result);
}
[Fact]
public void CallbackAppendNeverInvalidatesActiveHead()
{
var queue = new StreamingCompletionQueue();
StreamingQueuedCompletion active = Completion(
new LandblockStreamResult.Failed(30, "active"),
StreamingCompletionPriority.Near,
sequence: 1);
StreamingQueuedCompletion appended = Completion(
new LandblockStreamResult.Failed(31, "appended"),
StreamingCompletionPriority.Near,
sequence: 2);
queue.Enqueue(active);
Assert.True(queue.TryPeekNext(
static _ => false,
out StreamingQueuedCompletion? selected));
Assert.Equal(active.Sequence, selected?.Sequence);
Assert.Same(active.Result, selected?.Result);
queue.Enqueue(appended);
queue.RemoveHead(active);
Assert.True(queue.TryPeekNext(
static _ => false,
out selected));
Assert.Equal(appended.Sequence, selected?.Sequence);
Assert.Same(appended.Result, selected?.Result);
}
private static StreamingQueuedCompletion Completion(
LandblockStreamResult result,
StreamingCompletionPriority priority,
long sequence,
long adoptedBytes = 0)
{
var estimate = new LandblockStreamCostEstimate(
Work: new StreamingWorkCost(
CompletionAdmissions: 1,
AdoptedCpuBytes: adoptedBytes),
TerrainPayloadBytes: 0,
Entities: 0,
MeshReferences: 0,
VisibilityCells: 0,
EnvCellShells: 0,
PortalConnections: 0,
PortalPolygonVertices: 0,
PhysicsEnvCells: 0,
PhysicsEnvironments: 0,
PhysicsSetups: 0,
PhysicsGfxObjects: 0);
return new StreamingQueuedCompletion(
result,
estimate,
priority,
result.Generation,
sequence,
Stopwatch.GetTimestamp());
}
}

View file

@ -83,6 +83,40 @@ public sealed class StreamingWorkBudgetTests
snapshot.LastLimit);
}
[Fact]
public void EnsureProgressCanCrossTheLimitOnlyOncePerFrame()
{
var meter = new StreamingWorkMeter(
Budget(cpuBytes: 1),
static () => 0,
timestampFrequency: 1_000);
Assert.Equal(
StreamingWorkAdmission.Admitted,
meter.TryReserve(
new StreamingWorkCost(CompletionAdmissions: 1),
"admission"));
meter.Complete();
Assert.Equal(
StreamingWorkAdmission.Admitted,
meter.TryReserve(
new StreamingWorkCost(AdoptedCpuBytes: 1),
"first-execution",
ensureProgress: true));
meter.Complete();
Assert.Equal(
StreamingWorkAdmission.Yielded,
meter.TryReserve(
new StreamingWorkCost(AdoptedCpuBytes: 1),
"second-execution",
ensureProgress: true));
Assert.Equal(1, meter.Snapshot.YieldCount);
Assert.Equal(
StreamingWorkLimit.AdoptedCpuBytes,
meter.Snapshot.LastLimit);
}
[Fact]
public void MeterUsesInjectedMonotonicClockAndRecordsOverrunAndFailure()
{
@ -129,6 +163,64 @@ public sealed class StreamingWorkBudgetTests
timestampFrequency: 1_000));
}
[Theory]
[InlineData(2, 1.0, 2, 50, 4, 50, 2)]
[InlineData(4, 2.0, 4, 100, 8, 100, 4)]
[InlineData(6, 3.0, 6, 150, 12, 150, 6)]
public void LegacyCompletionSelectorScalesTheWholeProfile(
int count,
double milliseconds,
int admissions,
long cpuBytes,
int entityOperations,
long gpuBytes,
int retireOperations)
{
var options = new StreamingWorkBudgetOptions(
MaxUpdateMilliseconds: 2,
MaxCompletionAdmissions: 4,
MaxAdoptedCpuBytes: 100,
MaxEntityOperations: 8,
MaxGpuUploadBytes: 100,
MaxGlRetireOperations: 4,
DestinationReserveFraction: 0.75f);
StreamingWorkBudgetOptions scaled =
options.ScaleForLegacyCompletionCount(count);
Assert.Equal(milliseconds, scaled.MaxUpdateMilliseconds);
Assert.Equal(admissions, scaled.MaxCompletionAdmissions);
Assert.Equal(cpuBytes, scaled.MaxAdoptedCpuBytes);
Assert.Equal(entityOperations, scaled.MaxEntityOperations);
Assert.Equal(gpuBytes, scaled.MaxGpuUploadBytes);
Assert.Equal(retireOperations, scaled.MaxGlRetireOperations);
Assert.Equal(0.75f, scaled.DestinationReserveFraction);
}
[Fact]
public void LegacyCompletionSelectorRejectsZeroAndSaturatesHugeProfiles()
{
var options = new StreamingWorkBudgetOptions(
MaxUpdateMilliseconds: 2,
MaxCompletionAdmissions: int.MaxValue,
MaxAdoptedCpuBytes: long.MaxValue,
MaxEntityOperations: int.MaxValue,
MaxGpuUploadBytes: long.MaxValue,
MaxGlRetireOperations: int.MaxValue,
DestinationReserveFraction: 0.75f);
Assert.Throws<ArgumentOutOfRangeException>(() =>
options.ScaleForLegacyCompletionCount(0));
StreamingWorkBudgetOptions scaled =
options.ScaleForLegacyCompletionCount(int.MaxValue);
Assert.Equal(int.MaxValue, scaled.MaxCompletionAdmissions);
Assert.Equal(long.MaxValue, scaled.MaxAdoptedCpuBytes);
Assert.Equal(int.MaxValue, scaled.MaxEntityOperations);
Assert.Equal(long.MaxValue, scaled.MaxGpuUploadBytes);
Assert.Equal(int.MaxValue, scaled.MaxGlRetireOperations);
}
[Fact]
public void CompletionCostChargesExactArraysAndDeterministicLogicalEntries()
{
@ -239,10 +331,15 @@ public sealed class StreamingWorkBudgetTests
applyTerrain: static (_, _) => { },
state: new GpuWorldState(),
nearRadius: 1,
farRadius: 1)
{
MaxCompletionsPerFrame = 1,
};
farRadius: 1,
workBudgetOptions: new StreamingWorkBudgetOptions(
MaxUpdateMilliseconds: 100,
MaxCompletionAdmissions: 2,
MaxAdoptedCpuBytes: 1_000,
MaxEntityOperations: 100,
MaxGpuUploadBytes: 44,
MaxGlRetireOperations: 100,
DestinationReserveFraction: 0.75f));
controller.Tick(32, 32);
@ -250,9 +347,10 @@ public sealed class StreamingWorkBudgetTests
Assert.Equal(1, first.DeferredCompletions);
Assert.Equal(44, first.DeferredAdoptedCpuBytes);
Assert.True(first.OldestDeferredAgeMilliseconds >= 0);
Assert.Equal(1, first.LastFrame.Operations);
Assert.Equal(1, first.LastFrame.CompletedOperations);
Assert.Equal(44, first.LastFrame.Used.AdoptedCpuBytes);
Assert.Equal(3, first.LastFrame.Operations);
Assert.Equal(3, first.LastFrame.CompletedOperations);
Assert.Equal(1, first.LastFrame.YieldCount);
Assert.Equal(88, first.LastFrame.Used.AdoptedCpuBytes);
controller.Tick(32, 32);
@ -263,6 +361,155 @@ public sealed class StreamingWorkBudgetTests
Assert.Equal(1, second.LastFrame.CompletedOperations);
}
[Fact]
public void ControllerBoundsProductionAdmissionByCountBeforeAdoption()
{
uint center = StreamingRegion.EncodeLandblockIdForTest(32, 32);
var source = new QueueCompletionSource(
Loaded(center),
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 33)),
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 34)));
int applied = 0;
StreamingController controller = Controller(
source,
() => applied++,
WorkOptions(admissions: 2, cpuBytes: 1_000));
controller.Tick(32, 32);
Assert.Equal(2, applied);
Assert.Equal(1, source.BacklogCount);
Assert.Equal(1, controller.WorkDiagnostics.WorkerCompletionBacklog);
Assert.Equal(2, controller.WorkDiagnostics.LastFrame.Used.CompletionAdmissions);
Assert.Equal(0, controller.WorkDiagnostics.DeferredCompletions);
}
[Fact]
public void ControllerBoundsProductionAdmissionByRetainedCpuBytes()
{
uint center = StreamingRegion.EncodeLandblockIdForTest(32, 32);
var source = new QueueCompletionSource(
Loaded(center),
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 33)),
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 34)));
int applied = 0;
StreamingController controller = Controller(
source,
() => applied++,
WorkOptions(admissions: 64, cpuBytes: 44));
controller.Tick(32, 32);
Assert.Equal(1, applied);
Assert.Equal(2, source.BacklogCount);
Assert.Equal(44, controller.WorkDiagnostics.LastFrame.Used.AdoptedCpuBytes);
Assert.Equal(
StreamingWorkLimit.AdoptedCpuBytes,
controller.WorkDiagnostics.LastFrame.LastLimit);
}
[Fact]
public void DestinationAndUnloadPriorityNeverBypassExecutionBudgets()
{
uint center = StreamingRegion.EncodeLandblockIdForTest(32, 32);
uint ordinary = StreamingRegion.EncodeLandblockIdForTest(33, 33);
var source = new QueueCompletionSource(
Loaded(ordinary),
Loaded(center),
new LandblockStreamResult.Unloaded(0x1111FFFFu),
new LandblockStreamResult.Unloaded(0x2222FFFFu));
var applied = new List<uint>();
StreamingController controller = Controller(
source,
id => applied.Add(id),
WorkOptions(
admissions: 4,
cpuBytes: 1_000,
gpuBytes: 44,
retireOperations: 1));
controller.PriorityLandblockId = center;
controller.Tick(32, 32);
Assert.Equal([center], applied);
Assert.Equal(2, controller.WorkDiagnostics.DeferredCompletions);
Assert.Equal(1, controller.WorkDiagnostics.LastFrame.YieldCount);
Assert.Equal(44, controller.WorkDiagnostics.LastFrame.Used.GpuUploadBytes);
Assert.Equal(1, controller.WorkDiagnostics.LastFrame.Used.GlRetireOperations);
controller.Tick(32, 32);
Assert.Equal([center, ordinary], applied);
Assert.Equal(0, controller.WorkDiagnostics.DeferredCompletions);
Assert.Equal(1, controller.WorkDiagnostics.LastFrame.Used.GlRetireOperations);
}
[Fact]
public void StaleGenerationResultsConsumeAdmissionButRetainNoPayload()
{
uint center = StreamingRegion.EncodeLandblockIdForTest(32, 32);
var source = new QueueCompletionSource(
Loaded(center, generation: 1),
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 33), generation: 1),
Loaded(center));
int applied = 0;
StreamingController controller = Controller(
source,
() => applied++,
WorkOptions(admissions: 2, cpuBytes: 44));
controller.Tick(32, 32);
Assert.Equal(0, applied);
Assert.Equal(1, source.BacklogCount);
Assert.Equal(0, controller.WorkDiagnostics.DeferredCompletions);
Assert.Equal(0, controller.WorkDiagnostics.DeferredAdoptedCpuBytes);
Assert.Equal(2, controller.WorkDiagnostics.LastFrame.Used.CompletionAdmissions);
Assert.Equal(0, controller.WorkDiagnostics.LastFrame.Used.AdoptedCpuBytes);
controller.Tick(32, 32);
Assert.Equal(1, applied);
}
private static StreamingController Controller(
ILandblockCompletionSource source,
Action applied,
StreamingWorkBudgetOptions options) =>
Controller(source, _ => applied(), options);
private static StreamingController Controller(
ILandblockCompletionSource source,
Action<uint> applied,
StreamingWorkBudgetOptions options)
{
var state = new GpuWorldState();
var presentation = new LandblockPresentationPipeline(
(build, _) => applied(build.Landblock.LandblockId),
state);
return new StreamingController(
enqueueLoad: static (_, _, _) => { },
enqueueUnload: static (_, _) => { },
completionSource: source,
state,
nearRadius: 1,
farRadius: 1,
presentationPipeline: presentation,
workBudgetOptions: options);
}
private static StreamingWorkBudgetOptions WorkOptions(
int admissions,
long cpuBytes,
long gpuBytes = 1_000,
int retireOperations = 100) => new(
MaxUpdateMilliseconds: 100,
MaxCompletionAdmissions: admissions,
MaxAdoptedCpuBytes: cpuBytes,
MaxEntityOperations: 100,
MaxGpuUploadBytes: gpuBytes,
MaxGlRetireOperations: retireOperations,
DestinationReserveFraction: 0.75f);
private static WorldEntity Entity(uint id, int meshRefs)
{
var refs = new MeshRef[meshRefs];
@ -278,7 +525,9 @@ public sealed class StreamingWorkBudgetTests
};
}
private static LandblockStreamResult.Loaded Loaded(uint landblockId) => new(
private static LandblockStreamResult.Loaded Loaded(
uint landblockId,
ulong generation = 0) => new(
landblockId,
LandblockStreamTier.Near,
new LoadedLandblock(
@ -287,5 +536,39 @@ public sealed class StreamingWorkBudgetTests
Array.Empty<WorldEntity>()),
new LandblockMeshData(
new TerrainVertex[1],
new uint[1]));
new uint[1]),
generation);
private sealed class QueueCompletionSource(
params LandblockStreamResult[] results)
: ILandblockCompletionSource
{
private readonly Queue<LandblockStreamResult> _results = new(results);
public int BacklogCount => _results.Count;
public bool TryPeek(out LandblockStreamResult? result)
{
if (_results.TryPeek(out LandblockStreamResult? peeked))
{
result = peeked;
return true;
}
result = null;
return false;
}
public bool TryRead(out LandblockStreamResult? result)
{
if (_results.TryDequeue(out LandblockStreamResult? consumed))
{
result = consumed;
return true;
}
result = null;
return false;
}
}
}

View file

@ -263,6 +263,34 @@ public class LandblockStreamerTests
Assert.Equal(43ul, unloaded.Generation);
}
[Fact]
public async Task CompletionSourcePeekPreservesExactResultAndBacklog()
{
using var streamer = new LandblockStreamer(loadLandblock: _ => null);
streamer.Start();
streamer.EnqueueUnload(0xABCE0000u, generation: 44);
for (int i = 0;
i < SpinMaxIterations && streamer.BacklogCount == 0;
i++)
{
await Task.Delay(SpinStepMs);
}
Assert.Equal(1, streamer.BacklogCount);
Assert.True(streamer.TryPeek(out LandblockStreamResult? firstPeek));
Assert.NotNull(firstPeek);
Assert.Equal(1, streamer.BacklogCount);
Assert.True(streamer.TryPeek(out LandblockStreamResult? secondPeek));
Assert.Same(firstPeek, secondPeek);
Assert.Equal(1, streamer.BacklogCount);
Assert.True(streamer.TryRead(out LandblockStreamResult? consumed));
Assert.Same(firstPeek, consumed);
Assert.Equal(0, streamer.BacklogCount);
Assert.False(streamer.TryRead(out _));
}
[Fact]
public async Task Load_ExecutesLoaderOnWorkerThread()
{

View file

@ -18,7 +18,7 @@ public class StreamingControllerPriorityApplyTests
generation);
[Fact]
public void PriorityLandblock_isApplied_evenWhenBeyondPerFrameCap()
public void PriorityLandblock_WinsExecutionOrderAfterBoundedAdmission()
{
uint priority = StreamingRegion.EncodeLandblockIdForTest(169, 180);
var outbox = new Queue<LandblockStreamResult>(new LandblockStreamResult[]
@ -42,14 +42,18 @@ public class StreamingControllerPriorityApplyTests
return batch;
},
applyTerrain: (lb, _) => applied.Add(lb.LandblockId),
state: state, nearRadius: 4, farRadius: 12)
state: state,
nearRadius: 4,
farRadius: 12,
workBudgetOptions: GenerousBudget())
{ 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
Assert.NotEmpty(applied);
Assert.Equal(priority, applied[0]);
Assert.True(applied.Count <= 5);
}
[Fact]
@ -98,10 +102,8 @@ public class StreamingControllerPriorityApplyTests
public void PriorityNeverArrives_noThrow_noLoss_noDoubleApply()
{
// While a PriorityLandblockId is set but the matching completion never
// arrives (e.g. the load failed), the hunt moves outbox completions into
// _deferredApply and they drain at the per-frame budget — same backpressure
// as the normal throttle, just relocated — until the caller clears
// PriorityLandblockId (the TAS MaxContinue safety net does this on timeout).
// 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);
uint otherId0 = StreamingRegion.EncodeLandblockIdForTest(169, 181);
uint otherId1 = StreamingRegion.EncodeLandblockIdForTest(169, 182);
@ -149,6 +151,15 @@ public class StreamingControllerPriorityApplyTests
Assert.Equal(3, applied.Count);
}
private static StreamingWorkBudgetOptions GenerousBudget() => new(
MaxUpdateMilliseconds: 100,
MaxCompletionAdmissions: 64,
MaxAdoptedCpuBytes: 16 * StreamingWorkBudgetOptions.MiB,
MaxEntityOperations: 10_000,
MaxGpuUploadBytes: 16 * StreamingWorkBudgetOptions.MiB,
MaxGlRetireOperations: 10_000,
DestinationReserveFraction: 0.75f);
[Fact]
public void DeferredCompaction_ApplyFailureRetainsExactResultWithoutReplayingCommittedPrefix()
{
@ -223,7 +234,15 @@ public class StreamingControllerPriorityApplyTests
applyTerrain: (build, _) => applied.Add(build.LandblockId),
state: state,
nearRadius: 4,
farRadius: 12)
farRadius: 12,
workBudgetOptions: new StreamingWorkBudgetOptions(
MaxUpdateMilliseconds: 100,
MaxCompletionAdmissions: 4,
MaxAdoptedCpuBytes: 1_000_000,
MaxEntityOperations: 1_000,
MaxGpuUploadBytes: 1_000_000,
MaxGlRetireOperations: 1_000,
DestinationReserveFraction: 0.75f))
{ MaxCompletionsPerFrame = 4, PriorityLandblockId = priority };
ctrl.Tick(169, 180);