acdream/src/AcDream.App/Rendering/TerrainDrawDiagnosticsController.cs
Erik 420e5eea70 refactor(app): key live projections by runtime identity
Move materialized live-object sidecars and presentation worksets to exact RuntimeEntityKey ownership. Runtime remains the only GUID/incarnation/local-ID authority while hydration, animation, effects, lights, equipped children, renderer resources, visibility, liveness, and teardown resolve exact projection identities. Preserve synchronous callbacks, local-ID allocation order, and current rendering behavior.
2026-07-25 21:50:58 +02:00

148 lines
6.1 KiB
C#

using System.Diagnostics;
using AcDream.App.Streaming;
using AcDream.App.World;
using AcDream.App.Rendering.Wb;
namespace AcDream.App.Rendering;
internal readonly record struct FramePipelineDiagnosticFacts(
LandblockPresentationDiagnostics Presentation,
RollingTimingPercentiles UploadTiming,
int EntitiesWalked,
int DeferredApplyBacklog,
int FullWindowRetirementCount,
int LastFullWindowRetirementLandblockCount,
int MaterializedEntityCount,
int SpawnSnapshotCount,
int ResidentEntityCount);
internal interface IFramePipelineDiagnosticFactsSource
{
TerrainRenderDiagnosticFacts CaptureTerrain();
FramePipelineDiagnosticFacts CaptureFrame();
}
/// <summary>
/// Samples the existing canonical runtime owners only when the five-second
/// frame diagnostic cadence publishes. It owns no world or render resource.
/// </summary>
internal sealed class RuntimeFramePipelineDiagnosticFactsSource :
IFramePipelineDiagnosticFactsSource
{
private readonly TerrainModernRenderer? _terrain;
private readonly LandblockPresentationPipeline? _presentation;
private readonly RuntimeRenderFrameLivePreparation? _livePreparation;
private readonly WbDrawDispatcher? _dispatcher;
private readonly StreamingController? _streaming;
private readonly LiveEntityRuntime _liveEntities;
private readonly GpuWorldState _world;
public RuntimeFramePipelineDiagnosticFactsSource(
TerrainModernRenderer? terrain,
LandblockPresentationPipeline? presentation,
RuntimeRenderFrameLivePreparation? livePreparation,
WbDrawDispatcher? dispatcher,
StreamingController? streaming,
LiveEntityRuntime liveEntities,
GpuWorldState world)
{
_terrain = terrain;
_presentation = presentation;
_livePreparation = livePreparation;
_dispatcher = dispatcher;
_streaming = streaming;
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
_world = world ?? throw new ArgumentNullException(nameof(world));
}
public TerrainRenderDiagnosticFacts CaptureTerrain() => new(
_terrain?.VisibleSlots ?? 0,
_terrain?.LoadedSlots ?? 0,
_terrain?.CapacitySlots ?? 0);
public FramePipelineDiagnosticFacts CaptureFrame() => new(
_presentation?.Diagnostics ?? default,
_livePreparation?.UploadTiming ?? default,
_dispatcher?.LastDrawStats.EntitiesWalked ?? 0,
_streaming?.DeferredApplyBacklog ?? 0,
_streaming?.FullWindowRetirementCount ?? 0,
_streaming?.LastFullWindowRetirementLandblockCount ?? 0,
_liveEntities.MaterializedCount,
_liveEntities.Snapshots.Count,
_world.Entities.Count);
}
/// <summary>
/// Keeps the terrain timing sample and the frame-pipeline rollup on one
/// failure-atomic publication cadence. The owner is shared by fallback and
/// PView terrain passes, so extraction cannot silently split their telemetry.
/// </summary>
internal sealed class TerrainDrawDiagnosticsController
{
internal const long PublicationIntervalMilliseconds = 5_000;
private readonly bool _enabled;
private readonly WorldRenderDiagnostics _world;
private readonly IFramePipelineDiagnosticFactsSource _facts;
private readonly IRenderFrameDiagnosticLog _log;
private long _lastPublicationMilliseconds;
public TerrainDrawDiagnosticsController(
bool enabled,
WorldRenderDiagnostics world,
IFramePipelineDiagnosticFactsSource facts,
IRenderFrameDiagnosticLog log)
{
_enabled = enabled;
_world = world ?? throw new ArgumentNullException(nameof(world));
_facts = facts ?? throw new ArgumentNullException(nameof(facts));
_log = log ?? throw new ArgumentNullException(nameof(log));
}
public void Begin() => _world.BeginTerrainDraw();
public void Complete() => Complete(_enabled ? Environment.TickCount64 : 0L);
internal void Complete(long nowMilliseconds)
{
_world.EndTerrainDraw();
if (!_enabled
|| nowMilliseconds - _lastPublicationMilliseconds <= PublicationIntervalMilliseconds)
{
return;
}
// Both writes are one diagnostic transaction. Any failure leaves the
// cadence uncommitted so a later terrain draw retries both publications.
_world.PublishTerrainDiagnostics(_facts.CaptureTerrain());
_log.WriteLine(FormatFrame(_facts.CaptureFrame()));
_lastPublicationMilliseconds = nowMilliseconds;
}
internal static string FormatFrame(FramePipelineDiagnosticFacts facts)
{
LandblockRenderPublisherDiagnostics render = facts.Presentation.Render;
LandblockPhysicsPublisherDiagnostics physics = facts.Presentation.Physics;
LandblockStaticPresentationDiagnostics statics = facts.Presentation.Statics;
double ticksToMicros = 1_000_000.0 / Stopwatch.Frequency;
double uploadMedian = facts.UploadTiming.MedianHundredthsMicroseconds / 100.0;
double uploadP95 = facts.UploadTiming.Percentile95HundredthsMicroseconds / 100.0;
return
$"[FRAME-DIAG] publish={render.BeginCount}/{render.CompleteCount} "
+ $"render_total_us=[terrain={render.TerrainPublishTicks * ticksToMicros:F1} "
+ $"begin={render.BeginPublishTicks * ticksToMicros:F1} "
+ $"complete={render.CompletePublishTicks * ticksToMicros:F1}] "
+ $"physics_total_us=[base={physics.BasePublishTicks * ticksToMicros:F1} "
+ $"gfx={physics.GfxCacheTicks * ticksToMicros:F1} "
+ $"complete={physics.CompletePublishTicks * ticksToMicros:F1}] "
+ $"static={statics.BeginCount}/{statics.CompleteCount}"
+ $"(active={statics.ActiveEntityCount}) "
+ $"entUpl_us={uploadMedian:F1}m/{uploadP95:F1}p95 "
+ $"deferred={facts.DeferredApplyBacklog} "
+ $"fullRetire={facts.FullWindowRetirementCount}"
+ $"(landblocks={facts.LastFullWindowRetirementLandblockCount}) "
+ $"esg={facts.MaterializedEntityCount} spawn={facts.SpawnSnapshotCount} "
+ $"resident={facts.ResidentEntityCount} walked={facts.EntitiesWalked}";
}
}