How does plotscripting work?
From OHRRPGCE-Wiki
Or, The Magic Behind the Most Convoluted System in Existance!
Contents |
[edit] The Stack
The main driving force behind plotscripting is this magical entitity known as the stack. It does not like to be refered to by name, instead prefering to do it's work behind the scenes.
The stack is like a stack of dishes at a chinese-buffet restaraunt, where they're in that spring-loaded container, and you can only take the top plate, not any plate in the middle. The stack has two operations that can happen to it: Push and Pop. Pushing something onto the stack is like putting a plate on the stack of plates. Poping it is like taking the top plate off and using it (or, throwing it in the garbage).
So, what does this have to do with plotscripting? Well, when a script is triggered, it's pushed onto the stack, where it can run. Only the top-most script can run at any time. When it completes, it's popped off the stack, so whatever's beneath it can do its stuff (or, if there's nothing, it does nothing).
[edit] The Buffer
Now, when I said that the script is pushed onto the stack, I lied slightly. The script itself is not pushed onto the stack. Instead, it's loaded into the buffer. The buffer, much like the stack, is an area in memory where data can be stored. However, unlike the stack, the buffer is more like the buffet counters where you get your food. There's a limited space for the buffer, and as scripts are loaded and unloaded, different areas of the buffer are used.
The stack contains pointers into the buffer for the script that's running. So, if you have one script running, the buffer would contain the script, and the stack would have a bit in it that says "The current script is the one at the beginning of the buffer". Then, if the script calls a function, that script will be loaded into the buffer (after the original script), and a new bit will be pushed onto the stack: "The current script is the one after the first script on the buffer". Then, when that script finishes, the second bit is popped off the stack, leaving the first bit by itself.
Scripts are not unloaded from the buffer unless another, different, script runs, and takes up the space. This is for optimization. If you have a loop that calls another script, it doesn't need to continuously be loaded and unloaded from disk, and depending on how often you call it, it can really make a difference.
[edit] The Script
So, we've looked at how scripts are stored in memory, and how the engine can find them. But, how do they actually run?
Well, the interpreter starts at the beginning, and works its way from there. Different types of commands have different numbers, and the interpreter uses those numbers to figure out what type of data follows the number. However, that's beyond the scope of this FAQ ;)
Anyway, the interpreter runs until something puts it into a wait state. The wait state tells the interpreter that it's done, and should pass control to the rest of the engine. The only thing that can put the interpreter into a wait state is a waiting command, such as wait, wait for hero or wait for camera. This means that the following code will freeze the engine, as the interpreter never goes into a waiting state:
while(true) do ( )
Also note that this code will not freeze the engine, as the interpreter will almost continuously be put into a waiting state:
while(true) do (wait(1))
In fact, if that script it running, it's mostly harmless, except for taking up a bit of stack and heap space. Of course, the script its in will never end, so you should only use an infinite loop if you really know what you're doing.
Now, let's say you have a script like this:
show text box(1) wait for text box wait(1000)
show text box(2) wait for text box
As you can see, between the two text boxes, you have quite a bit of time to do whatever you want: Talk to NPCs, fight battles (which, as far as the script can tell, only take one tick), do whatever. Some of these things might even trigger another script! In this case, the script is loaded into the heap, and pushed onto the stack like anything else.
From the interpreter's point of view, all that happens is that another script is called. It might be a function in a script, or it might be from a seprate event. It doesn't know, and it doesn't care. It just runs that script until it finishes, at which point it's popped from the stack, and the previous script keeps running, right where it left off. Note that if it had 500 ticks left to wait when the other script happened, it will still have 500 ticks to wait, regardless of how many passed in the other script.
[edit] The End
Well, that's all for now. If you want to know more about how scripts themselves are compiled, check out the (very much incomplete) HSX docs. They'll tell you all about how they're compiled, and how the interpreter runs it.
