perf(streaming): quiesce retired generations and budget teardown

Publish the retail blocking-for-cells edge before deferred recenter work, freeze old-world presentation/simulation/audio, and advance full-window retirement from exact metered entity and owner cursors. This removes synchronous portal teardown without allowing retained owners to remain observable.
This commit is contained in:
Erik 2026-07-24 18:29:52 +02:00
parent b8f6317fe1
commit bb16f74fd4
38 changed files with 1691 additions and 170 deletions

View file

@ -20,6 +20,13 @@ public enum LandblockRetirementStage : ushort
LegacyPresentation = 1 << 11,
}
internal enum LandblockRetirementOperationResult : byte
{
NoWork,
Progressed,
Failed,
}
/// <summary>
/// One retryable landblock-retirement ledger. Successful owner operations are
/// committed once; a later attempt resumes at the exact failed owner/entity.
@ -70,6 +77,29 @@ public sealed class LandblockRetirementTicket
}
}
internal LandblockRetirementOperationResult RunOnceStep(
LandblockRetirementStage stage,
Action operation)
{
ValidateSingleStage(stage);
ArgumentNullException.ThrowIfNull(operation);
if ((CompletedStages & stage) != 0)
return LandblockRetirementOperationResult.NoWork;
try
{
operation();
CompletedStages |= stage;
_failures.Remove(stage);
return LandblockRetirementOperationResult.Progressed;
}
catch (Exception error)
{
_failures[stage] = error;
return LandblockRetirementOperationResult.Failed;
}
}
public bool RunForEachEntity(
LandblockRetirementStage stage,
Func<WorldEntity, bool> predicate,
@ -109,6 +139,74 @@ public sealed class LandblockRetirementTicket
return true;
}
/// <summary>
/// Advances exactly one entity cursor, including predicate misses. This
/// makes both owner work and the scan needed to find it visible to the
/// frame budget.
/// </summary>
internal LandblockRetirementOperationResult RunEntityStep(
LandblockRetirementStage stage,
Func<WorldEntity, bool> predicate,
Action<WorldEntity> operation)
{
ValidateSingleStage(stage);
ArgumentNullException.ThrowIfNull(predicate);
ArgumentNullException.ThrowIfNull(operation);
if ((CompletedStages & stage) != 0)
return LandblockRetirementOperationResult.NoWork;
_entityCursors.TryGetValue(stage, out int cursor);
if (cursor >= Entities.Count)
{
CompletedStages |= stage;
_entityCursors.Remove(stage);
_failures.Remove(stage);
return LandblockRetirementOperationResult.Progressed;
}
WorldEntity entity = Entities[cursor];
if (predicate(entity))
{
try
{
operation(entity);
}
catch (Exception error)
{
_entityCursors[stage] = cursor;
_failures[stage] = error;
return LandblockRetirementOperationResult.Failed;
}
}
cursor++;
if (cursor == Entities.Count)
{
CompletedStages |= stage;
_entityCursors.Remove(stage);
_failures.Remove(stage);
}
else
{
_entityCursors[stage] = cursor;
}
return LandblockRetirementOperationResult.Progressed;
}
internal LandblockRetirementStage NextIncompleteStage
{
get
{
ushort remaining = (ushort)(RequiredStages & ~CompletedStages);
if (remaining == 0)
return LandblockRetirementStage.None;
ushort lowest = (ushort)(remaining & (ushort)-(short)remaining);
return (LandblockRetirementStage)lowest;
}
}
internal void BeginAttempt() => _callbackFailure = null;
internal void RecordCallbackFailure(Exception error) =>
@ -170,9 +268,13 @@ public sealed class LandblockRetirementCoordinator
private readonly GpuWorldState _state;
private readonly Action<LandblockRetirementTicket> _advancePresentation;
private readonly Func<
LandblockRetirementTicket,
LandblockRetirementOperationResult>? _advancePresentationStep;
private readonly Func<LandblockRetirementKind, LandblockRetirementStage>
_requiredPresentationStages;
private readonly Dictionary<uint, List<LandblockRetirementTicket>> _pending = new();
private readonly Queue<LandblockRetirementTicket> _pendingOrder = new();
private readonly List<uint> _completedIds = new();
private readonly Queue<Request> _requests = new();
private readonly HashSet<Request> _seenDuringDrain = new();
@ -187,12 +289,30 @@ public sealed class LandblockRetirementCoordinator
ArgumentNullException.ThrowIfNull(advancePresentation);
_state = state;
_advancePresentation = advancePresentation;
_advancePresentationStep = null;
_requiredPresentationStages = requiredPresentationStages
?? (kind => kind == LandblockRetirementKind.Full
? ProductionPresentationStages
: ProductionPresentationStages & ~LandblockRetirementStage.Terrain);
}
private LandblockRetirementCoordinator(
GpuWorldState state,
Func<LandblockRetirementTicket, LandblockRetirementOperationResult>
advancePresentationStep,
Action<LandblockRetirementTicket> advancePresentation,
Func<LandblockRetirementKind, LandblockRetirementStage>
requiredPresentationStages)
{
_state = state ?? throw new ArgumentNullException(nameof(state));
_advancePresentationStep = advancePresentationStep
?? throw new ArgumentNullException(nameof(advancePresentationStep));
_advancePresentation = advancePresentation
?? throw new ArgumentNullException(nameof(advancePresentation));
_requiredPresentationStages = requiredPresentationStages
?? throw new ArgumentNullException(nameof(requiredPresentationStages));
}
public int PendingCount { get; private set; }
public bool IsPending(uint landblockId) =>
@ -200,6 +320,7 @@ public sealed class LandblockRetirementCoordinator
internal bool MatchesState(GpuWorldState state) =>
ReferenceEquals(_state, state);
internal bool UsesBudgetedSteps => _advancePresentationStep is not null;
public void BeginFull(uint landblockId) => EnqueueRequest(
new Request(RequestKind.BeginFull, Canonicalize(landblockId)));
@ -210,8 +331,81 @@ public sealed class LandblockRetirementCoordinator
public void Advance() => EnqueueRequest(
new Request(RequestKind.Advance, 0u));
internal static LandblockRetirementCoordinator CreateBudgeted(
GpuWorldState state,
Func<LandblockRetirementTicket, LandblockRetirementOperationResult>
advancePresentationStep,
Action<LandblockRetirementTicket> advancePresentation)
{
return new LandblockRetirementCoordinator(
state,
advancePresentationStep,
advancePresentation,
kind => kind == LandblockRetirementKind.Full
? ProductionPresentationStages
: ProductionPresentationStages & ~LandblockRetirementStage.Terrain);
}
/// <summary>
/// Advances stable FIFO retirement work under the caller's one frame meter.
/// Every reservation maps to exactly one entity cursor or owner stage.
/// </summary>
public void Advance(StreamingWorkMeter meter)
{
ArgumentNullException.ThrowIfNull(meter);
if (_advancePresentationStep is null)
{
Advance();
return;
}
while (_pendingOrder.TryPeek(out LandblockRetirementTicket? ticket))
{
if (ticket.IsComplete)
{
RemoveCompletedTicket(ticket);
continue;
}
LandblockRetirementStage stage = ticket.NextIncompleteStage;
if (stage == LandblockRetirementStage.None)
throw new InvalidOperationException(
"An incomplete retirement ticket has no unfinished stage.");
StreamingWorkCost cost = new(
EntityOperations: 1,
GlRetireOperations:
stage is LandblockRetirementStage.MeshReferences
or LandblockRetirementStage.Terrain
? 1
: 0);
StreamingWorkAdmission admission = meter.TryReserve(
cost,
$"retire-{stage}-0x{ticket.LandblockId:X8}");
if (admission == StreamingWorkAdmission.Yielded)
return;
LandblockRetirementOperationResult result = AdvanceTicketOne(ticket);
if (result == LandblockRetirementOperationResult.Failed)
{
meter.Fail();
return;
}
meter.Complete();
if (ticket.IsComplete)
RemoveCompletedTicket(ticket);
}
}
private void AdvanceCore()
{
if (_advancePresentationStep is not null)
{
AdvanceBudgetedEagerAttempt();
return;
}
if (_pending.Count == 0)
return;
@ -278,6 +472,9 @@ public sealed class LandblockRetirementCoordinator
if (existing[i].Kind == kind)
{
LandblockRetirementTicket existingTicket = existing[i];
if (_advancePresentationStep is not null)
return;
AdvanceTicket(existingTicket);
if (existingTicket.IsComplete)
{
@ -331,14 +528,19 @@ public sealed class LandblockRetirementCoordinator
}
existing.Add(ticket);
PendingCount++;
if (_advancePresentationStep is not null)
_pendingOrder.Enqueue(ticket);
AdvanceTicket(ticket);
if (ticket.IsComplete)
if (_advancePresentationStep is null)
{
existing.Remove(ticket);
PendingCount--;
if (existing.Count == 0)
_pending.Remove(canonical);
AdvanceTicket(ticket);
if (ticket.IsComplete)
{
existing.Remove(ticket);
PendingCount--;
if (existing.Count == 0)
_pending.Remove(canonical);
}
}
// Visibility is a delivered-by-attempt broadcast: every subscriber is
// invoked and failures are aggregated, but failed subscribers are not
@ -380,6 +582,96 @@ public sealed class LandblockRetirementCoordinator
}
}
private void AdvanceBudgetedEagerAttempt()
{
if (!_pendingOrder.TryPeek(out LandblockRetirementTicket? ticket))
return;
if (ticket.IsComplete)
{
RemoveCompletedTicket(ticket);
return;
}
// Compatibility/debug callers explicitly asking for an unmetered
// advance retain the former one-attempt behavior: every independent
// stage is attempted once, while successful stages/entities remain
// committed in the same exact ticket. Production calls the metered
// overload and never enters this path.
AdvanceTicket(ticket);
if (ticket.IsComplete)
RemoveCompletedTicket(ticket);
}
private LandblockRetirementOperationResult AdvanceTicketOne(
LandblockRetirementTicket ticket)
{
ticket.BeginAttempt();
LandblockRetirementStage stage = ticket.NextIncompleteStage;
LandblockRetirementOperationResult result;
try
{
result = stage switch
{
LandblockRetirementStage.MeshReferences =>
ticket.RunOnceStep(
stage,
() => _state.ReleaseLandblockMeshReferences(
ticket.LandblockId)),
LandblockRetirementStage.StaticScripts =>
ticket.RunEntityStep(
stage,
static entity => entity.ServerGuid == 0,
_state.StopStaticEntityScript),
LandblockRetirementStage.Classification =>
ticket.RunOnceStep(
stage,
() => _state.InvalidateLandblockClassification(
ticket.LandblockId)),
LandblockRetirementStage.None =>
LandblockRetirementOperationResult.NoWork,
_ => _advancePresentationStep!(ticket),
};
}
catch (Exception error)
{
ticket.RecordCallbackFailure(error);
result = LandblockRetirementOperationResult.Failed;
}
if (ticket.TryTakeNewFailure(out Exception? failure))
{
Console.WriteLine(
$"streaming: retirement for 0x{ticket.LandblockId:X8} " +
$"({ticket.Kind}) remains pending: {failure}");
}
return result;
}
private void RemoveCompletedTicket(LandblockRetirementTicket ticket)
{
if (!_pendingOrder.TryPeek(out LandblockRetirementTicket? head)
|| !ReferenceEquals(head, ticket))
{
throw new InvalidOperationException(
"Retirement FIFO completion did not match its active head.");
}
_pendingOrder.Dequeue();
uint id = Canonicalize(ticket.LandblockId);
if (!_pending.TryGetValue(id, out List<LandblockRetirementTicket>? tickets)
|| !tickets.Remove(ticket))
{
throw new InvalidOperationException(
"Completed retirement ticket is missing from its owner ledger.");
}
PendingCount--;
if (tickets.Count == 0)
_pending.Remove(id);
}
private static uint Canonicalize(uint id) =>
(id & 0xFFFF0000u) | 0xFFFFu;