Extract VM timing opcode handler
This commit is contained in:
@@ -198,6 +198,8 @@ and refresh helpers remain in the VM coordinator because live text and input pat
|
|||||||
`engine/Age.Engine/Vm/VirtualMachine.Input.cs` owns blocking ADV waits, hotspot registration/arming, cursor
|
`engine/Age.Engine/Vm/VirtualMachine.Input.cs` owns blocking ADV waits, hotspot registration/arming, cursor
|
||||||
resources and virtual position, raw mouse/joystick callback registration and dispatch, action polling, and
|
resources and virtual position, raw mouse/joystick callback registration and dispatch, action polling, and
|
||||||
physical-input mapping; public host-thread input entry points and shared synchronization remain in the coordinator.
|
physical-input mapping; public host-thread input entry points and shared synchronization remain in the coordinator.
|
||||||
|
`engine/Age.Engine/Vm/VirtualMachine.Timing.cs` owns the monotonic-time query, host sleep, relative timed-callback
|
||||||
|
schedule construction, deadline/catch-up selection, and callback resumption opcode handler.
|
||||||
|
|
||||||
The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map
|
The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map
|
||||||
runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports
|
runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports
|
||||||
|
|||||||
@@ -688,6 +688,12 @@ do not mix mechanical moves with semantic changes.
|
|||||||
host-thread input entry points, shared synchronization, and callback service helpers remain in the coordinator.
|
host-thread input entry points, shared synchronization, and callback service helpers remain in the coordinator.
|
||||||
Runtime validation remains green.
|
Runtime validation remains green.
|
||||||
|
|
||||||
|
The eleventh bounded `VirtualMachine.Step` extraction moved the monotonic-time query, host sleep, relative
|
||||||
|
timed-callback schedule construction, deadline/catch-up selection, and callback resumption into
|
||||||
|
`engine/Age.Engine/Vm/VirtualMachine.Timing.cs`. The top-level dispatcher retains all labels and aliases at
|
||||||
|
their existing positions and routes the separated groups through guarded `StepTiming`; animation-frame
|
||||||
|
sampling remains with `StepAnimation`. Runtime validation remains green.
|
||||||
|
|
||||||
**Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where
|
**Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where
|
||||||
deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
|
deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
|
||||||
each domain move.
|
each domain move.
|
||||||
@@ -1059,10 +1065,9 @@ layer's rendering diverges from ADV; save layout.
|
|||||||
## 8. Immediate next step
|
## 8. Immediate next step
|
||||||
Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed
|
Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed
|
||||||
by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState`
|
by the tracked launcher and layered validation driver. With the planned `Main`, `GodotAdvHost`, and `GfxState`
|
||||||
domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text, and
|
domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text,
|
||||||
text-history `VirtualMachine.Step` families routed through domain handlers, with ADV skip/auto-message services
|
text-history, ADV-service, input, and timing `VirtualMachine.Step` families routed through domain handlers,
|
||||||
and interactive input/hotspot/cursor callbacks now isolated as well, extract the sleep/timed-callback sequencing
|
extract numbered-save/shared-profile persistence dispatch next without replacing the proven dispatcher or
|
||||||
opcode family next without replacing the proven dispatcher or changing public types, commands, and generated
|
changing public types, commands, and generated output.
|
||||||
output.
|
|
||||||
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
|
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
|
||||||
not replace Phase B gameplay validation or the open cross-platform gates.
|
not replace Phase B gameplay validation or the open cross-platform gates.
|
||||||
|
|||||||
76
engine/Age.Engine/Vm/VirtualMachine.Timing.cs
Normal file
76
engine/Age.Engine/Vm/VirtualMachine.Timing.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using Age.Engine.Model;
|
||||||
|
|
||||||
|
namespace Age.Engine.Vm;
|
||||||
|
|
||||||
|
public sealed partial class VirtualMachine
|
||||||
|
{
|
||||||
|
private int StepTiming(string label, IReadOnlyList<Operand> a, int pc)
|
||||||
|
{
|
||||||
|
switch (label)
|
||||||
|
{
|
||||||
|
case "get-monotonic-time-ms":
|
||||||
|
Write(a[0], unchecked((int)_host.InputClockMilliseconds)); return pc + 1;
|
||||||
|
case "sleep": // 0xc8 (duration) — pause the host duration ms; headless hosts no-op (parity). Frame pacing.
|
||||||
|
_host.Sleep(Read(a[0])); return pc + 1;
|
||||||
|
case "u00425960":
|
||||||
|
case "begin-timed-callback-sequence": // 0xd3: clear the frame-local relative schedule
|
||||||
|
_cur.TimedCallbacks.Clear();
|
||||||
|
_cur.TimedCallbackCursor = 0;
|
||||||
|
_cur.TimedCallbackStartedAtMs = null;
|
||||||
|
_cur.TimedCallbackAbortOffset = -1;
|
||||||
|
return pc + 1;
|
||||||
|
case "u004266F0":
|
||||||
|
case "append-relative-timed-callbacks": // 0xd4: (interval ms, count, on-time PC, catch-up PC)
|
||||||
|
{
|
||||||
|
long intervalMs = Read(a[0]);
|
||||||
|
int count = System.Math.Max(0, unchecked((int)Read(a[1])));
|
||||||
|
int primaryOffset = unchecked((int)Read(a[2]));
|
||||||
|
int catchUpOffset = unchecked((int)Read(a[3]));
|
||||||
|
long deadlineMs = _cur.TimedCallbacks.Count == 0 ? 0 : _cur.TimedCallbacks[^1].DeadlineMs;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
deadlineMs += intervalMs;
|
||||||
|
_cur.TimedCallbacks.Add(new ExecFrame.TimedCallback(
|
||||||
|
deadlineMs, primaryOffset, catchUpOffset));
|
||||||
|
}
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
case "u004262C0":
|
||||||
|
case "run-timed-callback-sequence": // 0xd5: dispatch each scheduled local callback, resuming here after ret
|
||||||
|
{
|
||||||
|
if (_cur.TimedCallbackStartedAtMs == null)
|
||||||
|
{
|
||||||
|
_cur.TimedCallbackStartedAtMs = _host.InputClockMilliseconds;
|
||||||
|
_cur.TimedCallbackAbortOffset = unchecked((int)Read(a[0]));
|
||||||
|
}
|
||||||
|
// Native op 0xd5 stops at last_index rather than count. The final entry is a
|
||||||
|
// look-ahead sentinel: it supplies the next deadline for the preceding event but
|
||||||
|
// is not itself dispatched.
|
||||||
|
if (_cur.TimedCallbackCursor >= _cur.TimedCallbacks.Count - 1)
|
||||||
|
{
|
||||||
|
_cur.TimedCallbackStartedAtMs = null;
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var callback = _cur.TimedCallbacks[_cur.TimedCallbackCursor];
|
||||||
|
long elapsedMs = _host.InputClockMilliseconds - _cur.TimedCallbackStartedAtMs.Value;
|
||||||
|
if (elapsedMs < callback.DeadlineMs)
|
||||||
|
{
|
||||||
|
_host.WaitForTimedCallbackDeadline(callback.DeadlineMs - elapsedMs);
|
||||||
|
elapsedMs = _host.InputClockMilliseconds - _cur.TimedCallbackStartedAtMs.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fellBehind = _cur.TimedCallbackCursor + 1 < _cur.TimedCallbacks.Count
|
||||||
|
&& _cur.TimedCallbacks[_cur.TimedCallbackCursor + 1].DeadlineMs < elapsedMs;
|
||||||
|
int targetOffset = fellBehind ? callback.CatchUpOffset : callback.PrimaryOffset;
|
||||||
|
_cur.TimedCallbackCursor++;
|
||||||
|
if (targetOffset < 0 || !_cur.Script.IndexByOffset.TryGetValue(targetOffset, out int target))
|
||||||
|
return pc;
|
||||||
|
_cur.CallStack.Add(pc);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Non-timing opcode routed to timing handler: {label}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1247,7 +1247,7 @@ public sealed partial class VirtualMachine
|
|||||||
return pc + 1;
|
return pc + 1;
|
||||||
}
|
}
|
||||||
case "get-monotonic-time-ms":
|
case "get-monotonic-time-ms":
|
||||||
Write(a[0], unchecked((int)_host.InputClockMilliseconds)); return pc + 1;
|
return StepTiming(label, a, pc);
|
||||||
case "lt": Write(a[0], Read(a[1]) < Read(a[2]) ? 1 : 0); return pc + 1;
|
case "lt": Write(a[0], Read(a[1]) < Read(a[2]) ? 1 : 0); return pc + 1;
|
||||||
case "lte": Write(a[0], Read(a[1]) <= Read(a[2]) ? 1 : 0); return pc + 1;
|
case "lte": Write(a[0], Read(a[1]) <= Read(a[2]) ? 1 : 0); return pc + 1;
|
||||||
case "gr": Write(a[0], Read(a[1]) > Read(a[2]) ? 1 : 0); return pc + 1;
|
case "gr": Write(a[0], Read(a[1]) > Read(a[2]) ? 1 : 0); return pc + 1;
|
||||||
@@ -2019,65 +2019,14 @@ public sealed partial class VirtualMachine
|
|||||||
case "u0041E5E0":
|
case "u0041E5E0":
|
||||||
case "map-keyboard-scancode": // 0x10c: logical action <- DIK translated through native VK table
|
case "map-keyboard-scancode": // 0x10c: logical action <- DIK translated through native VK table
|
||||||
return StepInput(label, a, pc);
|
return StepInput(label, a, pc);
|
||||||
case "sleep": // 0xc8 (duration) — pause the host duration ms; headless hosts no-op (parity). Frame pacing.
|
case "sleep":
|
||||||
_host.Sleep(Read(a[0])); return pc + 1;
|
|
||||||
case "u00425960":
|
case "u00425960":
|
||||||
case "begin-timed-callback-sequence": // 0xd3: clear the frame-local relative schedule
|
case "begin-timed-callback-sequence": // 0xd3: clear the frame-local relative schedule
|
||||||
_cur.TimedCallbacks.Clear();
|
|
||||||
_cur.TimedCallbackCursor = 0;
|
|
||||||
_cur.TimedCallbackStartedAtMs = null;
|
|
||||||
_cur.TimedCallbackAbortOffset = -1;
|
|
||||||
return pc + 1;
|
|
||||||
case "u004266F0":
|
case "u004266F0":
|
||||||
case "append-relative-timed-callbacks": // 0xd4: (interval ms, count, on-time PC, catch-up PC)
|
case "append-relative-timed-callbacks": // 0xd4: (interval ms, count, on-time PC, catch-up PC)
|
||||||
{
|
|
||||||
long intervalMs = Read(a[0]);
|
|
||||||
int count = System.Math.Max(0, unchecked((int)Read(a[1])));
|
|
||||||
int primaryOffset = unchecked((int)Read(a[2]));
|
|
||||||
int catchUpOffset = unchecked((int)Read(a[3]));
|
|
||||||
long deadlineMs = _cur.TimedCallbacks.Count == 0 ? 0 : _cur.TimedCallbacks[^1].DeadlineMs;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
deadlineMs += intervalMs;
|
|
||||||
_cur.TimedCallbacks.Add(new ExecFrame.TimedCallback(
|
|
||||||
deadlineMs, primaryOffset, catchUpOffset));
|
|
||||||
}
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "u004262C0":
|
case "u004262C0":
|
||||||
case "run-timed-callback-sequence": // 0xd5: dispatch each scheduled local callback, resuming here after ret
|
case "run-timed-callback-sequence": // 0xd5: dispatch each scheduled local callback, resuming here after ret
|
||||||
{
|
return StepTiming(label, a, pc);
|
||||||
if (_cur.TimedCallbackStartedAtMs == null)
|
|
||||||
{
|
|
||||||
_cur.TimedCallbackStartedAtMs = _host.InputClockMilliseconds;
|
|
||||||
_cur.TimedCallbackAbortOffset = unchecked((int)Read(a[0]));
|
|
||||||
}
|
|
||||||
// Native op 0xd5 stops at last_index rather than count. The final entry is a
|
|
||||||
// look-ahead sentinel: it supplies the next deadline for the preceding event but
|
|
||||||
// is not itself dispatched.
|
|
||||||
if (_cur.TimedCallbackCursor >= _cur.TimedCallbacks.Count - 1)
|
|
||||||
{
|
|
||||||
_cur.TimedCallbackStartedAtMs = null;
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var callback = _cur.TimedCallbacks[_cur.TimedCallbackCursor];
|
|
||||||
long elapsedMs = _host.InputClockMilliseconds - _cur.TimedCallbackStartedAtMs.Value;
|
|
||||||
if (elapsedMs < callback.DeadlineMs)
|
|
||||||
{
|
|
||||||
_host.WaitForTimedCallbackDeadline(callback.DeadlineMs - elapsedMs);
|
|
||||||
elapsedMs = _host.InputClockMilliseconds - _cur.TimedCallbackStartedAtMs.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fellBehind = _cur.TimedCallbackCursor + 1 < _cur.TimedCallbacks.Count
|
|
||||||
&& _cur.TimedCallbacks[_cur.TimedCallbackCursor + 1].DeadlineMs < elapsedMs;
|
|
||||||
int targetOffset = fellBehind ? callback.CatchUpOffset : callback.PrimaryOffset;
|
|
||||||
_cur.TimedCallbackCursor++;
|
|
||||||
if (targetOffset < 0 || !_cur.Script.IndexByOffset.TryGetValue(targetOffset, out int target))
|
|
||||||
return pc;
|
|
||||||
_cur.CallStack.Add(pc);
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
case "u0041B290":
|
case "u0041B290":
|
||||||
case "set-message-skip": // 0x88: persistent all-message fast-forward service state
|
case "set-message-skip": // 0x88: persistent all-message fast-forward service state
|
||||||
case "u00414E50": // 0x19a: persistent state used by the SO001 active overlay
|
case "u00414E50": // 0x19a: persistent state used by the SO001 active overlay
|
||||||
|
|||||||
Reference in New Issue
Block a user