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.
148 lines
6.1 KiB
C#
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}";
|
|
}
|
|
}
|