Extract VM script lifecycle handler
This commit is contained in:
@@ -208,7 +208,9 @@ inline arrays, rectangle search and stable index sorting, bounded integer queues
|
|||||||
and native-style random-modulo dispatch; shared storage/address helpers remain in the coordinator.
|
and native-style random-modulo dispatch; shared storage/address helpers remain in the coordinator.
|
||||||
`engine/Age.Engine/Vm/VirtualMachine.ControlFlow.cs` owns local jumps/calls/returns, value-switch construction,
|
`engine/Age.Engine/Vm/VirtualMachine.ControlFlow.cs` owns local jumps/calls/returns, value-switch construction,
|
||||||
ADV coroutine handler save/yield/resume, and bounded labeled-yield dispatch; process/root exit and cross-script
|
ADV coroutine handler save/yield/resume, and bounded labeled-yield dispatch; process/root exit and cross-script
|
||||||
lifecycle remain in `VirtualMachine.cs`.
|
lifecycle are routed separately. `engine/Age.Engine/Vm/VirtualMachine.ScriptLifecycle.cs` owns process/frame/root
|
||||||
|
exit, ordinary cross-script calls, mounted append autoruns, and preloaded script-slot load/call dispatch; frame
|
||||||
|
execution, script-provider access, and shared lifecycle state remain in `VirtualMachine.cs`.
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -714,6 +714,13 @@ do not mix mechanical moves with semantic changes.
|
|||||||
available for labeled-yield opcode/offset diagnostics. Process/root exit and cross-script lifecycle remain in
|
available for labeled-yield opcode/offset diagnostics. Process/root exit and cross-script lifecycle remain in
|
||||||
the coordinator. Runtime validation remains green.
|
the coordinator. Runtime validation remains green.
|
||||||
|
|
||||||
|
The fifteenth bounded `VirtualMachine.Step` extraction moved process/frame/root exit, ordinary cross-script
|
||||||
|
calls, mounted append autoruns, and preloaded script-slot load/call dispatch into
|
||||||
|
`engine/Age.Engine/Vm/VirtualMachine.ScriptLifecycle.cs`. The top-level dispatcher retains all labels and
|
||||||
|
aliases at their existing positions and routes them through guarded `StepScriptLifecycle`; frame execution,
|
||||||
|
provider access, call-depth enforcement, and shared lifecycle state remain in the coordinator. 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.
|
||||||
@@ -1087,8 +1094,8 @@ Continue step 2 of the **codebase consolidation** maintenance slice: behavior-ne
|
|||||||
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,
|
domains isolated and the audio, movie, surface/texture, retained-object, animation, presentation, ADV-text,
|
||||||
text-history, ADV-service, input, timing, persistence, and memory/collection `VirtualMachine.Step` families routed
|
text-history, ADV-service, input, timing, persistence, and memory/collection `VirtualMachine.Step` families routed
|
||||||
through domain handlers, with control-flow/coroutine dispatch now isolated as well, extract process/root-exit and
|
through domain handlers, with control-flow/coroutine and process/root-exit/cross-script lifecycle dispatch now
|
||||||
cross-script lifecycle dispatch next without replacing the proven dispatcher or changing public types, commands,
|
isolated as well, extract scalar arithmetic and string-value dispatch next without replacing the proven
|
||||||
and generated output.
|
dispatcher or changing public types, commands, and generated 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.
|
||||||
|
|||||||
142
engine/Age.Engine/Vm/VirtualMachine.ScriptLifecycle.cs
Normal file
142
engine/Age.Engine/Vm/VirtualMachine.ScriptLifecycle.cs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
using Age.Engine.Diagnostics;
|
||||||
|
using Age.Engine.Model;
|
||||||
|
|
||||||
|
namespace Age.Engine.Vm;
|
||||||
|
|
||||||
|
public sealed partial class VirtualMachine
|
||||||
|
{
|
||||||
|
private int StepScriptLifecycle(string label, IReadOnlyList<Operand> a, int pc)
|
||||||
|
{
|
||||||
|
switch (label)
|
||||||
|
{
|
||||||
|
case "throw-exit-request":
|
||||||
|
if (_o.IgnoreExitRequests) return pc + 1;
|
||||||
|
// Native op 0x1 throws Command_Exit_Exception through callbacks and nested script
|
||||||
|
// frames. The outer engine loop catches it and exits without advancing frame_pc.
|
||||||
|
throw new ProcessExitRequestedException();
|
||||||
|
case "exit": return FRAME_RETURN;
|
||||||
|
case "exit-script":
|
||||||
|
// Native op 0x9 clears the process-initial flag, disposes every active script frame,
|
||||||
|
// resets scene-owned services, and loads raw script resource 0 as the new root.
|
||||||
|
_initialRootRun = false;
|
||||||
|
return ROOT_RELOAD;
|
||||||
|
case "call-script":
|
||||||
|
{
|
||||||
|
long id = a.Count > 0 ? Read(a[0]) : 0;
|
||||||
|
CallScriptDispatches++;
|
||||||
|
if (_provider == null)
|
||||||
|
{
|
||||||
|
_sink.Emit(TraceEvent.CallScript(id, null)); // stub mode: notify only, no child pushed
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
if (_depth >= _o.CallDepthCap) { HaltReason ??= "call-depth-exceeded"; return HALT; }
|
||||||
|
var child = _provider.GetById(id);
|
||||||
|
_sink.Emit(TraceEvent.CallScript(id, child?.Name));
|
||||||
|
if (child == null) { HaltReason ??= $"callscript-unresolved:0x{id:x}"; return HALT; }
|
||||||
|
var entry = child.IndexByOffset.TryGetValue(0, out var ci) ? ci : 0;
|
||||||
|
var outcome = RunFrame(new ExecFrame(child, entry), FrameCause.CallScript, id);
|
||||||
|
if (outcome == FrameOutcome.Halted) return HALT; // propagate whole-VM halt up
|
||||||
|
if (outcome == FrameOutcome.RootReload) return ROOT_RELOAD; // discard every caller frame
|
||||||
|
if (outcome == FrameOutcome.ExitRequested) throw new ProcessExitRequestedException();
|
||||||
|
return pc + 1; // Returned / RanOff: resume caller
|
||||||
|
}
|
||||||
|
case "u00415FB0":
|
||||||
|
case "run-mounted-append-autoruns": // 0x143: selector slots 1..255, packed record zero
|
||||||
|
{
|
||||||
|
if (_provider == null) return pc + 1;
|
||||||
|
|
||||||
|
// Native first scans every mounted selector into its launch queue, then dispatches
|
||||||
|
// those packed scripts serially. Snapshot before running any child so script-side
|
||||||
|
// effects cannot change the current batch.
|
||||||
|
int[] selectors = _provider.MountedAppendSelectors
|
||||||
|
.Where(selector => selector is > 0 and <= 0xff)
|
||||||
|
.Distinct()
|
||||||
|
.Order()
|
||||||
|
.ToArray();
|
||||||
|
foreach (int selector in selectors)
|
||||||
|
{
|
||||||
|
if (_depth >= _o.CallDepthCap)
|
||||||
|
{
|
||||||
|
HaltReason ??= "call-depth-exceeded";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
|
||||||
|
long id = (long)selector << 24;
|
||||||
|
CallScriptDispatches++;
|
||||||
|
var child = _provider.GetById(id);
|
||||||
|
_sink.Emit(TraceEvent.CallScript(id, child?.Name));
|
||||||
|
if (child == null)
|
||||||
|
{
|
||||||
|
HaltReason ??= $"append-autorun-unresolved:0x{id:x}";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int entry = child.IndexByOffset.TryGetValue(0, out int childEntry) ? childEntry : 0;
|
||||||
|
var outcome = RunFrame(new ExecFrame(child, entry), FrameCause.CallScript, id);
|
||||||
|
if (outcome == FrameOutcome.Halted) return HALT;
|
||||||
|
if (outcome == FrameOutcome.RootReload) return ROOT_RELOAD;
|
||||||
|
if (outcome == FrameOutcome.ExitRequested) throw new ProcessExitRequestedException();
|
||||||
|
}
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
case "u00417E80":
|
||||||
|
case "preload-script-slot": // 0x06 (script_id, frame_slot), valid slots 0..39
|
||||||
|
{
|
||||||
|
long id = Read(a[0]);
|
||||||
|
int slot = unchecked((int)Read(a[1]));
|
||||||
|
if ((uint)slot >= 40)
|
||||||
|
{
|
||||||
|
HaltReason ??= $"preloaded-script-slot-out-of-range:{slot}";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
if (_provider == null)
|
||||||
|
{
|
||||||
|
HaltReason ??= $"preloaded-script-provider-unavailable:0x{id:x}";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
var script = _provider.GetById(id);
|
||||||
|
if (script == null)
|
||||||
|
{
|
||||||
|
HaltReason ??= $"preloaded-script-unresolved:0x{id:x}";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
int entry = script.IndexByOffset.TryGetValue(0, out int loadedEntry) ? loadedEntry : 0;
|
||||||
|
_preloadedScriptSlots[slot] = new PreloadedScriptSlot(id, new ExecFrame(script, entry));
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
case "u00417FC0":
|
||||||
|
case "call-preloaded-script-slot": // 0x08 (frame_slot)
|
||||||
|
{
|
||||||
|
int slot = unchecked((int)Read(a[0]));
|
||||||
|
if ((uint)slot >= 40)
|
||||||
|
{
|
||||||
|
HaltReason ??= $"preloaded-script-slot-out-of-range:{slot}";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
if (!_preloadedScriptSlots.TryGetValue(slot, out var loaded))
|
||||||
|
{
|
||||||
|
HaltReason ??= $"preloaded-script-slot-empty:{slot}";
|
||||||
|
return HALT;
|
||||||
|
}
|
||||||
|
if (_depth >= _o.CallDepthCap) { HaltReason ??= "call-depth-exceeded"; return HALT; }
|
||||||
|
|
||||||
|
CallScriptDispatches++;
|
||||||
|
_sink.Emit(TraceEvent.CallScript(loaded.ScriptId, loaded.Frame.Script.Name));
|
||||||
|
// PC restarts at codebase while the native slot's local banks remain allocated.
|
||||||
|
// Balanced local calls leave this empty; clearing the port-only emission guard makes
|
||||||
|
// each invocation an independent diagnostic activation.
|
||||||
|
loaded.Frame.CallStack.Clear();
|
||||||
|
loaded.Frame.EmitSeen.Clear();
|
||||||
|
loaded.Frame.Pc = loaded.Frame.Script.IndexByOffset.TryGetValue(0, out int loadedEntry)
|
||||||
|
? loadedEntry : 0;
|
||||||
|
var outcome = RunFrame(loaded.Frame, FrameCause.CallScript, loaded.ScriptId);
|
||||||
|
if (outcome == FrameOutcome.Halted) return HALT;
|
||||||
|
if (outcome == FrameOutcome.RootReload) return ROOT_RELOAD;
|
||||||
|
if (outcome == FrameOutcome.ExitRequested) throw new ProcessExitRequestedException();
|
||||||
|
return pc + 1;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Non-script-lifecycle opcode routed to script-lifecycle handler: {label}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1361,131 +1361,16 @@ public sealed partial class VirtualMachine
|
|||||||
case "coroutine-label-yield": // 0x140: bounded host model for LABEL/J only
|
case "coroutine-label-yield": // 0x140: bounded host model for LABEL/J only
|
||||||
return StepControlFlow(label, ins, pc);
|
return StepControlFlow(label, ins, pc);
|
||||||
case "throw-exit-request":
|
case "throw-exit-request":
|
||||||
if (_o.IgnoreExitRequests) return pc + 1;
|
case "exit":
|
||||||
// Native op 0x1 throws Command_Exit_Exception through callbacks and nested script
|
|
||||||
// frames. The outer engine loop catches it and exits without advancing frame_pc.
|
|
||||||
throw new ProcessExitRequestedException();
|
|
||||||
case "exit": return FRAME_RETURN;
|
|
||||||
case "exit-script":
|
case "exit-script":
|
||||||
// Native op 0x9 clears the process-initial flag, disposes every active script frame,
|
|
||||||
// resets scene-owned services, and loads raw script resource 0 as the new root.
|
|
||||||
_initialRootRun = false;
|
|
||||||
return ROOT_RELOAD;
|
|
||||||
case "call-script":
|
case "call-script":
|
||||||
{
|
|
||||||
long id = a.Count > 0 ? Read(a[0]) : 0;
|
|
||||||
CallScriptDispatches++;
|
|
||||||
if (_provider == null)
|
|
||||||
{
|
|
||||||
_sink.Emit(TraceEvent.CallScript(id, null)); // stub mode: notify only, no child pushed
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
if (_depth >= _o.CallDepthCap) { HaltReason ??= "call-depth-exceeded"; return HALT; }
|
|
||||||
var child = _provider.GetById(id);
|
|
||||||
_sink.Emit(TraceEvent.CallScript(id, child?.Name));
|
|
||||||
if (child == null) { HaltReason ??= $"callscript-unresolved:0x{id:x}"; return HALT; }
|
|
||||||
var entry = child.IndexByOffset.TryGetValue(0, out var ci) ? ci : 0;
|
|
||||||
var outcome = RunFrame(new ExecFrame(child, entry), FrameCause.CallScript, id);
|
|
||||||
if (outcome == FrameOutcome.Halted) return HALT; // propagate whole-VM halt up
|
|
||||||
if (outcome == FrameOutcome.RootReload) return ROOT_RELOAD; // discard every caller frame
|
|
||||||
if (outcome == FrameOutcome.ExitRequested) throw new ProcessExitRequestedException();
|
|
||||||
return pc + 1; // Returned / RanOff: resume caller
|
|
||||||
}
|
|
||||||
case "u00415FB0":
|
case "u00415FB0":
|
||||||
case "run-mounted-append-autoruns": // 0x143: selector slots 1..255, packed record zero
|
case "run-mounted-append-autoruns": // 0x143: selector slots 1..255, packed record zero
|
||||||
{
|
|
||||||
if (_provider == null) return pc + 1;
|
|
||||||
|
|
||||||
// Native first scans every mounted selector into its launch queue, then dispatches
|
|
||||||
// those packed scripts serially. Snapshot before running any child so script-side
|
|
||||||
// effects cannot change the current batch.
|
|
||||||
int[] selectors = _provider.MountedAppendSelectors
|
|
||||||
.Where(selector => selector is > 0 and <= 0xff)
|
|
||||||
.Distinct()
|
|
||||||
.Order()
|
|
||||||
.ToArray();
|
|
||||||
foreach (int selector in selectors)
|
|
||||||
{
|
|
||||||
if (_depth >= _o.CallDepthCap)
|
|
||||||
{
|
|
||||||
HaltReason ??= "call-depth-exceeded";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
|
|
||||||
long id = (long)selector << 24;
|
|
||||||
CallScriptDispatches++;
|
|
||||||
var child = _provider.GetById(id);
|
|
||||||
_sink.Emit(TraceEvent.CallScript(id, child?.Name));
|
|
||||||
if (child == null)
|
|
||||||
{
|
|
||||||
HaltReason ??= $"append-autorun-unresolved:0x{id:x}";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
|
|
||||||
int entry = child.IndexByOffset.TryGetValue(0, out int childEntry) ? childEntry : 0;
|
|
||||||
var outcome = RunFrame(new ExecFrame(child, entry), FrameCause.CallScript, id);
|
|
||||||
if (outcome == FrameOutcome.Halted) return HALT;
|
|
||||||
if (outcome == FrameOutcome.RootReload) return ROOT_RELOAD;
|
|
||||||
if (outcome == FrameOutcome.ExitRequested) throw new ProcessExitRequestedException();
|
|
||||||
}
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "u00417E80":
|
case "u00417E80":
|
||||||
case "preload-script-slot": // 0x06 (script_id, frame_slot), valid slots 0..39
|
case "preload-script-slot": // 0x06 (script_id, frame_slot), valid slots 0..39
|
||||||
{
|
|
||||||
long id = Read(a[0]);
|
|
||||||
int slot = unchecked((int)Read(a[1]));
|
|
||||||
if ((uint)slot >= 40)
|
|
||||||
{
|
|
||||||
HaltReason ??= $"preloaded-script-slot-out-of-range:{slot}";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
if (_provider == null)
|
|
||||||
{
|
|
||||||
HaltReason ??= $"preloaded-script-provider-unavailable:0x{id:x}";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
var script = _provider.GetById(id);
|
|
||||||
if (script == null)
|
|
||||||
{
|
|
||||||
HaltReason ??= $"preloaded-script-unresolved:0x{id:x}";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
int entry = script.IndexByOffset.TryGetValue(0, out int loadedEntry) ? loadedEntry : 0;
|
|
||||||
_preloadedScriptSlots[slot] = new PreloadedScriptSlot(id, new ExecFrame(script, entry));
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "u00417FC0":
|
case "u00417FC0":
|
||||||
case "call-preloaded-script-slot": // 0x08 (frame_slot)
|
case "call-preloaded-script-slot": // 0x08 (frame_slot)
|
||||||
{
|
return StepScriptLifecycle(label, a, pc);
|
||||||
int slot = unchecked((int)Read(a[0]));
|
|
||||||
if ((uint)slot >= 40)
|
|
||||||
{
|
|
||||||
HaltReason ??= $"preloaded-script-slot-out-of-range:{slot}";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
if (!_preloadedScriptSlots.TryGetValue(slot, out var loaded))
|
|
||||||
{
|
|
||||||
HaltReason ??= $"preloaded-script-slot-empty:{slot}";
|
|
||||||
return HALT;
|
|
||||||
}
|
|
||||||
if (_depth >= _o.CallDepthCap) { HaltReason ??= "call-depth-exceeded"; return HALT; }
|
|
||||||
|
|
||||||
CallScriptDispatches++;
|
|
||||||
_sink.Emit(TraceEvent.CallScript(loaded.ScriptId, loaded.Frame.Script.Name));
|
|
||||||
// PC restarts at codebase while the native slot's local banks remain allocated.
|
|
||||||
// Balanced local calls leave this empty; clearing the port-only emission guard makes
|
|
||||||
// each invocation an independent diagnostic activation.
|
|
||||||
loaded.Frame.CallStack.Clear();
|
|
||||||
loaded.Frame.EmitSeen.Clear();
|
|
||||||
loaded.Frame.Pc = loaded.Frame.Script.IndexByOffset.TryGetValue(0, out int loadedEntry)
|
|
||||||
? loadedEntry : 0;
|
|
||||||
var outcome = RunFrame(loaded.Frame, FrameCause.CallScript, loaded.ScriptId);
|
|
||||||
if (outcome == FrameOutcome.Halted) return HALT;
|
|
||||||
if (outcome == FrameOutcome.RootReload) return ROOT_RELOAD;
|
|
||||||
if (outcome == FrameOutcome.ExitRequested) throw new ProcessExitRequestedException();
|
|
||||||
return pc + 1;
|
|
||||||
}
|
|
||||||
case "show-text":
|
case "show-text":
|
||||||
case "define-adv-text-layout":
|
case "define-adv-text-layout":
|
||||||
case "reset-adv-text-layout":
|
case "reset-adv-text-layout":
|
||||||
|
|||||||
Reference in New Issue
Block a user