Complete native numbered-save round trips

This commit is contained in:
gamer147
2026-07-24 23:25:54 -04:00
parent 4db7e412bd
commit 0a4e200876
17 changed files with 907 additions and 89 deletions

View File

@@ -1,4 +1,5 @@
using Age.Engine.Model;
using Age.Engine.Persistence;
namespace Age.Engine.Vm;
/// <summary>One script activation: the running script, its instruction cursor, its local slots,
@@ -10,6 +11,10 @@ internal sealed class ExecFrame
public readonly Script Script;
public int Pc; // entry instruction index
// While a restored descendant is running, this activation is parked at the synthetic op-0xae
// rendezvous rather than its native T1/T2/T3 coordinate. Retain the serialized coordinates until
// the descendant returns so an intervening save can reproduce the native parent frame.
public NativeSavedScriptFrame? RestoredSaveFrame;
public int ReadMessageOffset = -1; // latest op-0x71 code DWORD coordinate
public readonly Frame Locals = new();
public readonly List<int> CallStack = new(); // intra-script `call` (op 0x8f) returns

View File

@@ -595,12 +595,17 @@ public sealed class VirtualMachine
Script root = _s;
int rootEntry = root.IndexByOffset.TryGetValue(entryOffset, out var idx) ? idx : 0;
FrameCause cause = FrameCause.TopScene;
NativeSavedScriptFrame? restoredRootFrame = null;
while (true)
{
FrameOutcome outcome;
try
{
outcome = RunFrame(new ExecFrame(root, rootEntry), cause);
ExecFrame rootFrame = restoredRootFrame == null
? new ExecFrame(root, rootEntry)
: CreateRestoredFrame(root, restoredRootFrame);
restoredRootFrame = null;
outcome = RunFrame(rootFrame, cause);
}
catch (NumberedRestoreRequestedException)
{
@@ -618,8 +623,9 @@ public sealed class VirtualMachine
}
}
root = ResolveSavedScript(_loadedNumberedState.Frames[0]);
rootEntry = FindRestoreRendezvous(root);
rootEntry = 0;
_restoreFrameIndex = 0;
restoredRootFrame = _loadedNumberedState.Frames[0];
cause = FrameCause.SaveRestore;
continue;
}
@@ -800,19 +806,26 @@ public sealed class VirtualMachine
for (int i = 0; i <= cutoff; i++)
{
ExecFrame frame = active[i];
int[] returns = frame.CallStack
.Where(returnPc => (uint)returnPc < (uint)frame.Script.Instructions.Count)
.Select(returnPc =>
{
int returnOffset = frame.Script.Instructions[returnPc].Offset;
return FindTableIndex(frame.Script.LocalCallOffsets, returnOffset - 3);
})
.Where(index => index >= 0)
.ToArray();
int resumeIndex = CurrentReadMessageIndex(frame);
int callTargetIndex = i == cutoff || (uint)frame.Pc >= (uint)frame.Script.Instructions.Count
NativeSavedScriptFrame? restored = frame.RestoredSaveFrame;
int[] returns = restored?.ReturnIndices.ToArray()
?? frame.CallStack
.Where(returnPc => (uint)returnPc < (uint)frame.Script.Instructions.Count)
.Select(returnPc =>
{
int returnOffset = frame.Script.Instructions[returnPc].Offset;
return FindTableIndex(frame.Script.LocalCallOffsets, returnOffset - 3);
})
.Where(index => index >= 0)
.ToArray();
int resumeIndex = restored?.ResumeIndex ?? CurrentReadMessageIndex(frame);
int callTargetIndex = i == cutoff
? -1
: FindTableIndex(frame.Script.ScriptCallOffsets, frame.Script.Instructions[frame.Pc].Offset);
: restored?.CallTargetIndex
?? ((uint)frame.Pc >= (uint)frame.Script.Instructions.Count
? -1
: FindTableIndex(
frame.Script.ScriptCallOffsets,
frame.Script.Instructions[frame.Pc].Offset));
frames[i] = new NativeSavedScriptFrame(
i - 1, frame.Script.PackedId, returns, resumeIndex, callTargetIndex);
}
@@ -941,6 +954,29 @@ public sealed class VirtualMachine
$"Saved script {script.Name} has no opcode 0xae restore rendezvous.");
}
private static ExecFrame CreateRestoredFrame(Script script, NativeSavedScriptFrame saved)
{
// Native creates each saved script context at its ordinary entrypoint. The script runs its
// local-array/constants/resource prologue and rendezvouses at 0xae itself; jumping directly
// to 0xae leaves those frame-local tables zeroed (FIELD then collapses its map zoom to 0%).
_ = FindRestoreRendezvous(script);
int entry = script.IndexByOffset.TryGetValue(0, out int index) ? index : 0;
var frame = new ExecFrame(script, entry)
{
RestoredSaveFrame = saved,
};
if ((uint)saved.ResumeIndex < (uint)script.ReadMessageOffsets.Count)
frame.ReadMessageOffset = script.ReadMessageOffsets[saved.ResumeIndex];
foreach (int returnIndex in saved.ReturnIndices)
{
if ((uint)returnIndex >= (uint)script.LocalCallOffsets.Count) continue;
int returnOffset = checked(script.LocalCallOffsets[returnIndex] + 3);
if (script.IndexByOffset.TryGetValue(returnOffset, out int returnPc))
frame.CallStack.Add(returnPc);
}
return frame;
}
private static int ResolveTableOffset(
Script script, IReadOnlyList<int> table, int index, int fallback)
{
@@ -1039,6 +1075,9 @@ public sealed class VirtualMachine
{
case "script-entry":
Gfx.ClearSurfaceReloadPolicies(); return pc + 1;
case "set-surface-persistence-flags": // 0x258 (slot)(flags): bit 0 = numbered-load reload
Gfx.SetSurfaceReloadOnRestore(unchecked((int)Read(a[0])), (Read(a[1]) & 1) != 0);
return pc + 1;
case "add": Write(a[0], Read(a[1]) + Read(a[2])); return pc + 1;
case "sub": Write(a[0], Read(a[1]) - Read(a[2])); return pc + 1;
case "mul": Write(a[0], Read(a[1]) * Read(a[2])); return pc + 1;
@@ -1139,6 +1178,11 @@ public sealed class VirtualMachine
bool terminal = _restoreFrameIndex == _loadedNumberedState.Frames.Count - 1;
if (terminal)
{
// Native restores the saved top frame as the numbered-save boundary selected by
// opcode 0x1ad. Re-establish that identity so a subsequent save excludes transient
// SAVE/menu helper frames instead of serializing the currently open modal stack.
lock (_debugControlLock) _saveResumeFrame = _cur;
_cur.RestoredSaveFrame = null;
_loadedNumberedState = null;
_restoreFrameIndex = -1;
return ResolveTableOffset(_cur.Script, _cur.Script.ReadMessageOffsets, saved.ResumeIndex, pc + 1);
@@ -1149,11 +1193,14 @@ public sealed class VirtualMachine
Script child = ResolveSavedScript(childSaved);
_restoreFrameIndex = parentIndex + 1;
FrameOutcome childOutcome = RunFrame(
new ExecFrame(child, FindRestoreRendezvous(child)), FrameCause.SaveRestore,
CreateRestoredFrame(child, childSaved), FrameCause.SaveRestore,
childSaved.ScriptId);
_restoreFrameIndex = parentIndex;
if (childOutcome is FrameOutcome.Halted or FrameOutcome.ExitRequested)
return HALT;
if (childOutcome == FrameOutcome.Halted) return HALT;
if (childOutcome == FrameOutcome.RootReload) return ROOT_RELOAD;
if (childOutcome == FrameOutcome.ExitRequested)
throw new ProcessExitRequestedException();
_cur.RestoredSaveFrame = null;
return ResolveTableOffset(
_cur.Script, _cur.Script.ScriptCallOffsets, saved.CallTargetIndex, pc) + 1;
}