Harden movie playback lifecycle and diagnostics

This commit is contained in:
gamer147
2026-07-22 09:44:57 -04:00
parent 816673cf6a
commit 4f5137a98f
25 changed files with 1145 additions and 147 deletions

View File

@@ -31,6 +31,22 @@ public readonly record struct NumericGlyphStyle(int SurfaceSlot, int AtlasX, int
public bool Registered => SurfaceSlot != 0;
}
public sealed record BlockingGfxObjectDiagnostic(
long Handle, int SourceSlot, long StartMs, long ControlFlags,
bool ColorEnabled, long ColorDelayMs, long ColorDurationMs,
bool ScaleEnabled, long ScaleDelayMs, long ScaleDurationMs,
bool RotationEnabled, long RotationDelayMs, long RotationDurationMs,
bool TranslationEnabled, long TranslationDelayMs, long TranslationDurationMs);
public sealed record GfxDiagnosticSnapshot(
long NowMs, bool HasActiveTimedPresentation, int ObjectCount, int VisibleObjectCount,
int ActiveSurfaceTransitionCount, long AnimationServiceFlags,
long AnimClockDurationTicks, long AnimClockGeneration,
uint PreviousFrameTimeMilliseconds, uint CurrentFrameTimeMilliseconds,
long RangeTransformFirst, long RangeTransformCount,
BlockingGfxObjectDiagnostic? BlockingRangeTransform,
IReadOnlyList<BlockingGfxObjectDiagnostic> BlockingObjects);
/// <summary>A renderable view of one visible gfx object — the host composites these in ascending-handle order
/// (= the engine's z-order) each frame. Built by <see cref="GfxState.SnapshotVisibleObjects"/>; the surface
/// resId/colorkey are resolved from the object's live source slot at snapshot time (see docs/engine-re.md,
@@ -545,6 +561,51 @@ public sealed class GfxState
o.RotationChannelEnabled || o.TranslationEnabled));
}
/// <summary>Observe-only state for a runtime stall capture. It identifies the exact finite channels
/// which can keep the host's op-0x21c presentation wait active.</summary>
public GfxDiagnosticSnapshot CaptureDiagnosticSnapshot(long nowMs)
{
lock (_lock)
{
BlockingGfxObjectDiagnostic? range = HasBlockingChannels(_rangeTransform)
? DescribeBlockingObject(-1, _rangeTransform)
: null;
var objects = _objects
.Where(pair => pair.Value.Visible
&& (pair.Value.OneShotAnimationControlFlags & 1) == 0
&& HasBlockingChannels(pair.Value))
.OrderBy(pair => pair.Key)
.Select(pair => DescribeBlockingObject(pair.Key, pair.Value))
.ToArray();
return new GfxDiagnosticSnapshot(
nowMs,
_surfaceTransitions.Values.Any(t => TransitionProgress(t, nowMs) < 1.0)
|| range != null || objects.Length != 0,
_objects.Count,
_objects.Values.Count(o => o.Visible),
_surfaceTransitions.Values.Count(t => TransitionProgress(t, nowMs) < 1.0),
AnimationServiceFlags,
AnimClockDurationTicks,
AnimClockGeneration,
PreviousFrameTimeMilliseconds,
CurrentFrameTimeMilliseconds,
_rangeTransformFirst,
_rangeTransformCount,
range,
objects);
}
}
private static bool HasBlockingChannels(GfxObject o)
=> o.OneShotColorEnabled || o.ScaleEnabled || o.RotationChannelEnabled || o.TranslationEnabled;
private static BlockingGfxObjectDiagnostic DescribeBlockingObject(long handle, GfxObject o)
=> new(handle, o.SourceSlot, o.OneShotStartMs, o.OneShotAnimationControlFlags,
o.OneShotColorEnabled, o.ColorDelayMs, o.ColorDurationMs,
o.ScaleEnabled, o.ScaleDelayMs, o.ScaleDurationMs,
o.RotationChannelEnabled, o.RotationDelayMs, o.RotationDurationMs,
o.TranslationEnabled, o.TranslationDelayMs, o.TranslationDurationMs);
/// <summary>Op 0x242: replace the retained object's animation-control word. Native bit 0 makes its
/// finite one-shot channels nonblocking and immune to op 0x243 forced completion.</summary>
public void SetOneShotAnimationControl(long handle, long flags)

View File

@@ -179,23 +179,27 @@ public sealed class Sys4AssetCatalog
.Where(f => f.Name.EndsWith(".BIN", StringComparison.OrdinalIgnoreCase))
.Select(f => f.Name.ToUpperInvariant()).ToArray();
/// <summary>Enumerate every real asset in native packed-id order, including mounted append packs.</summary>
public IReadOnlyList<PackedAssetEntry> EnumerateAssets()
{
var assets = new List<PackedAssetEntry>();
AddAssets(this, assets);
foreach (var append in _appendPacks.OrderBy(pair => pair.Key).Select(pair => pair.Value))
AddAssets(append, assets);
return assets;
}
/// <summary>Enumerate every script in native packed-id order, including mounted append packs.
/// Placeholder slots and non-script assets are excluded without collapsing raw indices.</summary>
public IReadOnlyList<PackedAssetEntry> EnumerateScripts()
{
var scripts = new List<PackedAssetEntry>();
AddScripts(this, scripts);
foreach (var append in _appendPacks.OrderBy(pair => pair.Key).Select(pair => pair.Value))
AddScripts(append, scripts);
return scripts;
}
=> EnumerateAssets().Where(entry =>
entry.Asset.Name.EndsWith(".BIN", StringComparison.OrdinalIgnoreCase)).ToArray();
private static void AddScripts(Sys4AssetCatalog catalog, List<PackedAssetEntry> scripts)
private static void AddAssets(Sys4AssetCatalog catalog, List<PackedAssetEntry> assets)
{
long selector = (long)catalog.PackId << 24;
foreach (var entry in catalog.Files)
if (entry.Name.EndsWith(".BIN", StringComparison.OrdinalIgnoreCase))
scripts.Add(new PackedAssetEntry(selector | (uint)entry.RawIndex, entry));
assets.Add(new PackedAssetEntry(selector | (uint)entry.RawIndex, entry));
}
private static Dictionary<string, (int Start, int End)> BuildSceneRanges(IReadOnlyList<AssetEntry> files)