Talk:Plan for script multitasking
From OHRRPGCE-Wiki
James Paige: it might be a good idea to actually avoid the word "thread" when naming this feature. To a lot of people that will imply real OS threads.
S'orlok Reaves: [Introduction] Then in that case, you seriously need to consider if you'll need pre-emptive threads or not. "OS Threads" generally must be pre-emptable, because OS software is designed by a distributed, independant network of programmers. Photoshop doesn't care about your mail daemon. An RPG is designed by ONE, interdependant group of developers, and as such the number of reasons a thread may have to pre-empt is quite small. Moreover, you don't WANT threads to pre-empt any thread at any time. It is very, very difficult to design a threaded script, knowing that script may be interrupted at any time, for any reason.
S'orlok Reaves: [Body] A healthy compromise is to enforce that: 1) Threads can only force themselves to sleep. 2) Threads can only terminate themselves. By "sleep", I mean that a thread goes to sleep, waiting on an event. For instance:
sleep(10) //will sleep for 10 ticks. wait for text box //is implicitly wrapped as sleep(waitForTextBox) sleep() //will sleep forever (until another thread wakes it up)
...in all cases, this is non-busy waiting.
S'orlok Reaves: [Remarks] With this scheme, threading reduces to the following:
1) For all "WAIT" commands, multitasking may now occur.
2) Short threads (e.g., opening treasure chests) never really pre-empt, which reduces memory usage.
3) Long threads (e.g., tracking keystates) reduce to:
loop:
//Get key
//Do something
//Allow other threads the chance to execute
sleep(1)
end loop
S'orlok Reaves: [One more thing] Actually, you might want to allow suggestions. So requestSleep(someThread) will set someThread's "sleep" bit. And then, someThread can decide when it wants to and if it wants to to sleep, checking if this bit is set. But this is mere syntactic sugar; a global variable could just as easily be used.
Any thoughts?
Bob the Hamster: Right. we don't want threads to be pre-emptive. We just want scripts to be able to continue running while other scripts are waiting. For example, if two scripts are running in tight loops with no waits they do not need to run simultaneously. But if two scripts are running in tight loops and they both contain waits, they will run together, trading off control with their wait statements.
TMC: (My tilde key broke!) (James got in a comment before me)
OK, it seems James was right: the term "thread" has thrown you off. We definitely don't want "preemptive threading" - this is a totally different situation (in terms of splitting CPU usage) than OS threads. "Thread" here refers to shared memory, but independence of execution of different scripts.
Think of script "threads" (new name pending) as a checklist of scripts the plotscripter wants to run every tick. Their order may be important, which is the plotscripter's responsibility, but just one is run at a time until it waits or terminates. They may interact, but generally if you need two-way interaction within the same tick, you would call a script directly instead.
I think scripts should be able to terminate other threads: there will be many cases where you leave the map/a minigame ends/you want to cancel some complicated multi-script action, and provided it's safe to do so, it's easier to kill the relevant scripts instead of "broadcasting" a quit signal with global variables.
We don't need a new sleep command. Wait will work to cause a script to stop for the desired number of ticks. Putting threads to sleep (remotely) sounds just the same as pausing them. But right, you should be able to pause yourself.
Thinking about it, I think the default for new threads should be to run them last. So:
plotscript, main, begin new script thread (@scripta) new script thread (@scriptb) while (1) do ( .... foo .... wait(1) ) end
Every tick, triggered scripts, then main, scripta, scriptb will run in that order. Hmm.. maybe there should be better control over triggered scripts somehow...
Also, that blocking thread idea sounds good to me. I think that scripts with definescripts should be blocking whenever in backwards compatibility mode.
S'orlok Reaves: Ah, I see. So the prototypical (best-example) use for scripting threads would be to break up old code like:
loop:
if (I'm on this map)
do this thing
end if
if (I have this item)
do this thing
end if
...
end loop
...into different scripts for each "if". Certainly, that makes much more sense.
So they're kinda like... concurrent scripts.... or "multi"-scripting...
Oh, also, I wasn't refering to a global kill switch. I just feel that it's unwise to remotely kill a thread, and that remote termination is only needed for threads that loop forever or sleep for a very long time. So why not just "ask" the thread to terminate?
ThreadA: (shows some splash screen on "Esc", terminates it on "Esc")
//Note: Assume that a thread which has finished is given the status "inactive"
threadB := null //nothing
loop:
on keypress (Esc)
if (isNull(threadB) || isInactive(threadB))
ask_to_kill(threadB)
else
threadB := new thread(@showHUD)
end if
end
wait 1
end loop
ThreadB:
loop:
if (my "ask to kill" bit was set)
break
end if
//Do normal stuff
...
wait 1
end loop
...since this keeps control of Thread A entirely within the code for Thread A.
Sidenote: As far as new scripts running first or last, you might consider giving the user control over this at creation time. I.e., thread aThread := new thread(10, @scriptB) will make a new thread which runs in slot "10" (10th position). This encourages users to keep a low number of threads, and to know which threads are running at any time.
The Mad Cacti: (On a different computer now)
Game Maker (I poked around a couple of Rinku's games) seems to do scripting by allowing you to attach an "each step" or creation or destruction, etc, script to objects in the "room" (map). I don't think the order they are called is defined, and they must finish (no waiting). The idea is: each object can call an independent script to handle their movement, or their collision detection, complex animation, etc. This is a core use I'd like for threads ("npc move script" trigger type maybe?)
I don't see why you would want to kill a thread that has already terminated. Finished threads won't need to be kept around.
The ask to kill mechanism is better achieved through a global variable, as it's simpler, I think. Of course, then the engine can't request to kill the thread, but I don't see any situations where it would actually need to ask. All possible cases in which a thread is killed: because of an error, because it finishes, or because of game over/loading a game.
I'll update the article with a suggested (rather complex) mechanism for specifying order when I have time.
S'orlok Reaves: Certainly. Er... my idea of keeping a thread around is actually just a reflex: I use Threads in Java, and they can be expensive to create. So if you KNOW that you're going to "restart" a thread, you just keep the old one around and avoid re-creating it.
This isn't an issue in the OHR, I think, since threads are just... what? A pointer to a script, some stack/heap space, and a program counter? Yeah, no need to keep used threads.
Bob the Hamster: Regarding the idea of an NPC each-step script, the only reason I did not implement that at the same time as the hero each-step script was back then performance was *much* worse, and i didn't think it could happen at a sane speed. There has been a lot of interpreter cleanup since then, so I think it is sensible now.
Although rather than an each-step script it might make more sense to have a new sort of trigger, and "NPC AI script" which is run once for each NPC on every cycle in which it isn't already running (which means that each NPC instance needs to keep track of whether or not its own script has terminated). So you could write a script for your NPC that looks like this: and it would do the right thing:
plotscript, castle guards, npcref, begin
variable(x,y)
if(hero is close to(npcref, 1))
then (got caught)
if(hero is straight ahead)
then (alarm := true)
if(alarm)
then (chase hero(npcref))
else (walk NPC(npcref, random(0,3), random(1,2)), wait for NPC(npcref))
end
Does that make any sense? As long as script multitasking was available, and the above script was only run once-at-a-time for each instance of the "guard" NPC, it would behave like a loop, but with simpler syntax.
S'orlok Reaves: So... is NPCAI just like NPC On Step, except at a finer granularity (each tick, instead of each finished step), and threaded?
The Mad Cacti: Regarding the blockingscript trigger type idea, I thought it was a good idea, however it turns out that the way we designed script triggers, that this isn't possible: each script id field is presumed to be a certain trigger type, and you can't tell which type it is (plot or blocking, for example) from its value. Since we only have one trigger type so far, we can still easily change this, and associate each trigger id with a trigger type, script id, and script name. But we don't have to fix it right now, and I'd rather not get that sidetracked.
Besides, I think this blockingscript command is a more powerful idea.
Bob the Hamster: Yeah, that sounds like a good idea.
