diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index e64b042d..e6d1d6e2 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -228,6 +229,50 @@ public sealed partial class WbDrawDispatcher : IDisposable + (_compositeWarmupScanComplete ? 0 : entities.Count - _compositeWarmupScanIndex); CompositeTexturesReady = _compositeWarmupScanComplete && _compositeWarmupQueue.Count == 0; + if (!CompositeTexturesReady + && AcDream.Core.Net.NetDiagnostics.ProbeReveal) + { + ProbeRevealWarmupStall(); + } + } + + // #260 probe: while composite warmup blocks a reveal, name the stall once + // per second. The two permanent-stall shapes — a GfxObj id that never + // resolves (silent load failure, e.g. custom-server content missing from + // the baked pak) versus an upload budget that never reopens — are + // otherwise indistinguishable from the reveal log's composites=False. + private long _probeWarmupLastEmitTs; + + private void ProbeRevealWarmupStall() + { + long now = Stopwatch.GetTimestamp(); + if (_probeWarmupLastEmitTs != 0 + && now - _probeWarmupLastEmitTs < Stopwatch.Frequency) + { + return; + } + _probeWarmupLastEmitTs = now; + + var pendingIds = new System.Text.StringBuilder(); + int listed = 0; + foreach (WorldEntity entity in _compositeWarmupQueue) + { + if (listed >= 4) + break; + ulong gfxObjId = entity.MeshRefs.Count > 0 + ? entity.MeshRefs[0].GfxObjId + : 0; + if (listed > 0) + pendingIds.Append(','); + pendingIds.Append($"0x{gfxObjId:X8}"); + listed++; + } + Console.WriteLine( + $"[composite-warmup] STALL pending={LastCompositeWarmupPendingCount}" + + $" queue={_compositeWarmupQueue.Count}" + + $" scanComplete={_compositeWarmupScanComplete}" + + $" uploadOpen={_textures.CanStartCompositeUpload}" + + $" firstPending=[{pendingIds}]"); } internal static bool RequiresCompositeWarmupRebuild( diff --git a/src/AcDream.Core.Net/NetDiagnostics.cs b/src/AcDream.Core.Net/NetDiagnostics.cs index 7ac800e3..e885dd25 100644 --- a/src/AcDream.Core.Net/NetDiagnostics.cs +++ b/src/AcDream.Core.Net/NetDiagnostics.cs @@ -33,4 +33,16 @@ public static class NetDiagnostics /// public static bool ProbeNet { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_NET") == "1"; + + /// + /// ACDREAM_PROBE_REVEAL=1 — #260 reveal-stall probe: while a + /// reveal destination's composite warmup is incomplete, emit one + /// [composite-warmup] line per second naming the pending queue + /// depth, scan state, upload-budget gate, and the first few unresolved + /// GfxObj ids. The two permanent-stall shapes (a mesh id that never + /// resolves vs. an upload budget that never reopens) are otherwise + /// indistinguishable and invisible. + /// + public static bool ProbeReveal { get; set; } = + Environment.GetEnvironmentVariable("ACDREAM_PROBE_REVEAL") == "1"; }