Plan for script multitasking

From OHRRPGCE-Wiki

Jump to: navigation, search

The thoughts here are messy and confused! Please help sort them out by providing feedback!

To support script multitasking, each script "thread" will have separate execution information: the list of running scripts (currently scrat), a private stack for arguments and state information, and probably space for local variables (if each script instance doesn't get it's own buffer for locals).

Now each triggered script will run in its own thread. So a map autorun script containing a loop will be able to check if the player has left the map so it should stop, even if another looping map autorun script was triggered. This will of course break a great many games, so it will be turned on by a general bitset which will be off for old games and on for new games.

Original thoughts started at this thread: http://www.castleparadox.com/ohr/viewtopic.php?t=4239

Contents

[edit] Naming

Use of the term "thread" has already caused a lot of confusion and should perhaps be abandoned. The plan refers to threads variously as either "threads" or "scripts", please use context. However "script" might be even more confusing, since users think they already know what a script is and it can be ambiguous even with context. "script stacks" might be another alternative, though in my experience stacks have been another difficult term for users to grasp :P

[edit] Blocking scripts

A general bitset will disable threading for old games, so backwards compatibility will not be affected.

However, to ease the transition from blocking scripts to threading scripts for authors of existing games, we can add a new script command, blocking script (see above). By adding this command to the top of a script, it will behave like old scripts, and pause any other blockingscripts until it is finished.

A game author who wishes to convert their existing scripts to use threads can do the following:

  1. Add blocking script at the top of all their plotscripts
  2. Change the general bitset to enable threads for their RPG
  3. Remove blocking script one-at-a-time, with testing.

[edit] Execution order

New triggered threads should be set to run before all current ones. That way, onkeypress scripts are run first, and the current script order is preserved: the mapautorun script of the first map runs before the newgame script.

However, script threads started from other scripts are tricky.

My current thinking is this: firstly, any new threads should run that tick instead of having to wait. In fact, it would hopefully be least confusing if they ran immediately, in the order in which they are written in the script. In fact, they would be put before the parent thread in the thread list, and the interpreter would jump up the list by one script. This has the additional advantage that all spawned threads will run each tick in the order they were created from their parent.

(This idea gets the James seal of approval Bob the Hamster 20:48, 16 October 2008 (UTC))

[edit] Hazards

The script debugger would need a lot of attention to handle threads. It also needs a lot of attention to make it user-usable, and potentially to handle extra HSpeak outputted debug info. In sum, a rewrite. Before it was ever officially ready for use :(

Some commands, like teleporttomap, imply a wait and others set variables to trigger an effect when they finish, like showtextbox. Commands which communicate with want variables can be rewritten where it's not backwards incompatible to do so, other all threads could just continue to use the same want variables. Still uncertain-

Here is the list of want flags:

wantbox, wantdoor, wantbattle, wantteleport, wantusenpc, wantloadgame

(How about making want variables members of the script's UDT, that way each "thread" has its own copy of them and they cannot collide. I don't think this would break any compat. Bob the Hamster 20:50, 16 October 2008 (UTC))
(Also, most want variables will indeed be possible to eliminate. The main reason they exist is to provide a way for code inside subs to trigger jumps to module-level GOSUBs which is irrelevant now that many of those gosubs have already be SUBified Bob the Hamster 20:52, 16 October 2008 (UTC))


[edit] Opportunities!

Perhaps the implicit waits in commands like usenpc could be disabled when script multitasking is enabled? Rational: continuing with the current behaviour would require either forcing just the calling script to wait and allowing others to run (when a user who doesn't know about the implicit wait probably won't expect them to!) or jumping out of the interpreter completely, halting other scripts for a tick. Both seem illogical.


[edit] New script commands

A tentative list of suggested new commands to handle threads.

Note that the command names are VERY open to change. I'm not happy with many of them.

Omitted are many commands to check what threads are currently running, to manipulate the order they run in, other usefuls. Possibly the ability to switch temporarily between threads?

[edit] new script (commands...)

When this block (which is actually a flow control type (or maybe a new type altogether), and NOT a top level statement like 'script') is encountered, execution appears to proceed into it, but in fact occurs in a newly created thread which is set to run BEFORE this thread in the thread list, but immediately gets focus. See #Execution order. When it waits or completes, the parent script is reentered all in the same tick. Can contain anything. This block also returns a script handle.

The parent script can quit regardless of what spawned scripts do, and if any are still alive, then the parent script's local variables aren't freed.

You can access variables declared in the parent script, while variables declared inside this block could be in a different scope to the parent script. This can be achieved by putting all the variables in the parent script's variable list, but keeping track in HSpeak what scope variables have (probably with some behind the scenes name decoration) But is suddenly adding scoping to HS a good idea? I guess it's just not needed, and encourages people to put big messy scripts in newscript blocks instead of splitting them off.

Should be nestable.

Example:

script, split up!, begin
 variable (Sean, Sarah, Sam)
 Sean := npc reference (...
 #... NPC manip

 show text box (49)  # everyone split up!
 wait for textbox
 variable (count down)
 count down := 300

 new script, begin
  walk npc (Sean, left, 4)
  wait for npc (Sean)
  walk npc (Sean, up, 10)
  #disappear off map...
 end

 variable (Sarah's path)
 Sarah's path := new script, begin
  walk npc (Sarah, right, 8)
  wait for npc (Sarah)
  #...
 end

 #continue main script here...
 # some special condition: Sarah stops and comes back
  kill script (Sarah's path)
  wait for npc (Sarah)
  #walk Sarah back...
 
end

But normally you would split things up into other scripts if they are nontrivial:

To run a script in a new thread instead of a bunch of commands, you would just write

handle := new script( falling chimney animation(npc) )

Since this would be common, and we won't want the parent script's variables and data kept around so unnecessarily, this should be optimised as special command/whatever.

What if you don't want the script to run immediately, but just want create it and get the handle to manipulate it? You could write

handle := new script( wait, falling chimney animation(npc) )

but this bypasses the above proposed optimisation and slightly unpleasant. Prehaps we need another command.

[edit] new script by id (@scriptname, arg1, arg2, ...)

Probably don't need/shouldn't add this command. You can write

new script, begin
 run script by id (@scriptname, arg1, arg2, ...)
end

instead.

[edit] this script

Returns a handle to this script thread. Notice that inside newscript, it returns a handle to the child script.

[edit] parent script

Returns a handle to the script thread that called this one, or 0 if it is spontaneous.

[edit] find script (id)

Given a script id (as in definescript), return a handle to the first script with that id. Undecided:...should it return the first thread with that script anywhere, at the bottom of, or on top of the script stack? Other commands for other cases? Undecided:...what about a method of returning all such scripts, like NPCcopycount/NPCreference? (and not like find hero)

[edit] get script id (handle)

(Confusing name)

Return the script id (as in definescript) of the topmost script of a script stack. Or would bottom most be more useful? Two different commands?

[edit] pause script (handle)

Stop a thread from being executed. For example, if you want the current and another thread to wait for something, you could do:

pause script (handle)
wait for npc (npc)
unpause script (handle)

[edit] pause script and children (handle)

[edit] resume script (handle)

[edit] wait for script (handle)

Waits for the given script (thread) to finish.

[edit] wait for child scripts

Waits for all script threads spawned from this one to finish. (What about grand children?)

[edit] blocking script

Pauses any other script which has run this command (there will normally be at most one unpaused one), and automatically resumes it when the script in which this appears quits. Perhaps this should override the other script pausing/resuming commands.

[edit] pause all scripts

Pause all other scripts.

[edit] resume all scripts

[edit] kill script (handle)

Kill off a whole thread, ending all scripts in it. Could be used to kill the current thread (prehaps a command to get an id for the current thread? But would it have other uses?)

[edit] exit thread??

(Replace by kill script with default argument, I suppose) Kill off current thread. This could also be used if multitasking threads are not enabled, killing all scripts.

[edit] pause script triggers

Stop triggering of scripts (by game events). This would also be useful if multitasking were disabled.

[edit] resume script triggers

[edit] run each tick (commands...)

(Needs a better name. Also, maybe this command should not be implemented. It's meant to provide simple solitio)

A newscript-style command. Specify some code that should be called every tick immediately before this script on the script-stack, even if this script is paused or waiting. Can be used to kill off the thread in appropriate circumstances. Here's an example to kill off a script when the map changes, a common source of problems:

plotscript, NPCs play tennis, begin
  variable (map)
  map := current map
  run each tick, begin
    if (map <> current map) then (kill script (parent script))
  end
  #long strings of NPC and map manipulation commands
  ...
end

What should happen if the script doesn't terminate within a tick? Presumably it wouldn't be respawned if it's already running. And it obviously stop respawning when the parent thread is killed.

run each tick would NOT return a script handle, unlike newscript.

Note that the same effect as runeachtick can always be achieved using newscript:

plotscript, NPCs play tennis, begin
  new script, begin
    variable (map)
    map := current map
    while (true) do, begin
      if (map <> current map) then (kill script (parent script), exit script)
      wait
    end
  end
  ...

So it's likely that providing the convenience of runeachtick is just a bad idea. I know what Python would have to say about this...

[edit] New Triggers

Eg. NPC movement scripts. To be completed...

[edit] See Also

Personal tools