Fix ADV transition and nested input lifecycles
This commit is contained in:
@@ -94,6 +94,7 @@ public sealed class GodotAdvHost : IHost
|
||||
private volatile bool _modalMovieWaiting;
|
||||
private volatile bool _modalMovieCancelled;
|
||||
private GfxState? _foregroundGfx;
|
||||
private volatile bool _foregroundTransitionWaitBypassed;
|
||||
private readonly object _screenTransitionLock = new();
|
||||
private readonly Dictionary<int, IReadOnlyList<RenderObject>> _renderTargetSnapshots = new();
|
||||
private readonly object _backbufferRangeLock = new();
|
||||
@@ -662,13 +663,16 @@ public sealed class GodotAdvHost : IHost
|
||||
{
|
||||
if (IsTransitionWaiting && _foregroundGfx != null)
|
||||
{
|
||||
int completed = _foregroundGfx.CompleteClickSkippableTimedPresentation(_clock.NowMs);
|
||||
if (completed > 0)
|
||||
bool accepted = _foregroundGfx.TryCompleteClickSkippableTimedPresentation(
|
||||
_clock.NowMs, out int completed);
|
||||
if (accepted)
|
||||
{
|
||||
_timeline?.State("transition-forced-complete", new()
|
||||
_foregroundTransitionWaitBypassed = true;
|
||||
_timeline?.State("transition-skip-accepted", new()
|
||||
{
|
||||
["count"] = completed,
|
||||
["endpoints_completed"] = completed,
|
||||
["source"] = source,
|
||||
["wait_bypassed"] = true,
|
||||
});
|
||||
_frameSignal.Set();
|
||||
return true;
|
||||
@@ -781,6 +785,7 @@ public sealed class GodotAdvHost : IHost
|
||||
|
||||
public void WaitForForegroundTransition(GfxState gfx)
|
||||
{
|
||||
_foregroundTransitionWaitBypassed = false;
|
||||
int started = gfx.StartForegroundTransitions(_clock.NowMs);
|
||||
bool hasActivePresentation =
|
||||
gfx.HasActiveTimedPresentation(_clock.NowMs) || HasActiveMoviePresentation();
|
||||
@@ -790,6 +795,10 @@ public sealed class GodotAdvHost : IHost
|
||||
System.Threading.Interlocked.Exchange(ref _transitionStartedAtMs, _clock.NowMs);
|
||||
IsTransitionWaiting = true;
|
||||
_timeline?.State("transition-start", new() { ["count"] = started });
|
||||
// Skip may already have been active before this service boundary. Native ADV script usually
|
||||
// selects 0x243+0x20c in that case, but applying it here also covers transition helpers that
|
||||
// enter run-state 0x400 without repeating the script-side query.
|
||||
if (_messageSkipActive) TryForceActiveTransition("message-skip");
|
||||
}
|
||||
bool scriptSuspended = SuspendScriptForPresentation();
|
||||
try
|
||||
@@ -797,7 +806,9 @@ public sealed class GodotAdvHost : IHost
|
||||
RequestSynchronizedPresentation();
|
||||
if (!hasActivePresentation) return;
|
||||
int lastBucket = -1;
|
||||
while ((gfx.HasActiveTimedPresentation(_clock.NowMs) || HasActiveMoviePresentation()) && !_stopping)
|
||||
while (!_foregroundTransitionWaitBypassed
|
||||
&& (gfx.HasActiveTimedPresentation(_clock.NowMs) || HasActiveMoviePresentation())
|
||||
&& !_stopping)
|
||||
{
|
||||
var active = gfx.SnapshotForegroundTransitions(_clock.NowMs);
|
||||
int bucket = active.Count == 0 ? 100 : (int)System.Math.Floor(active[0].Progress * 10);
|
||||
@@ -812,8 +823,8 @@ public sealed class GodotAdvHost : IHost
|
||||
}
|
||||
_frameSignal.WaitOne(50);
|
||||
}
|
||||
// The active query becomes false at the exact transition/movie endpoint. Publish that terminal sample
|
||||
// once so the last visible frame cannot remain fractionally incomplete.
|
||||
// Publish the natural terminal sample or the forced service-resume sample once before the
|
||||
// following script burst mutates/releases its retained inputs.
|
||||
RequestSynchronizedPresentation();
|
||||
}
|
||||
finally
|
||||
@@ -821,6 +832,7 @@ public sealed class GodotAdvHost : IHost
|
||||
ResumeScriptAfterPresentation(scriptSuspended);
|
||||
}
|
||||
IsTransitionWaiting = false;
|
||||
_foregroundTransitionWaitBypassed = false;
|
||||
System.Threading.Interlocked.Exchange(ref _transitionStartedAtMs, -1);
|
||||
_foregroundGfx = null;
|
||||
_timeline?.State("running", new() { ["transition_complete"] = true });
|
||||
@@ -1102,13 +1114,10 @@ public sealed class GodotAdvHost : IHost
|
||||
_frameSignal.Set();
|
||||
}
|
||||
|
||||
// Ordinary opcode bursts run to the next service boundary without frame pacing. Persistent message
|
||||
// Skip removes most of those boundaries, but native adv_interpreter_tick still executes one opcode per
|
||||
// engine tick; retain that cadence here so Skip advances quickly instead of free-running whole scenes.
|
||||
public void FrameYield()
|
||||
{
|
||||
if (_messageSkipActive && !_stopping) _frameSignal.WaitOne(50);
|
||||
}
|
||||
// Native dispatch remains burst-fast between explicit presentation/sleep/input services even when
|
||||
// message Skip removes those services. Per-op frame pacing turns ordinary skipped setup/cleanup bursts
|
||||
// into multi-second invisible stalls.
|
||||
public void FrameYield() { }
|
||||
|
||||
// op 0xc8: block the VM background thread while the main-thread compositor keeps presenting retained state.
|
||||
// Time-based sibling of WaitForInput's suspend. The native op arms a non-blocking main-loop-polled timer;
|
||||
|
||||
@@ -265,6 +265,7 @@ public partial class Main : Godot.Control
|
||||
double sleepScale = 1.0; // --sleep-scale <f>: scale explicit op-0xc8 holds
|
||||
double speed = 1.0; // --speed <f>: sleeps + retained presentation clocks
|
||||
long transitionClickMs = -1; // --transition-click-ms <n>: force active transitions after n virtual ms
|
||||
bool holdMessageSkip = false; // --hold-message-skip: hold native logical action 6 for diagnostics
|
||||
string? histFile = null; // --trace-histogram <file>: op/call-site execution counts of the REAL run
|
||||
string? pageMapPath = null; // --page-map <jsonl>: override default build/page-map-SCxxxx.jsonl
|
||||
for (int i = 0; i < userArgs.Length; i++)
|
||||
@@ -289,6 +290,7 @@ public partial class Main : Godot.Control
|
||||
if (userArgs[i] == "--sleep-scale" && i + 1 < userArgs.Length) double.TryParse(userArgs[i + 1], out sleepScale);
|
||||
if (userArgs[i] == "--speed" && i + 1 < userArgs.Length) double.TryParse(userArgs[i + 1], out speed);
|
||||
if (userArgs[i] == "--transition-click-ms" && i + 1 < userArgs.Length) long.TryParse(userArgs[i + 1], out transitionClickMs);
|
||||
if (userArgs[i] == "--hold-message-skip") holdMessageSkip = true;
|
||||
if (userArgs[i] == "--trace-histogram" && i + 1 < userArgs.Length) histFile = userArgs[i + 1];
|
||||
if (userArgs[i] == "--page-map" && i + 1 < userArgs.Length) pageMapPath = userArgs[i + 1];
|
||||
if (userArgs[i] == "--locator-hud") _locatorHudVisible = true;
|
||||
@@ -457,6 +459,8 @@ public partial class Main : Godot.Control
|
||||
if (!_selftest && scene.Equals("SC0000", System.StringComparison.OrdinalIgnoreCase))
|
||||
_vm.ExternalGlobals[0x6242d] = 4;
|
||||
foreach (var (addr, val) in seeds) _vm.Globals[addr] = val; // --seed overrides boot state
|
||||
if (holdMessageSkip)
|
||||
_vm.UpdateKeyboardVirtualKeyState(0x11, true); // Ctrl; bindings resolve it to logical action 6.
|
||||
_vmTask = Task.Run(() =>
|
||||
{
|
||||
try { _vm.Run(); }
|
||||
|
||||
Reference in New Issue
Block a user