The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Language::Zcode::Runtime::State - Handle saving, restoring, etc. the game state

restoring

Getter/setter: currently in the process of restoring or not?

start_machine

Start executing the Z-machine.

In the normal case (starting a new game, or restarting), this is as simple as calling the Z-machine subroutine whose address is stored in the header.

If we're restoring from a save file, it's more complicated. See "resume_execution".

z_call

Wrapper around Z-code subroutine calls. The main reason we need it is for save/restore.

In the normal case, z_call just calls the Z-code subroutine at address arg0 with the given args (arg5-argn), if any. Args 1-4 aren't used by z_call, but (hack alert!) they go into the Perl call stack, which is needed for saving Z-machine state.

Input: subroutine address to call, local variables & eval stack (arrayrefs), next PC, store variable, args to the Z-sub.

See "The call stack" for far more detail on this sub and save/restore.

save_state

Implement the @save opcode, saving the current Z-machine state (as opposed to writing a table to a file, the other use of the @save opcode)

Note that this sub also gets called at the very end of the restoring process.

Returns 0 for failed save, 1 for successful save, 2 for "just finished restoring".

build_save_stack

Create a Z-machine call stack by peeking at the Perl call stack.

When calling Z_machine subroutines, we call z_call with all the information contained in a Z stack frame. We retrieve that information from the Perl call stack and build a Z-machine call stack with it.

restore_state

Implement the @restore opcode, restoring the current Z-machine state (as opposed to reading a table from a file, the other use of the @restore opcode)

NOTES

The call stack

The Z-code call stack is much different than the Perl call stack, so it takes a bit of work to convert one to the other. Almost all of the work is done by the z_call routine which is a wrapper around Z-code subs.

Building the call stack

z_call is called with extra args (arg1-arg4), that are not technically used by z_call or the subs it calls. However, the Perl call stack stores these args, and we can later build a Z-machine (Quetzal) call stack using @DB::args (see "caller" in perlfunc). Sneaky!

Restoring

How do we restore from a save file? We need to start execution with the Z-machine in the same state it was in when we did the @save. So we need to restore local variables and the eval stack. We also need to restore the call stack, so that when we finish a sub, we jump back into the middle of the sub that called that sub (at the address right after the call).

restore_state is quite simple: it just sets the restoring flag and then die()s (i.e., reboots the Z-machine). Most of the work is done by &z_call, when it sees the restoring flag.

When we're restoring, z_call is more complicated. We need to start executing subroutines in the middle (i.e., wherever the program counter was when @save was called), with the correct local variable and eval stack values.

z_call does this by reading data from the call stack and calling the Z-code subs with special args that tell them to start executing at a given address.

Where the usual calling tree looks like this: The main Perl program calls z_code(A) z_call calls Z-code sub A ('main' sub) Z-code sub A calls z_call(B) z_call calls B Z-code sub B calls z_call(C), etc.

We instead do this: The main Perl program calls z_code(A) z_call sees that A will call B, so it calls z_call(B) z_call(B) sees that B will call C, so it calls z_call(C) ... z_call(E) calls z_call(F), exhausting the restored call stack z_call(F) stops recursing and calls Z-code sub F with special args saying to start executing at the @save command F says "stop restoring", and returns z_call(E) calls E, saying to start executing after the call to F ... z_call(B) calls B, saying to start executing after the call to C

This is complicated, but has several benefits:

  • We rebuild the Perl call stack so that if we call @save again from, say, a later point in E, we'll save the stack correctly.

  • By climbing up the stack, we can properly store C's return value in a variable in B, etc.

  • The complexity is (mostly) kept in z_call, not all over the translated Z-code.

  • Each Z-code sub ends up executing in the middle of the sub, right where it was - and in the same state as it was - when the @save happened.

The very first opcode we execute when z_call gets to the top of the stack is guaranteed to be @save. This turns the restoring flag off. By the time we return from the called Z-code sub back to z_call, even though we're within several levels of recursion, we're in normal program flow.

(Note that z_call and the Z-code subs were calling each other recursively anyway. We just change the order around when restoring, so z_call calls itself and then calls the Z-code sub.)

Frame mismatch

Another piece of complexity is a mismatch between Quetzal and Perl.

Assume sub B calls sub C, with a call like $result = z_call(C, Blocals, Bstack, Bnext_PC, Bstore_var, args);

(where Blocals means the local variables in subroutine B).

Quetzal frames actually mix values from different (Perl) subroutines, so we have frames like (Clocals, Cstack, Bnext_PC, Bstore_var).

In addition, the only way we can figure out C while restoring is to get Cnext_PC (which will be in the frame with Dlocals et al.!) and to find the sub that contains the Cnext_PC address. So we actually need to look at three different frames in order to recreate the z_call that happened in the saved program.

Starting subs in the middle

z_call is called with @args, the list of arguments to be passed to the Z-code sub. Usually, we just pass the @args to the subroutine we call. However, when we're restoring, we replace @args with the values that should be in @locv for the called sub (which we got from the frame). We ignore @args in that case. Since Z-code subs automatically set @locv to be equal to the input args, this sets @locv to have the same value as it had when machine state was saved.

Also note that Quetzal saves only the number of args in a subroutine call, not their values. So when we're restoring and recursively calling z_call as we climb the stack, we don't know which @args to pass to those z_call calls. So we just pass in dummy values. (We need to get the right number of args so that the call stack is built correctly.)

1 POD Error

The following errors were encountered while parsing the POD:

Around line 47:

=back without =over