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

@ -24,7 +24,9 @@ public sealed class LandblockRenderPublication
Vector3 origin,
Dictionary<uint, LoadedCell> visibilityCells,
Vector3 aabbMin,
Vector3 aabbMax)
Vector3 aabbMax,
BuildingRegistryPublication? buildingPublication,
EnvCellLandblockPublication? envCellPublication)
{
Owner = owner;
Build = build;
@ -33,6 +35,8 @@ public sealed class LandblockRenderPublication
_visibilityCells = new ReadOnlyDictionary<uint, LoadedCell>(visibilityCells);
AabbMin = aabbMin;
AabbMax = aabbMax;
BuildingPublication = buildingPublication;
EnvCellPublication = envCellPublication;
}
internal object Owner { get; }
@ -40,6 +44,8 @@ public sealed class LandblockRenderPublication
internal LandblockMeshData MeshData { get; }
internal Vector3 AabbMin { get; }
internal Vector3 AabbMax { get; }
internal BuildingRegistryPublication? BuildingPublication { get; }
internal EnvCellLandblockPublication? EnvCellPublication { get; }
internal bool TerrainCommitted { get; set; }
internal bool VisibilityCommitted { get; set; }
internal bool AabbCommitted { get; set; }
@ -92,6 +98,7 @@ public sealed class LandblockRenderPublisher
private readonly CellVisibility _cellVisibility;
private readonly GpuWorldState _worldState;
private readonly Action<EnvCellLandblockBuild>? _commitEnvCells;
private readonly IEnvCellLandblockPublisher? _envCellPublisher;
private readonly Action<EnvCellLandblockBuild>? _prepareEnvCells;
private readonly Action<uint>? _removeEnvCells;
private readonly Dictionary<uint, BuildingRegistry> _buildingRegistries = new();
@ -114,7 +121,8 @@ public sealed class LandblockRenderPublisher
GpuWorldState worldState,
Action<EnvCellLandblockBuild>? commitEnvCells = null,
Action<EnvCellLandblockBuild>? prepareEnvCells = null,
Action<uint>? removeEnvCells = null)
Action<uint>? removeEnvCells = null,
IEnvCellLandblockPublisher? envCellPublisher = null)
{
ArgumentNullException.ThrowIfNull(publishTerrain);
ArgumentNullException.ThrowIfNull(removeTerrain);
@ -126,6 +134,13 @@ public sealed class LandblockRenderPublisher
_cellVisibility = cellVisibility;
_worldState = worldState;
_commitEnvCells = commitEnvCells;
_envCellPublisher = envCellPublisher;
if (_commitEnvCells is not null && _envCellPublisher is not null)
{
throw new ArgumentException(
"Supply either the retained EnvCell publisher or the compatibility callback, not both.",
nameof(envCellPublisher));
}
_prepareEnvCells = prepareEnvCells;
_removeEnvCells = removeEnvCells;
}
@ -197,6 +212,17 @@ public sealed class LandblockRenderPublisher
visibilityCells[cell.CellId] = cell;
}
(Vector3 aabbMin, Vector3 aabbMax) = ComputeAabb(meshData, origin);
BuildingRegistryPublication? buildingPublication =
build.Landblock.PhysicsDats?.Info is { } info
? BuildingLoader.PreparePublication(
info,
landblockId,
visibilityCells)
: null;
EnvCellLandblockPublication? envCellPublication =
build.EnvCells is { } retainedEnvCells
? _envCellPublisher?.PreparePublication(retainedEnvCells)
: null;
return new LandblockRenderPublication(
_receiptOwner,
build,
@ -204,7 +230,9 @@ public sealed class LandblockRenderPublisher
origin,
visibilityCells,
aabbMin,
aabbMax);
aabbMax,
buildingPublication,
envCellPublication);
}
/// <summary>
@ -214,13 +242,25 @@ public sealed class LandblockRenderPublisher
public void BeginPublication(LandblockRenderPublication publication)
{
ValidateReceipt(publication);
if (publication.BeginCommitted)
return;
while (!AdvanceBeginOne(publication))
{
}
}
long beginStarted = Stopwatch.GetTimestamp();
/// <summary>
/// Advances one exact render-prefix mutation from the retained receipt.
/// Terrain, visibility, and AABB publication remain ordered and retries do
/// not replay an already committed prefix.
/// </summary>
internal bool AdvanceBeginOne(LandblockRenderPublication publication)
{
ValidateReceipt(publication);
if (publication.BeginCommitted)
return true;
long started = Stopwatch.GetTimestamp();
LandblockBuild build = publication.Build;
uint landblockId = publication.LandblockId;
if (!publication.TerrainCommitted)
{
long terrainStarted = Stopwatch.GetTimestamp();
@ -228,15 +268,13 @@ public sealed class LandblockRenderPublisher
_terrainPublishTicks += Stopwatch.GetTimestamp() - terrainStarted;
publication.TerrainCommitted = true;
}
if (!publication.VisibilityCommitted)
else if (!publication.VisibilityCommitted)
{
if (build.EnvCells is { } envCells)
_cellVisibility.CommitLandblock(landblockId, envCells.VisibilityCells);
publication.VisibilityCommitted = true;
}
if (!publication.AabbCommitted)
else if (!publication.AabbCommitted)
{
_worldState.SetLandblockAabb(
landblockId,
@ -244,10 +282,14 @@ public sealed class LandblockRenderPublisher
publication.AabbMax);
publication.AabbCommitted = true;
}
else
{
publication.BeginCommitted = true;
_beginCount++;
}
publication.BeginCommitted = true;
_beginCount++;
_beginPublishTicks += Stopwatch.GetTimestamp() - beginStarted;
_beginPublishTicks += Stopwatch.GetTimestamp() - started;
return publication.BeginCommitted;
}
/// <summary>
@ -256,40 +298,77 @@ public sealed class LandblockRenderPublisher
/// receives the complete immutable transaction.
/// </summary>
public void CompletePublication(LandblockRenderPublication publication)
{
ValidateReceipt(publication);
if (!publication.BeginCommitted)
throw new InvalidOperationException(
"Render publication cannot complete before its prefix commits.");
while (!AdvanceCompleteOne(publication))
{
}
}
/// <summary>
/// Advances one exact render-suffix mutation. The building registry is
/// visible before the EnvCell renderer snapshot, matching the existing
/// retail-rooted ordering.
/// </summary>
internal bool AdvanceCompleteOne(LandblockRenderPublication publication)
{
ValidateReceipt(publication);
if (!publication.BeginCommitted)
throw new InvalidOperationException(
"Render publication cannot complete before its prefix commits.");
if (publication.CompletionCommitted)
return;
return true;
long started = Stopwatch.GetTimestamp();
LandblockBuild build = publication.Build;
uint landblockId = publication.LandblockId;
if (!publication.BuildingRegistryCommitted)
if (publication.BuildingPublication is { PreparationCommitted: false }
buildingPublication)
{
if (build.Landblock.PhysicsDats?.Info is { } info)
BuildingLoader.AdvancePreparationOne(buildingPublication);
}
else if (!publication.BuildingRegistryCommitted)
{
if (publication.BuildingPublication is { } completedBuildings)
{
BuildingLoader.CommitPublication(completedBuildings);
uint registryKey = landblockId & 0xFFFF0000u;
_buildingRegistries[registryKey] = BuildingLoader.Build(
info,
landblockId,
publication.VisibilityCells);
_buildingRegistries[registryKey] =
completedBuildings.Registry;
}
publication.BuildingRegistryCommitted = true;
}
if (!publication.EnvCellsCommitted)
else if (publication.EnvCellPublication is { } envCellPublication
&& !envCellPublication.PreparationCommitted)
{
if (build.EnvCells is { } envCells)
_envCellPublisher?.AdvancePreparationOne(envCellPublication);
}
else if (!publication.EnvCellsCommitted)
{
if (publication.EnvCellPublication is { } retained)
{
(_envCellPublisher
?? throw new InvalidOperationException(
"A retained EnvCell receipt has no owning publisher."))
.CommitPublication(retained);
}
else if (build.EnvCells is { } envCells)
{
_commitEnvCells?.Invoke(envCells);
}
publication.EnvCellsCommitted = true;
}
else
{
publication.CompletionCommitted = true;
_completeCount++;
}
publication.CompletionCommitted = true;
_completeCount++;
_completePublishTicks += Stopwatch.GetTimestamp() - started;
return publication.CompletionCommitted;
}
/// <summary>