Scheduling/suspending embedded interpreters

  softwareengineering

Let’s say that I want to embed a scripting engine inside some other program, to allow users to create custom behaviours for objects.

For example, when a particular event happens, the server would launch an embedded scripting engine and run a user-supplied response-to-event script supplied by each actor. Conceptually you can imagine it like a movie script. A new character enters the scene. Each of the actors in the scene must now respond to the new arrival, and that behaviour is governed by their user-supplied scripts.

Now I can launch an embedded interpreter which will run the user-supplied scripts, but I need somehow for the main application to “timeslice” or schedule those sub-processes.

For example there might be a rule that says each actor can perform one action per second. So after an actor performs the one action, I would like suspend their interpreter until their next chance to act.

Another way I thought of implementing it was for the actors to voluntarily release control. e.g. their script might go “say hello; sleep 1000 millis; wave arms; sleep 1000 mills;” If I was going to do that I might as well give them a “crash me” button to push as well.

If I had to destroy or re-enter the interpreters, I would also need some way of storing the state/program counter so it could resume.

I’m also considering running them as separate threads or processes, but I’m not sure how exactly how much control I will have over them, to be able to suspend and resume a thread or subprocess in this manner. Edit: I would also be dependant on the clients behaviour to synchronise everything, just as if I was depending on them to sleep.

I am told this is a common game design pattern, but I can’t really find any useful information on how to implement it. I’ve looked at embedded interpreters, for example Nashorn JS in Java or Lua in C++ and they don’t appear to support suspending the interpreter, which would be “easy way” to do this.

Can anyone give guidelines here? At the moment I think thread or process control might be the best solution?

Edit: I don’t really to want to have write my own interpreter … although if I did, I could obviously suspend it at will. It would be a lot of work even for a basic stack machine.

Edit: I’m beginning to think that I need to have a custom interpreter that I tell to “run 100 opcodes (etc), then sleep”.

3

An interpreter must be defined specifically for this problem. Specifically, many interpreters use the native system call stack to store contexts (i.e. they call themselves recursively, for example to interpret function calls). Such an interpreter cannot readily be suspended. Instead, the interpreter will need to store continuations of function calls directly.

Stackless Python is an implementation of this technique that is popular in the game development industry.

1

So I know this is old but why not pop my two cents:

If the execution model of the interpreter is a bytecode VM with a bytecode evaluation loop, a time-slicing scheduler can be implemented with the following strategy:

Each of these “scripts” runs in a VM-based “thread” of its own. Each of these threads is basically a piece of memory which holds an evaluation stack, a call stack and an instruction pointer of its own.

The VM holds a pointer to the current executing “thread”, and a list of some sort of all “threads” in the system. Each time the VM needs access to the call stack, the evaluation stack or the instruction pointer, it looks for it in the current “thread”.

The evaluation loop is responsible to switch to the next thread according to some strategy. A naive option is a simple round-robin every N bytecode opcodes. This will create “time-slicing” between the different “threads”.

Example C psuedo-code (not checked for logical bugs):

while (program_running) {
    Opcode opcode = *vm.current_thread->instruction_pointer;

    /* Big switch-case to handle all possible opcodes */
    switch (opcode) {
        /* ... Cases for each possible opcode ... */
    }

    /* At the end of the iteration, check if we need to switch thread */
    opcode_counter = (opcode_counter + 1) % THREAD_SWITCH_INTERVAL;
    if (opcode_counter == 0) {
        /* Switch to next thread */
        vm.current_thread = vm.threads[vm.thread_index++ % vm.threads_count]
    }
}

I implemented this strategy in a toy project of mine. Seems to work like a charm.

Hope this helps somebody in the future! 🙂

LEAVE A COMMENT