refactor(streaming): own presentation retirement in pipeline

Move full and near-tier presentation retirement out of GameWindow and into a concrete pipeline-owned stage mapper. Harden the retry ledger against reentrancy and committed visibility observer failures, enforce a single state/resource ownership graph, and split concrete versus legacy controller construction so callbacks cannot be silently ignored.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-21 22:07:36 +02:00
parent dc39772bf9
commit ea7ffbc186
10 changed files with 860 additions and 140 deletions

View file

@ -79,6 +79,13 @@ public sealed class LandblockPresentationPipeline
_state = state;
_onLandblockLoaded = onLandblockLoaded;
_ensureEnvCellMeshes = ensureEnvCellMeshes;
if (retirementCoordinator is not null
&& !retirementCoordinator.MatchesState(_state))
{
throw new ArgumentException(
"The retirement coordinator must own the pipeline's world state.",
nameof(retirementCoordinator));
}
_retirements = retirementCoordinator
?? LandblockRetirementCoordinator.CreateLegacy(
state,
@ -96,9 +103,9 @@ public sealed class LandblockPresentationPipeline
LandblockPhysicsPublisher physicsPublisher,
LandblockStaticPresentationPublisher staticPublisher,
GpuWorldState state,
LandblockPresentationRetirementOwner retirementOwner,
Action<uint>? onLandblockLoaded = null,
Action<EnvCellLandblockBuild>? ensureEnvCellMeshes = null,
LandblockRetirementCoordinator? retirementCoordinator = null)
Action<EnvCellLandblockBuild>? ensureEnvCellMeshes = null)
{
_renderPublisher = renderPublisher
?? throw new ArgumentNullException(nameof(renderPublisher));
@ -107,19 +114,35 @@ public sealed class LandblockPresentationPipeline
_staticPublisher = staticPublisher
?? throw new ArgumentNullException(nameof(staticPublisher));
_state = state ?? throw new ArgumentNullException(nameof(state));
if (!_renderPublisher.MatchesState(_state))
{
throw new ArgumentException(
"The render publisher must own the pipeline's world state.",
nameof(state));
}
ArgumentNullException.ThrowIfNull(retirementOwner);
if (!retirementOwner.Matches(
_renderPublisher,
_physicsPublisher,
_staticPublisher))
{
throw new ArgumentException(
"The retirement owner must reference the pipeline's concrete publishers.",
nameof(retirementOwner));
}
_onLandblockLoaded = onLandblockLoaded;
_ensureEnvCellMeshes = ensureEnvCellMeshes;
// Until Slice 5F internalizes concrete retirement ownership, a
// concrete publication pipeline must share the production coordinator.
// A legacy no-op retirement suffix would detach spatial state while
// leaking the concrete render/physics/static owners.
_retirements = retirementCoordinator
?? throw new ArgumentNullException(nameof(retirementCoordinator));
_retirements = new LandblockRetirementCoordinator(
state,
retirementOwner.Advance);
}
public int PendingRetirementCount => _retirements.PendingCount;
public int PendingPublicationCount => _publications.Count;
internal bool MatchesState(GpuWorldState state) =>
ReferenceEquals(_state, state);
public bool IsRetirementPending(uint landblockId) =>
_retirements.IsPending(landblockId);