perf(streaming): cursor publication across frame budgets

This commit is contained in:
Erik 2026-07-24 19:10:18 +02:00
parent bb16f74fd4
commit 98f1ac8934
32 changed files with 2215 additions and 823 deletions

View file

@ -14,6 +14,10 @@ public readonly record struct LandblockPresentationDiagnostics(
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
@ -48,6 +52,7 @@ public sealed class LandblockPresentationPipeline
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;
@ -186,7 +191,19 @@ public sealed class LandblockPresentationPipeline
ArgumentNullException.ThrowIfNull(result);
if (!_publications.TryGetValue(result, out PublicationTransaction? transaction))
return;
Advance(result, transaction);
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();
@ -223,8 +240,28 @@ public sealed class LandblockPresentationPipeline
loaded.Build,
loaded.MeshData,
loaded.LandblockId,
loaded.Tier);
Advance(loaded, transaction);
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(
@ -241,8 +278,32 @@ public sealed class LandblockPresentationPipeline
promoted.Build,
promoted.MeshData,
promoted.LandblockId,
LandblockStreamTier.Near);
Advance(promoted, transaction);
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(
@ -270,6 +331,11 @@ public sealed class LandblockPresentationPipeline
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);
@ -280,7 +346,56 @@ public sealed class LandblockPresentationPipeline
$"Landblock publication 0x{transaction.LandblockId:X8} changed kind while pending.");
}
Advance(acceptedResult, transaction);
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(
@ -289,7 +404,8 @@ public sealed class LandblockPresentationPipeline
LandblockBuild build,
LandblockMeshData meshData,
uint landblockId,
LandblockStreamTier tier)
LandblockStreamTier tier,
LandblockStreamCostEstimate? estimate)
{
if (_publications.TryGetValue(result, out PublicationTransaction? existing))
{
@ -311,71 +427,317 @@ public sealed class LandblockPresentationPipeline
Build = build,
MeshData = meshData,
LandblockId = landblockId,
Cost = estimate
?? LandblockStreamResultCost.Estimate(build, meshData),
Tier = tier,
};
_publications.Add(result, created);
return created;
}
private void Advance(
private LandblockPublicationAdvance Advance(
LandblockStreamResult result,
PublicationTransaction transaction)
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)
{
transaction.RenderPublication ??=
_renderPublisher.PreparePublication(
transaction.Build,
transaction.MeshData);
transaction.PhysicsPublication ??=
_physicsPublisher.PreparePublication(
transaction.RenderPublication);
transaction.StaticPublication ??=
_staticPublisher.PreparePublication(
transaction.PhysicsPublication);
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.
_renderPublisher.BeginPublication(transaction.RenderPublication);
_physicsPublisher.BeginPublication(transaction.PhysicsPublication);
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.
_renderPublisher.CompletePublication(
transaction.RenderPublication);
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);
}
}
_staticPublisher.BeginPublication(transaction.StaticPublication);
_physicsPublisher.CompletePublication(
transaction.PhysicsPublication,
entity => _staticPublisher.PrepareEntityBeforeCollision(
transaction.StaticPublication,
entity));
_staticPublisher.CompletePublication(
transaction.StaticPublication);
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
{
try
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)
{
(_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;
return new LandblockPublicationAdvance(false, progressed);
}
}
transaction.PresentationCommitted = true;
@ -387,46 +749,70 @@ public sealed class LandblockPresentationPipeline
// boundary: bucket mutation, pin/script reconciliation, then the
// outer spatial observer commit. If pinning throws, the receipt is
// retained and only that suffix retries.
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;
}
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."));
transaction.SpatialPresentationCommitted = true;
_state.ActivateLandblockPresentation(
transaction.SpatialPublication
?? throw new InvalidOperationException(
"A committed spatial publication has no activation receipt."));
transaction.SpatialPresentationCommitted = true;
}))
{
return new LandblockPublicationAdvance(false, progressed);
}
}
if (!transaction.EnvCellReplayCommitted)
{
if (transaction.Kind != PublicationKind.Far
&& transaction.Build.EnvCells is { Shells.Length: > 0 } envCells)
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;
}))
{
_ensureEnvCellMeshes?.Invoke(envCells);
return new LandblockPublicationAdvance(false, progressed);
}
transaction.EnvCellReplayCommitted = true;
}
if (!transaction.LiveRecoveryCommitted)
@ -434,10 +820,20 @@ public sealed class LandblockPresentationPipeline
// Retained live projections can materialize only after the
// canonical bucket exists. Landblock load never reconstructs their
// identity or replays create-time resources.
_onLandblockLoaded?.Invoke(transaction.LandblockId);
transaction.LiveRecoveryCommitted = true;
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);
}
}