Scripting
Threads

1. Introduction

Threads are the basic building blocks of SinScript. Programmers may refer to them as functions but the sake of clarity, let's just refer to them here as "independent blocks of executable script commands". Any number of these blocks can run independently of each other at the same time. This is the whole purpose behind them because you may want to have several sequences of events occuring at the same time in the game. A thread can be and is often started from within another thread. A thread called within another thread doesn't have to complete executing all of its commands before the thread from which it was called can resume executing its own commands (unless you want it to and use the designated command to achieve this).

In order to be able to run a thread, you first have to define it. A thread definition is simply the list of commands that it will execute when called. This can reside anywhere in the script. Here's a basic example from the intro_start.scr script file:

jc_thread:
$jc anim intro_sit_to_stand
end

This is the most basic form of thread definition but any thread no matter how simple or complex invariably needs to have at least 2 lines in its syntax. First, the name of the thread followed by a colon. Second, the end command. The sequence of commands to execute are sandwiched between these 2 lines although strictly speaking, a thread definition doesn't have to include a list of commands in order for its syntax to be "legal".

Now that the thread is defined, the script is able to run it. This is done simply by using the thread command:

$jc thread jc_thread

What this thread does is make JC stand up from his chair at the beginning of the intro_start cinematic sequence. We'll examine the details of how exactly this is done later.

Now let's use this particular example to illustrate what I mentionned earlier about a thread called within a thread: jc_thread is called within a much larger thread named camera_thread. Here's a snippet from camera_thread where this occurs:

        |
        |
trigger $alarm
wait .4
$jc thread jc_thread
$chair time .6
$chair moveNorth 16
        |
        |

What happens here is that jc_thread is called when camera_thread gets to that line during execution but it does not wait till jc_thread has finished executing all of its own commands before moving on to the next commands "$chair time .6" and "$chair moveNorth 16". These 2 last commands, BTW, make JC's chair move back when he stands up. But did you notice that the chair moves while JC is standing up and not after? Therefore, during that short period of time, 2 threads were running at the same time. That's what the term concurrent threads means but all threads are concurrent by definition so we refer to them simply as threads.

2. Open threads

In the previous example, jc_thread executes all of its commands and terminates when it reaches the end command. Therefore, the thread is closed. But a thread doesn't necessarily have to close. Some threads will pause and wait for an event to occur to resume execution and some might even loop indefinitely and close only when the entire script terminates. As long as a thread hasn't reached the end command, it remains open.

Here's an example from intro_start.scr of a thread that pauses and waits for an event:

skiplevel_thread:
$skiplevel_fake hide
$skiplevel_fake ontrigger skiplevel_sequence
pause
end

What this thread does basically is wait for the player to press the use key to skip the intro_start map entirely. If the player does not press the use key, the intro_start cinematic sequence will play until the end and the thread will remain open until then as well. If the player does press the use key, the thread skiplevel_sequence will be called and skiplevel_thread will resume (un-pause) and close. Here's the code of skiplevel_sequence:

skiplevel_sequence:
releaseplayer
map intro
end

Note:
There may be commands in these examples that you can't figure out but don't worry, we'll get to that later. I just want to avoid getting into too much detail at this point.

Here's an example from intro_start.scr of a thread that pauses and loops over itself indefinitely:

ignore_thread:
local.self ignoreall
pause
goto ignore_thread
end

What this thread does is to override all the characters' standard AI behaviour. They will ignore any event that would normally make them react in a certain way like go into a pain animation sequence when shot at for example. IOW, this prevents them from doing anything until the script gives them a specific command. When that event occurs, the thread is un-paused and loops back upon itself waiting for the next command to be given to the actor. But the main point here is that this thread will remain open for the entire duration of the script.

At this point, I feel that it's important to emphasize on a detail I mentionned at the beginning: A thread called within another thread doesn't have to complete executing all of its commands before the thread from which it was called can resume executing its own commands. This goes for any thread, whether it executes all the way to the end and is closed (like the jc_thread example) or remains open (like in the last 2 examples).

The thread skiplevel_thread stays on pause until the player presses the use key, does this prevent the rest of the script from playing the rest of the intro_start cinematics? Not at all. The thread ignore_thread is basically caught in an infinite loop, does this prevent the script from "moving along"? Absolutely not. And both of these threads are called within another thread.

In fact, this is true regardless if the thread was called from within another thread or not. No matter how many threads are running at any given time, you can always start another one.

3. Making threads sequential

But what if you want to call a thread within a thread but you want to make sure that the called thread finishes executing all of its commands and terminates before the script from which it was called can resume execution of its own commands? IOW, what if you want to make execution of certain threads sequential instead of concurrent? Well, that's what the waitForThread command is for.

Here's a typical example of the use of this command taken from bank.scr:

thread dialog::jc_clear_bank_out
local.waitforthread = parm.previousthread
waitForThread local.waitforthread

What this sequence of commands does is to call a dialog thread which makes JC's icon appear on the screen and say "Clear the bank out, boss. We need to end this!" at the beginning of the bank level. The main difference here is that the thread jc_clear_bank_out must be run in its entirety and allowed to terminate (close) before the commands which come after the waitForThread command can be be executed. As you will see, this command is used extensively in scripts for creating strings of events which have to occur in a specific order and has other uses as well.

4. Dialog threads

Now, some of you are probably wondering by now why the thread command's syntax in the previous example is:

thread dialog::jc_clear_bank_out

instead of simply:

thread jc_clear_bank_out

Well, there are mainly 2 reasons for this. The first one is that the thread that is called here is a particular type of thread, specifically a dialog thread. That's the reason for having the word dialog right after the thread command. The second reason is that the thread definition of jc_clear_bank_out (its actual list of commands) resides in an external file and not in the bank.scr script. This explains the need to have the 2 colons :: between the word dialog and the name of the thread.

5. About external threads

An important thing to note for external threads is that since their code is not defined locally, you have to tell the script in what file to search for the thread definition so it can find the code to execute it. This is done by having the following command at the beginning of the script:

setdialogscript dialog/dialog_bank.scr

This command tells the script that whenever dialog threads are called in the script, it should look for their definition in the dialog_bank.scr file. Also, unlike threads which are defined locally, the setdialogscript command has to be placed before any thread dialog:: commands in the main script to make sure that all the thread dialog:: calls know where to find their definitions to execute the code.

6. Multiple instances of a thread

When a script executes a thread <threadname> command, this is what happens: it reads the command list under the <threadname>: definition all the way down to the end command, then it "makes a copy" of that list and executes it. This is what I call an instance of a thread. The original definition remains available at all times so any other part of the script can "make its own copy" of that same thread and execute it at any time.

Let's look at one of the previous examples from intro_start.scr for this:

ignore_thread:
local.self ignoreall
pause
goto ignore_thread
end

Now let's take a look at a snippet from the script code from where the actual thread calls are made:

                |
                |
$hc1 thread ignore_thread
$hc2 thread ignore_thread
$hc3 thread ignore_thread
$jc thread ignore_thread
$jc2 thread ignore_thread
$blade thread ignore_thread
$hc1 thread ignore_thread
$hc2 thread ignore_thread
$hc3 thread ignore_thread
$hc4 thread ignore_thread
                |
                |

In this particular example, 10 separate instances of the same thread are called. All 10 of them run concurrently and independently from one another.

7. Owner of a thread

Now what's the use of running so many identical copies or instances of the same thread?... Don't they all do the same thing since they all execute the same code anyway?... Yes, they all excute the same code BUT they each of them has a different owner. The owner of the thread call is determined by the $<ownername> parameter that precedes the thread command ($<names> will be covered in more detail in the Named entities section) and local.self in the thread definition means "owner of thread" (this will be covered in more detail in the Variables section).

For now, all that matters is to demonstrate how the possibility to assign an owner to a thread call enables using the same thread definition to accomplish a specific task for 10 different owners instead of having to write 10 separate thread definitions that would do exactly the same job.

8. Conditional thread calls

You can also make thread calls conditional upon a lot of different things: a trigger that was fired in the game, the value of a variable in the script, the position or state of the player or any other object in the game, etc. In general terms, you can make the thread call conditional to anything that can be monitored or calculated in the game or script.

Here's an example from the bank.scr script, a thread defined as locker_door1_thread:

locker_door1_thread:
level.locker_dialog = 0

$locker_door1 time 1
locker_door1_sequence:

$locker_door1 onuse locker_door1_open
pause

locker_door1_open:
$locker_door1 nouse
level.locker_dialog ifequal 0 thread locker_dialog_thread
$locker_door1 rotateYdown 90
waitFor $locker_door1

$locker_door1 onuse locker_door1_close
pause

locker_door1_close:
$locker_door1 nouse
$locker_door1 rotateYup 90
waitFor $locker_door1
goto locker_door1_sequence
end

When you get to the security room in the bank level, there is a locker on the left of the security monitors. If you walk up to it and press the use key, the doors open and JC says: "Oh that could come in handy!". Now, as you probably already noticed, you can open and close the doors of the locker as many times as you want, but JC's remark only plays once.

The thread in this example is used mainly for the opening and closing of the locker doors (only one of them actually but they're both the same). The conditional thread call is the line highlighted in red. What the syntax says in this line is: "If the value of the variable named level.locker_dialog is equal to 0, launch the thread named locker_dialog_thread, otherwise just skip this line. The first thing this script does is to set the value of level.locker_dialog to 0 (second line in the example) so the first time that this thread is executed, the thread locker_dialog_thread will be called when the doors are opened and the script will pause at the second pause command waiting for the player to close the doors.

Now let's take a look at the definition of locker_dialog_thread:

locker_dialog_thread:
level.locker_dialog = 1
wait .5
thread dialog::jc_handy1
end

Ah! You see the what the first command of that thread is? Set the value of that same variable level.locker_dialog to 1. Next, it calls the dialog thread named jc_handy1 which makes JC's icon appears while his comment is heard. Finally the thread reaches the end statement and terminates.

Meanwhile, the thread locker_door1_thread has been on pause, waiting for the player to press the use key to close the doors. So what happens next? If the player closes the door, won't the thread resume? What prevents the thread from setting the level.locker_dialog variable back to 0? Well look at the next to last line in the main thread (the one just before the end command): goto locker_door1_sequence. This makes the thread go to the line with the locker_door1_sequence: label 2 lines before the first pause command but well after the command that sets the level.locker_dialog variable to 0. So therefore, the variable will stay at 1 and the thread locker_dialog_thread will never be called again since the condition will never be met. Thus the dialog will only play while the doors can be opened and closed as many times as you want.

9. Conclusion

Well that's it for threads. Hope this helped clear up a few things about what they are and gave you the information you need to understand what they can do. You should be able at this point to read and write the basic syntax for thread definitions and thread calls.

Next: Named entities