refactor(streaming): compose landblock presentation transaction

Own render, physics, DAT-static presentation, and retained static-resource rebinds behind retryable receipts so a failed publication resumes its exact unfinished suffix without replaying retail create-time defaults.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-21 21:45:16 +02:00
parent e1110f7e54
commit ea0da8c8ae
15 changed files with 1744 additions and 89 deletions

View file

@ -41,6 +41,9 @@ public sealed class LandblockPresentationPipeline
public required uint LandblockId;
public LandblockStreamTier Tier;
public bool PresentationCommitted;
public LandblockRenderPublication? RenderPublication;
public LandblockPhysicsPublication? PhysicsPublication;
public LandblockStaticPresentationPublication? StaticPublication;
public bool SpatialCommitted;
public GpuLandblockSpatialPublication? SpatialPublication;
public bool SpatialPresentationCommitted;
@ -48,7 +51,11 @@ public sealed class LandblockPresentationPipeline
public bool LiveRecoveryCommitted;
}
private readonly Action<LandblockBuild, LandblockMeshData> _publishBeforeSpatialCommit;
private readonly Action<LandblockBuild, LandblockMeshData>?
_publishBeforeSpatialCommit;
private readonly LandblockRenderPublisher? _renderPublisher;
private readonly LandblockPhysicsPublisher? _physicsPublisher;
private readonly LandblockStaticPresentationPublisher? _staticPublisher;
private readonly GpuWorldState _state;
private readonly Action<uint>? _onLandblockLoaded;
private readonly Action<EnvCellLandblockBuild>? _ensureEnvCellMeshes;
@ -79,6 +86,37 @@ public sealed class LandblockPresentationPipeline
demoteNearLayer);
}
/// <summary>
/// Production constructor. The pipeline retains each concrete owner
/// receipt before mutation and resumes only the unfinished exact stage
/// after a failure.
/// </summary>
public LandblockPresentationPipeline(
LandblockRenderPublisher renderPublisher,
LandblockPhysicsPublisher physicsPublisher,
LandblockStaticPresentationPublisher staticPublisher,
GpuWorldState state,
Action<uint>? onLandblockLoaded = null,
Action<EnvCellLandblockBuild>? ensureEnvCellMeshes = null,
LandblockRetirementCoordinator? retirementCoordinator = null)
{
_renderPublisher = renderPublisher
?? throw new ArgumentNullException(nameof(renderPublisher));
_physicsPublisher = physicsPublisher
?? throw new ArgumentNullException(nameof(physicsPublisher));
_staticPublisher = staticPublisher
?? throw new ArgumentNullException(nameof(staticPublisher));
_state = state ?? throw new ArgumentNullException(nameof(state));
_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));
}
public int PendingRetirementCount => _retirements.PendingCount;
public int PendingPublicationCount => _publications.Count;
@ -219,22 +257,60 @@ public sealed class LandblockPresentationPipeline
{
if (!transaction.PresentationCommitted)
{
try
if (_renderPublisher is not null
&& _physicsPublisher is not null
&& _staticPublisher is not null)
{
_publishBeforeSpatialCommit(transaction.Build, transaction.MeshData);
transaction.RenderPublication ??=
_renderPublisher.PreparePublication(
transaction.Build,
transaction.MeshData);
transaction.PhysicsPublication ??=
_physicsPublisher.PreparePublication(
transaction.RenderPublication);
transaction.StaticPublication ??=
_staticPublisher.PreparePublication(
transaction.PhysicsPublication);
// Prepare every immutable receipt before the first external
// mutation. Cross-owner identity/data errors therefore block
// the publication without leaving a committed prefix.
_renderPublisher.BeginPublication(transaction.RenderPublication);
_physicsPublisher.BeginPublication(transaction.PhysicsPublication);
// Retail establishes the building/visible-cell suffix before
// each static object publishes lights, collision, and scripts.
_renderPublisher.CompletePublication(
transaction.RenderPublication);
_staticPublisher.BeginPublication(transaction.StaticPublication);
_physicsPublisher.CompletePublication(
transaction.PhysicsPublication,
entity => _staticPublisher.PrepareEntityBeforeCollision(
transaction.StaticPublication,
entity));
_staticPublisher.CompletePublication(
transaction.StaticPublication);
}
catch (Exception error)
else
{
// The legacy GameWindow callback still spans several concrete
// owners and cannot report a committed prefix. Until Slice-5
// checkpoints C-E replace it with individually ledgered
// publishers, its production wrapper conservatively reports a
// committed mutation. Test/compatibility callbacks retain the
// established strong-exception contract unless they explicitly
// report otherwise.
if (error is StreamingMutationException { MutationCommitted: true })
_publications.Remove(result);
throw;
try
{
(_publishBeforeSpatialCommit
?? throw new InvalidOperationException(
"The presentation pipeline has no publication owners."))(
transaction.Build,
transaction.MeshData);
}
catch (Exception error)
{
// Compatibility callbacks cannot report an exact retained
// receipt. Preserve the established fail-fast rule only
// for callbacks that explicitly report committed mutation.
if (error is StreamingMutationException { MutationCommitted: true })
_publications.Remove(result);
throw;
}
}
transaction.PresentationCommitted = true;
}