acdream/src/AcDream.App/Streaming/LandblockPresentationPipeline.cs
Erik 823936ec31 fix(streaming): preserve portal destination ownership
Detach old-world spatial ownership atomically, prioritize destination retirement dependencies, and reveal the viewport at the retail transition edge. Give private paperdoll views independent mesh ownership and retain dormant ACE entities so portal revisits preserve server objects without extending active GPU lifetimes.
2026-07-25 08:35:12 +02:00

902 lines
38 KiB
C#

using AcDream.App.Rendering.Wb;
using AcDream.App.Rendering.Scene;
using AcDream.Core.Terrain;
using AcDream.Core.World;
namespace AcDream.App.Streaming;
/// <summary>
/// Read-only presentation state needed by render traversal and diagnostics.
/// Keeping this projection on the pipeline prevents the composition root from
/// retaining each concrete publication owner independently.
/// </summary>
public readonly record struct LandblockPresentationDiagnostics(
LandblockRenderPublisherDiagnostics Render,
LandblockPhysicsPublisherDiagnostics Physics,
LandblockStaticPresentationDiagnostics Statics);
internal readonly record struct LandblockPublicationAdvance(
bool Completed,
bool Progressed);
/// <summary>
/// Render-thread transaction coordinator for one accepted landblock streaming
/// result. Streaming policy (desired tier, generation, priority, and apply
/// budget) remains in <see cref="StreamingController"/>; spatial buckets remain
/// in <see cref="GpuWorldState"/>. This owner fixes the presentation ordering
/// independently of those policies. The concrete render, physics, and static
/// publishers remain private implementation participants behind this boundary.
/// </summary>
/// <remarks>
/// Retail establishes cells/buildings before static objects in
/// <c>LScape::grab_visible_cells @ 0x00504EC0</c>. Its release path separates
/// <c>CLandBlock::release_all @ 0x0052FCF0</c> (objects and visible cells) from
/// later destruction in <c>CLandBlock::Destroy @ 0x0052FAA0</c> (static objects
/// and buildings). Acdream's detach-first retry ledger is the existing
/// asynchronous lifetime adaptation. Production always uses the focused owner
/// graph; the internal delegate constructor exists only for hermetic policy and
/// retry characterization tests.
/// </remarks>
public sealed class LandblockPresentationPipeline
{
private enum PublicationKind : byte
{
Loaded,
PromoteExisting,
PromoteSelfContained,
Far,
}
private sealed class PublicationTransaction
{
public required PublicationKind Kind;
public required LandblockBuild Build;
public required LandblockMeshData MeshData;
public required uint LandblockId;
public required LandblockStreamCostEstimate Cost;
public LandblockStreamTier Tier;
public bool PresentationCommitted;
public LandblockRenderPublication? RenderPublication;
public LandblockPhysicsPublication? PhysicsPublication;
public LandblockStaticPresentationPublication? StaticPublication;
public bool SpatialCommitted;
public GpuLandblockSpatialPublication? SpatialPublication;
public bool SpatialPresentationCommitted;
public bool EnvCellReplayCommitted;
public bool LiveRecoveryCommitted;
}
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;
private readonly IRenderStaticProjectionJournalSink? _staticProjectionSink;
private readonly LandblockRetirementCoordinator _retirements;
private readonly Dictionary<LandblockStreamResult, PublicationTransaction> _publications =
new(ReferenceEqualityComparer.Instance);
internal LandblockPresentationPipeline(
Action<LandblockBuild, LandblockMeshData> publishBeforeSpatialCommit,
GpuWorldState state,
Action<uint>? onLandblockLoaded = null,
Action<EnvCellLandblockBuild>? ensureEnvCellMeshes = null,
LandblockRetirementCoordinator? retirementCoordinator = null,
Action<uint>? removeTerrain = null,
Action<uint>? demoteNearLayer = null,
IRenderStaticProjectionJournalSink? staticProjectionSink = null)
{
ArgumentNullException.ThrowIfNull(publishBeforeSpatialCommit);
ArgumentNullException.ThrowIfNull(state);
_publishBeforeSpatialCommit = publishBeforeSpatialCommit;
_state = state;
_onLandblockLoaded = onLandblockLoaded;
_ensureEnvCellMeshes = ensureEnvCellMeshes;
_staticProjectionSink = staticProjectionSink;
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,
removeTerrain,
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,
LandblockPresentationRetirementOwner retirementOwner,
Action<uint>? onLandblockLoaded = null,
Action<EnvCellLandblockBuild>? ensureEnvCellMeshes = null)
: this(
renderPublisher,
physicsPublisher,
staticPublisher,
state,
retirementOwner,
onLandblockLoaded,
ensureEnvCellMeshes,
staticProjectionSink: null)
{
}
internal LandblockPresentationPipeline(
LandblockRenderPublisher renderPublisher,
LandblockPhysicsPublisher physicsPublisher,
LandblockStaticPresentationPublisher staticPublisher,
GpuWorldState state,
LandblockPresentationRetirementOwner retirementOwner,
Action<uint>? onLandblockLoaded,
Action<EnvCellLandblockBuild>? ensureEnvCellMeshes,
IRenderStaticProjectionJournalSink? staticProjectionSink)
{
_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));
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;
_staticProjectionSink = staticProjectionSink;
_retirements = LandblockRetirementCoordinator.CreateBudgeted(
state,
retirementOwner.AdvanceOne,
retirementOwner.Advance,
staticProjectionSink is null
? null
: staticProjectionSink.Retire);
}
/// <summary>
/// Building visibility registries published by the render owner. Legacy
/// test pipelines have no render owner and therefore expose an empty view.
/// </summary>
public IReadOnlyCollection<BuildingRegistry> BuildingRegistries =>
_renderPublisher?.BuildingRegistries ?? Array.Empty<BuildingRegistry>();
/// <summary>
/// Cumulative diagnostics from the three concrete presentation owners.
/// </summary>
public LandblockPresentationDiagnostics Diagnostics => new(
_renderPublisher?.Diagnostics ?? default,
_physicsPublisher?.Diagnostics ?? default,
_staticPublisher?.Diagnostics ?? default);
public int PendingRetirementCount => _retirements.PendingCount;
internal bool UsesBudgetedRetirementSteps => _retirements.UsesBudgetedSteps;
public int PendingPublicationCount => _publications.Count;
internal bool MatchesState(GpuWorldState state) =>
ReferenceEquals(_state, state);
public bool IsRetirementPending(uint landblockId) =>
_retirements.IsPending(landblockId);
public bool HasPendingPublication(LandblockStreamResult result)
{
ArgumentNullException.ThrowIfNull(result);
return _publications.ContainsKey(result);
}
public IReadOnlyList<LandblockStreamResult> GetPendingPublicationResults() =>
_publications.Keys.ToArray();
public void ResumePublication(LandblockStreamResult result)
{
ArgumentNullException.ThrowIfNull(result);
if (!_publications.TryGetValue(result, out PublicationTransaction? transaction))
return;
Advance(result, transaction, meter: null, ensureProgress: false);
}
internal LandblockPublicationAdvance ResumePublication(
LandblockStreamResult result,
StreamingWorkMeter meter,
bool ensureProgress)
{
ArgumentNullException.ThrowIfNull(result);
ArgumentNullException.ThrowIfNull(meter);
if (!_publications.TryGetValue(result, out PublicationTransaction? transaction))
return new LandblockPublicationAdvance(Completed: true, Progressed: false);
return Advance(result, transaction, meter, ensureProgress);
}
public void AdvanceRetirements() => _retirements.Advance();
public void AdvanceRetirements(StreamingWorkMeter meter) =>
_retirements.Advance(meter);
internal void AdvancePriorityRetirement(
uint landblockId,
StreamingWorkMeter meter) =>
_retirements.AdvancePriority(landblockId, meter);
public void BeginFullRetirement(uint landblockId)
{
_retirements.BeginFull(landblockId);
if (_retirements.UsesBudgetedSteps)
_retirements.Advance();
}
public void BeginNearLayerRetirement(uint landblockId)
{
_retirements.BeginNearLayer(landblockId);
if (_retirements.UsesBudgetedSteps)
_retirements.Advance();
}
internal void EnqueueFullRetirement(uint landblockId) =>
_retirements.BeginFull(landblockId);
internal void EnqueueNearLayerRetirement(uint landblockId) =>
_retirements.BeginNearLayer(landblockId);
/// <summary>
/// Withdraws the complete old spatial generation in one transaction, then
/// transfers its exact receipts to the ordinary budgeted retirement FIFO.
/// </summary>
internal GpuWorldRecenterRetirement DetachAllForOriginRecenter()
{
GpuWorldRecenterRetirement detached =
_state.DetachAllForOriginRecenter();
Exception? adoptionFailure =
_retirements.AdoptDetachedFull(detached.Landblocks);
Exception? failure = (detached.ObserverFailure, adoptionFailure) switch
{
(null, null) => null,
({ } observer, null) => observer,
(null, { } adoption) => adoption,
({ } observer, { } adoption) => new AggregateException(
"Origin-recenter spatial observers and retirement adoption failed.",
observer,
adoption),
};
return detached with { ObserverFailure = failure };
}
public void PublishLoaded(LandblockStreamResult.Loaded loaded)
{
ArgumentNullException.ThrowIfNull(loaded);
PublicationTransaction transaction = GetOrCreate(
loaded,
PublicationKind.Loaded,
loaded.Build,
loaded.MeshData,
loaded.LandblockId,
loaded.Tier,
estimate: null);
Advance(loaded, transaction, meter: null, ensureProgress: false);
}
internal LandblockPublicationAdvance PublishLoaded(
LandblockStreamResult.Loaded loaded,
LandblockStreamCostEstimate estimate,
StreamingWorkMeter meter,
bool ensureProgress)
{
ArgumentNullException.ThrowIfNull(loaded);
ArgumentNullException.ThrowIfNull(meter);
PublicationTransaction transaction = GetOrCreate(
loaded,
PublicationKind.Loaded,
loaded.Build,
loaded.MeshData,
loaded.LandblockId,
loaded.Tier,
estimate);
return Advance(loaded, transaction, meter, ensureProgress);
}
public void PublishPromoted(
LandblockStreamResult.Promoted promoted,
bool mergeIntoExistingLandblock)
{
ArgumentNullException.ThrowIfNull(promoted);
PublicationKind kind = mergeIntoExistingLandblock
? PublicationKind.PromoteExisting
: PublicationKind.PromoteSelfContained;
PublicationTransaction transaction = GetOrCreate(
promoted,
kind,
promoted.Build,
promoted.MeshData,
promoted.LandblockId,
LandblockStreamTier.Near,
estimate: null);
Advance(promoted, transaction, meter: null, ensureProgress: false);
}
internal LandblockPublicationAdvance PublishPromoted(
LandblockStreamResult.Promoted promoted,
bool mergeIntoExistingLandblock,
LandblockStreamCostEstimate estimate,
StreamingWorkMeter meter,
bool ensureProgress)
{
ArgumentNullException.ThrowIfNull(promoted);
ArgumentNullException.ThrowIfNull(meter);
PublicationKind kind = mergeIntoExistingLandblock
? PublicationKind.PromoteExisting
: PublicationKind.PromoteSelfContained;
PublicationTransaction transaction = GetOrCreate(
promoted,
kind,
promoted.Build,
promoted.MeshData,
promoted.LandblockId,
LandblockStreamTier.Near,
estimate);
return Advance(promoted, transaction, meter, ensureProgress);
}
public void PublishAsFar(
LandblockStreamResult acceptedResult,
LandblockBuild completedBuild,
LandblockMeshData meshData)
{
ArgumentNullException.ThrowIfNull(acceptedResult);
ArgumentNullException.ThrowIfNull(completedBuild);
ArgumentNullException.ThrowIfNull(meshData);
if (!_publications.TryGetValue(acceptedResult, out PublicationTransaction? transaction))
{
LoadedLandblock completed = completedBuild.Landblock;
var farLandblock = new LoadedLandblock(
completed.LandblockId,
completed.Heightmap,
Array.Empty<WorldEntity>(),
PhysicsDatBundle.Empty);
transaction = new PublicationTransaction
{
Kind = PublicationKind.Far,
Build = new LandblockBuild(
farLandblock,
Origin: completedBuild.Origin),
MeshData = meshData,
LandblockId = farLandblock.LandblockId,
Cost = LandblockStreamResultCost.Estimate(
new LandblockBuild(
farLandblock,
Origin: completedBuild.Origin),
meshData),
Tier = LandblockStreamTier.Far,
};
_publications.Add(acceptedResult, transaction);
}
else if (transaction.Kind != PublicationKind.Far)
{
throw new InvalidOperationException(
$"Landblock publication 0x{transaction.LandblockId:X8} changed kind while pending.");
}
Advance(
acceptedResult,
transaction,
meter: null,
ensureProgress: false);
}
internal LandblockPublicationAdvance PublishAsFar(
LandblockStreamResult acceptedResult,
LandblockBuild completedBuild,
LandblockMeshData meshData,
StreamingWorkMeter meter,
bool ensureProgress)
{
ArgumentNullException.ThrowIfNull(acceptedResult);
ArgumentNullException.ThrowIfNull(completedBuild);
ArgumentNullException.ThrowIfNull(meshData);
ArgumentNullException.ThrowIfNull(meter);
if (!_publications.TryGetValue(
acceptedResult,
out PublicationTransaction? transaction))
{
LoadedLandblock completed = completedBuild.Landblock;
var farLandblock = new LoadedLandblock(
completed.LandblockId,
completed.Heightmap,
Array.Empty<WorldEntity>(),
PhysicsDatBundle.Empty);
LandblockBuild farBuild = new(
farLandblock,
Origin: completedBuild.Origin);
transaction = new PublicationTransaction
{
Kind = PublicationKind.Far,
Build = farBuild,
MeshData = meshData,
LandblockId = farLandblock.LandblockId,
Cost = LandblockStreamResultCost.Estimate(farBuild, meshData),
Tier = LandblockStreamTier.Far,
};
_publications.Add(acceptedResult, transaction);
}
else if (transaction.Kind != PublicationKind.Far)
{
throw new InvalidOperationException(
$"Landblock publication 0x{transaction.LandblockId:X8} changed kind while pending.");
}
return Advance(acceptedResult, transaction, meter, ensureProgress);
}
private PublicationTransaction GetOrCreate(
LandblockStreamResult result,
PublicationKind kind,
LandblockBuild build,
LandblockMeshData meshData,
uint landblockId,
LandblockStreamTier tier,
LandblockStreamCostEstimate? estimate)
{
if (_publications.TryGetValue(result, out PublicationTransaction? existing))
{
if (existing.Kind != kind
&& !(existing.Kind is PublicationKind.PromoteExisting
or PublicationKind.PromoteSelfContained
&& kind is PublicationKind.PromoteExisting
or PublicationKind.PromoteSelfContained))
{
throw new InvalidOperationException(
$"Landblock publication 0x{existing.LandblockId:X8} changed kind while pending.");
}
return existing;
}
var created = new PublicationTransaction
{
Kind = kind,
Build = build,
MeshData = meshData,
LandblockId = landblockId,
Cost = estimate
?? LandblockStreamResultCost.Estimate(build, meshData),
Tier = tier,
};
_publications.Add(result, created);
return created;
}
private LandblockPublicationAdvance Advance(
LandblockStreamResult result,
PublicationTransaction transaction,
StreamingWorkMeter? meter,
bool ensureProgress)
{
bool progressed = false;
bool TryRun(
StreamingWorkCost cost,
string stage,
Action operation)
{
if (meter is null)
{
operation();
progressed = true;
return true;
}
StreamingWorkAdmission admission = meter.TryReserve(
cost,
stage,
ensureProgress && !progressed);
if (admission == StreamingWorkAdmission.Yielded)
return false;
try
{
operation();
meter.Complete();
progressed = true;
return true;
}
catch
{
meter.Fail();
throw;
}
}
if (!transaction.PresentationCommitted)
{
if (_renderPublisher is not null
&& _physicsPublisher is not null
&& _staticPublisher is not null)
{
while (transaction.RenderPublication is null)
{
if (!TryRun(
new StreamingWorkCost(EntityOperations: 1),
"publication-prepare-render",
() => transaction.RenderPublication =
_renderPublisher.PreparePublication(
transaction.Build,
transaction.MeshData)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
while (transaction.PhysicsPublication is null)
{
if (!TryRun(
default,
"publication-prepare-physics",
() => transaction.PhysicsPublication =
_physicsPublisher.CreatePublication(
transaction.RenderPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
while (!transaction.PhysicsPublication.PreparationCommitted)
{
int entityOperations =
transaction.PhysicsPublication.PreparationCursor
< transaction.Build.Landblock.Entities.Count
? 1
: 0;
if (!TryRun(
new StreamingWorkCost(
EntityOperations: entityOperations),
"publication-index-physics",
() => _physicsPublisher.AdvancePreparationOne(
transaction.PhysicsPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
while (transaction.StaticPublication is null)
{
if (!TryRun(
default,
"publication-prepare-statics",
() => transaction.StaticPublication =
_staticPublisher.CreatePublication(
transaction.PhysicsPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
while (!transaction.StaticPublication.PreparationCommitted)
{
int entityOperations =
transaction.StaticPublication.PreparationCursor
< transaction.Build.Landblock.Entities.Count
? 1
: 0;
if (!TryRun(
new StreamingWorkCost(
EntityOperations: entityOperations),
"publication-validate-statics",
() => _staticPublisher.AdvancePreparationOne(
transaction.StaticPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
// Prepare every immutable receipt before the first external
// mutation. Cross-owner identity/data errors therefore block
// the publication without leaving a committed prefix.
while (!transaction.RenderPublication.BeginCommitted)
{
StreamingWorkCost cost =
!transaction.RenderPublication.TerrainCommitted
? new StreamingWorkCost(
GpuUploadBytes:
transaction.Cost.TerrainPayloadBytes)
: !transaction.RenderPublication.VisibilityCommitted
? new StreamingWorkCost(
EntityOperations:
transaction.Cost.VisibilityCells)
: default;
if (!TryRun(
cost,
"publication-render-prefix",
() => _renderPublisher.AdvanceBeginOne(
transaction.RenderPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
while (!transaction.PhysicsPublication.BeginCommitted)
{
int entityOperations =
transaction.PhysicsPublication.TerrainSurface is not null
&& transaction.PhysicsPublication.Build.Landblock.PhysicsDats?
.Info is { } info
&& transaction.PhysicsPublication.CellCursor < info.NumCells
? 1
: transaction.PhysicsPublication.BuildingCursor
< transaction.PhysicsPublication.Buildings.Length
? 1
: 0;
if (!TryRun(
new StreamingWorkCost(
EntityOperations: entityOperations),
"publication-physics-prefix",
() => _physicsPublisher.AdvanceBeginOne(
transaction.PhysicsPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
// Retail establishes the building/visible-cell suffix before
// each static object publishes lights, collision, and scripts.
while (!transaction.RenderPublication.CompletionCommitted)
{
int entityOperations =
transaction.RenderPublication.BuildingPublication is
{ PreparationCommitted: false } buildingPublication
&& buildingPublication.BuildingCursor
< buildingPublication.Buildings.Length
? 1
: transaction.RenderPublication.EnvCellPublication is
{ PreparationCommitted: false } envCellPublication
&& envCellPublication.ShellCursor
< envCellPublication.Build.Shells.Length
? 1
: !transaction.RenderPublication.EnvCellsCommitted
&& transaction.RenderPublication
.EnvCellPublication is null
? transaction.Cost.EnvCellShells
: 0;
if (!TryRun(
new StreamingWorkCost(
EntityOperations: entityOperations),
"publication-render-suffix",
() => _renderPublisher.AdvanceCompleteOne(
transaction.RenderPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
while (!transaction.StaticPublication.BeginCommitted)
{
int entityOperations =
transaction.StaticPublication.PriorCleanupCursor
< transaction.StaticPublication
.OrderedPreviouslyActiveIds.Count
? 1
: 0;
if (!TryRun(
new StreamingWorkCost(
EntityOperations: entityOperations),
"publication-static-cleanup",
() => _staticPublisher.AdvanceBeginOne(
transaction.StaticPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
while (!transaction.PhysicsPublication.CompletionCommitted)
{
int entityOperations =
transaction.PhysicsPublication.GfxCursor
< transaction.PhysicsPublication.GfxObjectIds.Length
? 1
: transaction.PhysicsPublication.PriorStaticCursor
< transaction.PhysicsPublication
.PriorStaticOwnerIds.Length
? 1
: transaction.PhysicsPublication.StaticCursor
< transaction.Build.Landblock.Entities.Count
? 1
: transaction.PhysicsPublication
.RefloodOwnerIds is not null
&& transaction.PhysicsPublication
.RefloodCursor
< transaction.PhysicsPublication
.RefloodOwnerIds.Length
? 1
: 0;
if (!TryRun(
new StreamingWorkCost(
EntityOperations: entityOperations),
"publication-physics-statics",
() => _physicsPublisher.AdvanceCompleteOne(
transaction.PhysicsPublication,
entity =>
_staticPublisher
.PrepareEntityBeforeCollision(
transaction.StaticPublication,
entity))))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
while (!transaction.StaticPublication.CompletionCommitted)
{
int entityOperations =
transaction.StaticPublication.PluginCursor
< transaction.StaticPublication.OrderedSnapshots.Count
? 1
: 0;
if (!TryRun(
new StreamingWorkCost(
EntityOperations: entityOperations),
"publication-plugin-projection",
() => _staticPublisher.AdvanceCompleteOne(
transaction.StaticPublication)))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
}
else
{
StreamingWorkCost legacyCost = new(
EntityOperations: transaction.Cost.Entities,
GpuUploadBytes: transaction.Cost.TerrainPayloadBytes);
bool ran = TryRun(
legacyCost,
"publication-legacy-presentation",
() =>
{
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;
}
});
if (!ran)
{
return new LandblockPublicationAdvance(false, progressed);
}
}
transaction.PresentationCommitted = true;
}
if (!transaction.SpatialPresentationCommitted)
{
// Keep live-projection visibility publication at the shipped
// boundary: bucket mutation, pin/script reconciliation, then the
// outer spatial observer commit. If pinning throws, the receipt is
// retained and only that suffix retries.
if (!TryRun(
default,
"publication-spatial-commit",
() =>
{
using GpuWorldState.MutationBatch mutation =
_state.BeginMutationBatch();
if (!transaction.SpatialCommitted)
{
IEnumerable<ulong>? renderIds =
transaction.Build.EnvCells?.Shells.Select(
static shell => shell.GeometryId);
transaction.SpatialPublication = transaction.Kind switch
{
PublicationKind.Loaded
or PublicationKind.PromoteSelfContained
or PublicationKind.Far =>
_state.CommitLandblockSpatial(
transaction.Build.Landblock,
renderIds,
transaction.Tier),
PublicationKind.PromoteExisting =>
_state.CommitEntitiesToExistingLandblockSpatial(
transaction.LandblockId,
transaction.Build.Landblock.Entities,
renderIds),
_ => throw new InvalidOperationException(
$"Unknown landblock publication kind {transaction.Kind}."),
};
transaction.SpatialCommitted = true;
}
_state.ActivateLandblockPresentation(
transaction.SpatialPublication
?? throw new InvalidOperationException(
"A committed spatial publication has no activation receipt."));
if (transaction.SpatialPublication.RequiresActivation)
{
_staticProjectionSink?.Reconcile(
transaction.Build,
transaction.SpatialPublication);
}
transaction.SpatialPresentationCommitted = true;
}))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
if (!transaction.EnvCellReplayCommitted)
{
if (!TryRun(
new StreamingWorkCost(
EntityOperations: transaction.Cost.EnvCellShells),
"publication-envcell-replay",
() =>
{
if (transaction.Kind != PublicationKind.Far
&& transaction.Build.EnvCells is
{
Shells.Length: > 0,
} envCells)
{
_ensureEnvCellMeshes?.Invoke(envCells);
}
transaction.EnvCellReplayCommitted = true;
}))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
if (!transaction.LiveRecoveryCommitted)
{
// Retained live projections can materialize only after the
// canonical bucket exists. Landblock load never reconstructs their
// identity or replays create-time resources.
if (!TryRun(
default,
"publication-live-recovery",
() =>
{
_onLandblockLoaded?.Invoke(transaction.LandblockId);
transaction.LiveRecoveryCommitted = true;
}))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
_publications.Remove(result);
return new LandblockPublicationAdvance(true, progressed);
}
}