perf #418: widen the destination-lane budget only while the reveal hold is active
While a destination reservation hides the world behind the authored tunnel, the streaming frame meter now runs a hold-widened profile (StreamingWorkBudget.WidenForDestinationHold): the time ceiling rises from the authored 2 ms to an absolute 8 ms default (ACDREAM_STREAM_WORK_HOLD_DEST_MS is a measurement-only override), every count/byte dimension scales by the same factor so elapsed time stays the authoritative guard (the measured binder is Time at both ceilings), and the reserve fraction is re-derived (0.75 -> 0.9375) so the NON-destination lane's absolute per-frame caps are unchanged. The widening keys off the existing BeginDestinationReservation/ EndDestinationReservation bracket only, is derived per-Tick from the CURRENT budget (mid-hold quality swaps compose), and a frame with no reservation uses the authored budget verbatim (test-pinned). Portal holds ride the same bracket as login holds by construction - intended, and pinned by a kind-parity test through the real coordinator plus a live @telepoi portal hold (kind=portal gate-ready 3589 ms). Why: issue #418's next-hypothesis (1). Measured result: the ~5 s publication drip collapsed to ~2 s (loaded 625/625 at ~3.0 s, tunnel at 64-66 fps), the portal-hold gate-ready fell to ~3.6 s - and login gate-ready/total stayed at 8.4-8.8 s / 12.6-12.7 s, exposing the real remaining pacer: the login-cold render-thread upload/registration barrier behind GpuWorldState.IsRenderReady, which ran concurrently under the old drip. Full attribution appended to docs/ISSUES.md #418; no divergence-register row (the streamed result and reveal gate are byte-identical; only the scheduling rate during a hidden hold changed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e94e8e0c61
commit
a1d15a82dd
5 changed files with 420 additions and 7 deletions
|
|
@ -3,8 +3,11 @@ using System.Numerics;
|
|||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Terrain;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.World;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
|
@ -232,6 +235,107 @@ public sealed class StreamingWorkBudgetTests
|
|||
Assert.Equal(2, meter.Snapshot.CompletedOperations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WidenForDestinationHold_ScalesEveryDimensionAndReservesTheAuthoredNonDestinationShare()
|
||||
{
|
||||
StreamingWorkBudget widened = StreamingWorkBudgetOptions.Default
|
||||
.ToBudget()
|
||||
.WidenForDestinationHold(8.0);
|
||||
|
||||
Assert.Equal(TimeSpan.FromMilliseconds(8), widened.MaxUpdateTime);
|
||||
Assert.Equal(256, widened.MaxCompletionAdmissions);
|
||||
Assert.Equal(
|
||||
32 * StreamingWorkBudgetOptions.MiB,
|
||||
widened.MaxAdoptedCpuBytes);
|
||||
Assert.Equal(16_384, widened.MaxEntityOperations);
|
||||
Assert.Equal(
|
||||
32 * StreamingWorkBudgetOptions.MiB,
|
||||
widened.MaxGpuUploadBytes);
|
||||
Assert.Equal(256, widened.MaxGlRetireOperations);
|
||||
// 1 - (1 - 0.75)/4: the non-destination share of the widened totals
|
||||
// is the authored absolute share (floor(256 x 0.0625) =
|
||||
// floor(64 x 0.25) = 16 completions, and so on per dimension).
|
||||
Assert.Equal(0.9375f, widened.DestinationReserveFraction);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WidenForDestinationHold_IsANoOpAtOrBelowTheCurrentCeiling()
|
||||
{
|
||||
StreamingWorkBudget authored =
|
||||
StreamingWorkBudgetOptions.Default.ToBudget();
|
||||
|
||||
Assert.Equal(authored, authored.WidenForDestinationHold(2.0));
|
||||
Assert.Equal(authored, authored.WidenForDestinationHold(1.0));
|
||||
Assert.Equal(authored, authored.WidenForDestinationHold(0.0));
|
||||
Assert.Equal(authored, authored.WidenForDestinationHold(double.NaN));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WidenForDestinationHold_DoesNotWidenTheNonDestinationLane()
|
||||
{
|
||||
StreamingWorkBudget authored =
|
||||
StreamingWorkBudgetOptions.Default.ToBudget();
|
||||
foreach (StreamingWorkBudget budget in new[]
|
||||
{
|
||||
authored,
|
||||
authored.WidenForDestinationHold(8.0),
|
||||
})
|
||||
{
|
||||
var meter = new StreamingWorkMeter(
|
||||
budget,
|
||||
static () => 0,
|
||||
timestampFrequency: 1_000,
|
||||
destinationReservationActive: true);
|
||||
|
||||
// The non-destination lane clips at the SAME absolute cap
|
||||
// (16 completion admissions) whether or not the profile is
|
||||
// hold-widened: widening must never hand mid-game background
|
||||
// work extra capacity.
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Admitted,
|
||||
meter.TryReserve(
|
||||
new StreamingWorkCost(CompletionAdmissions: 16),
|
||||
"ordinary"));
|
||||
meter.Complete();
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Yielded,
|
||||
meter.TryReserve(
|
||||
new StreamingWorkCost(CompletionAdmissions: 1),
|
||||
"ordinary-over-share"));
|
||||
Assert.Equal(
|
||||
StreamingWorkLimit.CompletionAdmissions,
|
||||
meter.Snapshot.LastLimit);
|
||||
}
|
||||
|
||||
// The destination lane of the widened profile owns the widened
|
||||
// remainder: 240 more admissions on top of the ordinary 16.
|
||||
var widenedMeter = new StreamingWorkMeter(
|
||||
authored.WidenForDestinationHold(8.0),
|
||||
static () => 0,
|
||||
timestampFrequency: 1_000,
|
||||
destinationReservationActive: true);
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Admitted,
|
||||
widenedMeter.TryReserve(
|
||||
new StreamingWorkCost(CompletionAdmissions: 16),
|
||||
"ordinary"));
|
||||
widenedMeter.Complete();
|
||||
using (widenedMeter.EnterLane(StreamingWorkLane.Destination))
|
||||
{
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Admitted,
|
||||
widenedMeter.TryReserve(
|
||||
new StreamingWorkCost(CompletionAdmissions: 240),
|
||||
"destination"));
|
||||
widenedMeter.Complete();
|
||||
Assert.Equal(
|
||||
StreamingWorkAdmission.Yielded,
|
||||
widenedMeter.TryReserve(
|
||||
new StreamingWorkCost(CompletionAdmissions: 1),
|
||||
"destination-over-widened-total"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeterUsesInjectedMonotonicClockAndRecordsOverrunAndFailure()
|
||||
{
|
||||
|
|
@ -598,6 +702,180 @@ public sealed class StreamingWorkBudgetTests
|
|||
Assert.Equal(0, source.BacklogCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveHold_WidensTheDestinationLaneAndRevertsWhenTheReservationEnds()
|
||||
{
|
||||
uint center = StreamingRegion.EncodeLandblockIdForTest(32, 32);
|
||||
var source = new QueueCompletionSource(
|
||||
Loaded(center),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 33)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(33, 32)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(33, 33)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 31)));
|
||||
int applied = 0;
|
||||
// Authored 100 ms / hold ceiling 200 ms: a deterministic 2x widening
|
||||
// that the wall clock cannot reach, so only the admission count can
|
||||
// clip. 2 authored admissions -> 4 during the hold.
|
||||
StreamingController controller = Controller(
|
||||
source,
|
||||
() => applied++,
|
||||
WorkOptions(admissions: 2, cpuBytes: 1_000) with
|
||||
{
|
||||
HoldDestinationCeilingMilliseconds = 200,
|
||||
});
|
||||
controller.BeginDestinationReservation(1, center, 1);
|
||||
|
||||
controller.Tick(32, 32);
|
||||
|
||||
Assert.Equal(4, applied);
|
||||
Assert.Equal(
|
||||
4,
|
||||
controller.WorkDiagnostics.LastFrame.Used.CompletionAdmissions);
|
||||
Assert.Equal(1, source.BacklogCount);
|
||||
|
||||
// The moment the reservation ends the authored profile is back:
|
||||
// the same backlog now admits 2 per frame, not 4.
|
||||
controller.EndDestinationReservation(1);
|
||||
source.Enqueue(
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(33, 31)));
|
||||
source.Enqueue(
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(31, 32)));
|
||||
|
||||
controller.Tick(32, 32);
|
||||
|
||||
Assert.Equal(6, applied);
|
||||
Assert.Equal(
|
||||
2,
|
||||
controller.WorkDiagnostics.LastFrame.Used.CompletionAdmissions);
|
||||
Assert.Equal(1, source.BacklogCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void HoldWidening_AppliesToLoginAndPortalRevealsAlikeAndRevertsAtEnd(
|
||||
bool portalKind)
|
||||
{
|
||||
// #418 kind-parity proof: BeginLogin and TryBeginPortal converge on
|
||||
// the SAME BeginHostLifetime -> BeginDestinationReservation bracket
|
||||
// (WorldRevealCoordinator), and the widening keys off that bracket
|
||||
// alone — so a portal hold widens exactly like a login hold, and
|
||||
// both revert the moment the reveal releases the reservation. The
|
||||
// real StreamingController is the coordinator's scheduler here; the
|
||||
// observable is the per-frame admission cap (2 authored, 4 widened
|
||||
// by the 100 ms -> 200 ms hold ceiling).
|
||||
uint center = StreamingRegion.EncodeLandblockIdForTest(32, 32);
|
||||
const uint destinationCell = 0x20200021u; // outdoor cell of (32,32)
|
||||
var source = new QueueCompletionSource(
|
||||
Loaded(center),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 33)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(33, 32)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(33, 33)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 31)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(33, 31)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(31, 32)));
|
||||
int applied = 0;
|
||||
StreamingController controller = Controller(
|
||||
source,
|
||||
() => applied++,
|
||||
WorkOptions(admissions: 2, cpuBytes: 1_000) with
|
||||
{
|
||||
HoldDestinationCeilingMilliseconds = 200,
|
||||
});
|
||||
var transit = new RuntimeWorldTransitState(null);
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
transit,
|
||||
revealWindow: static () => new StreamingRevealWindow(1, 1),
|
||||
isRenderNeighborhoodReady: static (_, _, _) => false,
|
||||
isSpawnCellReady: static _ => false,
|
||||
isTerrainNeighborhoodReady: static (_, _) => false,
|
||||
areCompositeTexturesReady: static () => false,
|
||||
prepareCompositeTextures: static (_, _) => { },
|
||||
invalidateCompositeTextures: static () => { },
|
||||
isSpawnClaimUnhydratable: static _ => false,
|
||||
streaming: controller);
|
||||
|
||||
if (portalKind)
|
||||
{
|
||||
Assert.True(transit.TryQueueTeleportStart(1));
|
||||
Assert.True(transit.ActivateQueuedTeleport());
|
||||
Assert.True(transit.OfferTeleportDestination(
|
||||
new RuntimeTeleportDestination(
|
||||
EntityGuid: 0x50000001u,
|
||||
InstanceSequence: 1,
|
||||
PositionSequence: 1,
|
||||
TeleportSequence: 1,
|
||||
ForcePositionSequence: 1,
|
||||
Position: new Position(
|
||||
destinationCell,
|
||||
Vector3.Zero,
|
||||
Quaternion.Identity)),
|
||||
teleportTimestampAdvanced: true));
|
||||
Assert.True(coordinator.TryBeginPortal(
|
||||
1,
|
||||
destinationCell,
|
||||
out _));
|
||||
Assert.Equal(
|
||||
RuntimePortalKind.Portal,
|
||||
coordinator.Snapshot.Kind);
|
||||
}
|
||||
else
|
||||
{
|
||||
coordinator.BeginLogin(destinationCell);
|
||||
Assert.Equal(
|
||||
RuntimePortalKind.Login,
|
||||
coordinator.Snapshot.Kind);
|
||||
}
|
||||
|
||||
controller.Tick(32, 32);
|
||||
|
||||
Assert.Equal(4, applied);
|
||||
Assert.Equal(
|
||||
4,
|
||||
controller.WorkDiagnostics.LastFrame.Used.CompletionAdmissions);
|
||||
|
||||
// Releasing the reveal releases the reservation; the very next frame
|
||||
// runs the authored profile again.
|
||||
coordinator.Cancel();
|
||||
controller.Tick(32, 32);
|
||||
|
||||
Assert.Equal(6, applied);
|
||||
Assert.Equal(
|
||||
2,
|
||||
controller.WorkDiagnostics.LastFrame.Used.CompletionAdmissions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoReservation_UsesTheAuthoredBudgetUnchanged()
|
||||
{
|
||||
// Constraint pin (#418): with NO destination reservation the frame
|
||||
// meter runs on the authored budget verbatim — the configured hold
|
||||
// ceiling must be invisible to mid-game streaming.
|
||||
uint center = StreamingRegion.EncodeLandblockIdForTest(32, 32);
|
||||
var source = new QueueCompletionSource(
|
||||
Loaded(center),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 33)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(33, 32)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(33, 33)),
|
||||
Loaded(StreamingRegion.EncodeLandblockIdForTest(32, 31)));
|
||||
int applied = 0;
|
||||
StreamingController controller = Controller(
|
||||
source,
|
||||
() => applied++,
|
||||
WorkOptions(admissions: 2, cpuBytes: 1_000) with
|
||||
{
|
||||
HoldDestinationCeilingMilliseconds = 200,
|
||||
});
|
||||
|
||||
controller.Tick(32, 32);
|
||||
|
||||
Assert.Equal(2, applied);
|
||||
Assert.Equal(
|
||||
2,
|
||||
controller.WorkDiagnostics.LastFrame.Used.CompletionAdmissions);
|
||||
Assert.Equal(3, source.BacklogCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IncompleteReveal_DoesNotStarveOrdinaryWorkBeforeDestinationArrives()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue