Threads


Definition

A thread is the standard programming term for the flow of control through code. If you're familiar with operating systems and multi-processor systems, then you understand that PCs with more than one processor are capable of running two threads simultaneously whereas systems with one processor can create the illusion of multi-threading, but they can't actually run two threads at once.

Of course, the threads of a scripting language like Cog aren't going to be the "real" threads used by compiled languages, but they are similar, and we can borrow the terminology.

How They Work

We've said before that when a cog receives an event that it has a handler for, JK finds that handler's label in the code section and code execution begins. But what if that handler contains a sleep and another message is received before the sleep ends? And what if it's the same event?

Threads are the answer to that problem. Cog is capable of having multiple threads for one cog, but JK will only work with the last-created thread. Here's a coding example that's designed to work with the above scenario:

activated:
	if(on) return;
	on = 1;
	channel=PlaySoundLocal(yourSnd, 1, 0, 0); 
	Sleep(GetSoundLen(yourSnd));
	on = 0;

return;
So in our example gameplay, a player activates a console and the above handler is run. The handler first checks to make sure it finished playing the sound from the last time it was run. Now if the player activates the console twice in succession before the sound finishes, the variable on will still have a value of 1, and the second thread will die with that return statement.

You can have up to five threads for each cog. Threads are local to their cogs - meaning that if one cog has five threads, it won't stop another cog from having its five threads. Whenever a cog needs to create a new thread in response to an event, the oldest thread that the cog has will die - or in some cases the new thread will not be created (as in a call).

Let's say that you have a lot of calls to carefully encapsulated messages in your code. But you've noticed after some recent changes that some of your calls aren't working. You need to check and make sure that you don't have more than four calls going at once (remember that the original message handler has a thread too).

Be careful when using sleeps that the sleep's handler won't be run again before the sleep ends. Otherwise, the thread limit will be exceeded, and you'll have to wait for the sleeps to end before you can do anything. And because when JK looks at the cog to see if it needs to do anything (like wakeup from a sleep), it will only look at the last thread. Now if the cog's first four threads slept for only a half second, but the fifth thread slept for five, the cog would wait those five seconds before going back to the first four threads.

If for some reason it's impossible for you to avoid too many threads building up or indefinite sleeping, JK provides the Reset() verb to kill all threads except the current one. This can be a good safeguard, but if your code is well-written, you shouldn't have to use it.

Affected Verbs

We've just gone over how calls and sleeps can cause thread problems, but any verb that causes another thread to be created in its cog can cause the same problem.

When you use SendTrigger() to send a trigger out, this event message is sent out to all of the local cogs that are listening - including the cog that sent the trigger. Whether that cog's trigger handler is looking for that sourceref or not, a thread is still created for the handler - if you've already got some sleeps or calls going, two more threads (the thread that ran SendTrigger() and the triggered thread) may take you over the limit.

This is true of any verb that waits until all local cogs have had a chance to answer its event message. If you use CreateThing() in the class cog of the thing that's created, or if you use SendMessage() to send a message within a cog, you'll have the same problem as with SendTrigger().

Alternatives

Instead of creating extra threads and waiting on them, you should use timers and pulses when you can. Timers and pulses are very similar in implementation and neither of them takes up a thread while they're waiting to run. Although sleeps cause a lot of problems, they aren't unstable or error-prone.

The above example, makes good use of a sleep, and there's no reason to switch to a timer. But in most cases, there's nothing stopping events from happening almost simultaneously - so even if you have a sleep of a tenth of second, there's still a chance of hitting the thread limit.

Imagine that there are ten fuel barrels in your level, and you're standing not too far away with a bryar. A few shots from you and all of those barrels will blow within a tenth of a second. If the barrels' class cog has a sleep for the slight pause before the explosion, then not all of the barrels will explode correctly. But if timers are used (and you can have a ton of timers), then everything will blow up nicely.

Continue