Skip to content

Concurrency

This section contains the documentation for all mechanisms revolving around several things running at once, usually involving threads and their related synchronization facilities.

A warning that delving into this section will expose you to some rather low level concepts, the misapplication of which could result in your program crashing or acting oddly without the usual helpful error information provided by NVGT.

The highest level and most easily invoked method for multithreading in nvgt is the async template class, allowing you to call any script or system function on another thread and retrieve it's return value later.

Classes

async

A template class that allows one to very easily call any function in nvgt on another thread and fetch that function's return value after the call has complete.

  1. async<T>();

  2. async<T>(const ?&in function, const ?&in arg1, const ?&in arg2, ...);

Template arguments:

Arguments (2):

Remarks:

Generally speaking, this is the most convenient method of applying multithreading to your nvgt application. While lower level methods of dealing with threads require the programmer to create functions with specific signatures that usually don't allow for return values, this class allows you to quite transparently call any function in the application from a function in your script to the alert box built into NVGT on another thread while still maintaining the return value of such a function for later retrieval.

For ease of use, the constructor of this class actually calls the function provided and passes the given arguments to it. Therefor, the standard use case for this class is to create an instance of it, then to first call instance.wait() or instance.try_wait(ms) before retrieving the instance.value variable when the function's return value is needed, after either instance.wait() returns or after instance.try_wait() returns true. Using the instance.complete/instance.failed properties with your own waiting logic is also perfectly acceptable and sometimes recommended.

The one drawback that makes this class look a little less pretty is that if you wish to call functions that are part of a class, you must create funcdefs for the signature of the function you want to call, then wrap the object's function in an instance of the funcdef. For example if a class contained a function bool load(string filename), you must declare funcdef void my_void_funcdef(string); and if you then had an instance of such a class called my_object, you must initialize the async object like async<bool>(my_void_funcdef(my_object.load), "my_file.txt"); however it is a relatively minor drawback.

Be aware that this class can throw exceptions if you do not pass arguments to the function correctly, this is because the function call happens completely at runtime rather than being prepared at compilation time like the rest of the script.

Internally, this function is registered with Angelscript multiple times with an expanding number of arguments, meaning that it is OK to pass only the number of arguments you need to a function you want to call even though this function's signature seems to indicate that the dynamically typed arguments here don't have a default value.

Example:

void main() {
	show_window("example");
	async<int>@ answer = async<int>(question, "exit program", "Do you want to exit the program?");
	// Now that the user sees the dialog, lets play a sound.
	sound s;
	s.load("c:/windows/media/ding.wav");
	s.play_looped();
	// We will move the sound around the stereo field just to drive home the point that we can execute our own code while the dialog is showing.
	int pan = 0;
	while(true) {
		wait(25); // A slower value than average for the sake of the pan effect, will make window a bit unresponsive though (use a timer).
		pan += random(-2, 2);
		if (pan < -100) pan = -100;
		else if (pan > 100) pan = 100;
		s.pan = pan;
		if (answer.complete) { // The async function has successfully finished and a return value is available.
			if (answer.value == 1) exit();
			else @answer = async<int>(question, "exit program", "Now do you want to exit the program?");
		}
	}
}

Methods

try_wait

Wait a given number of milliseconds for an async call to complete.

bool try_wait(uint milliseconds);

Arguments:
Returns:

bool: True if the async call has finished, or false if it is still running after the given number of milliseconds has expired.

Remarks:

If you try waiting for 50ms but the async call finishes in 10ms, the try_wait call will only block for 10 out of the 50ms requested. In short, any blocking try_wait call is canceled prematurely with the try_wait function returning true if the call finishes while try_wait is in the middle of executing.

If the function has already finished executing or if this async object is not connected to a function (thus nothing to wait for), try_wait() will immediately return true without waiting or blocking at all regardless of arguments.

Similar to the async::wait function, you should be careful using this method in the same thread that show_window was called or if you do, make sure to call it with tiny durations and interspurse standard nvgt wait(5) or similar calls in your loops so that window messages and events continue to be handled.

Example:
void main() {
	// Pan a sound around while an alert box shows, no window.
	async<int> call(alert, "hi", "press ok to exit");
	sound s;
	s.load("c:/windows/media/ding.wav");
	s.play_looped();
	while (!call.try_wait(40)) { // We'll use the blocking provided by try_wait for our timer.
		s.pan += random(-3, 3);
	}
}
wait

Wait for the async function to finish executing.

void wait();

Remarks:

This is the simplest way to wait for the execution of an async function to complete, however it does not allow you to execute any of your own code while doing so. The execution of your program or the thread you called the wait function on will be completely blocked until the async function returns. This also means that window events cannot be received while the wait function is executing, so you should be very careful about using this from an environment with a game window especially on the thread that created it.

For more control such as the ability to execute your own code during the wait, check out the try_wait() function.

If the async object this function is called on is executed with a default constructor and thus has no function call in progress, this function will return immediately as there is nothing to wait for.

Example:
void main() {
	async<string> result(input_box, "name", "please enter your name");
	result.wait(); // Input_box creates a dialog on it's own thread so the remark about windowing doesn't apply in this situation.
	alert("test", "hello " + result.value); // May not focus automatically because from a different thread than the input box.
}

Properties

complete

Immediately shows whether an async function call has completed, meaning that either a result is available or that there has been an error.

const bool complete;

Remarks:

This is the best and prettiest looking method of checking whether an async call has completed or not without blocking. You can also call the try_wait() function with an argument of 0 for a similar effect, though this looks nicer and takes any uninitialized state of the object into account.

Usually this will be used when a loop has some other sort of waiting logic, such as the global nvgt wait() function we are all familiar with.

Example:
void main() {
	sound s; // Press space to play even while alert box is opened.
	s.load("c:/windows/media/ding.wav");
	async<int> call(alert, "test", "press ok to exit"); // May need to alt+tab to it, window is shown after.
	show_window("example"); // Shown after alert because otherwise alert will be child of the window.
	while(!call.complete) {
		wait(5);
		if (key_pressed(KEY_SPACE)) {
			s.stop();
			s.play();
		}
	}
}
exception

A string describing any exception that took place during an async function call.

const string exception;

Remarks:

If there has been no error or if the async object has no attached function call, a blank string will be returned.

Example:
funcdef void return_void_taking_uint(uint);
void main() {
	string[] my_empty_array;
	async<void> result(return_void_taking_uint(my_empty_array.remove_at), 0); // We are calling my_empty_array.remove_at(0) on another thread, sure to cause an exception because the array is empty.
	result.wait();
	alert("test", result.exception);
}
failed

Returns true if an async function call has thrown an exception.

const bool failed;

Remarks:

This is a shorthand version of executing the expression (async.try_wait(0) and async.exception != ""), provided for syntactical ease.

Example:
string throw_exception_randomly() {
	if (random_bool(50)) throw("oh no!");
	return "yo yo";
}
void main() {
	async<string> result(throw_exception_randomly);
	result.wait();
	if (result.failed) alert("oops", result.exception);
	else alert("success", result.value);
}
value

Contains the return value of a successful async function call.

const T& value;

Remarks:

Remember that this class is a template type, so T will adapt to whatever type was being used when the async object was constructed.

If a value is not yet available, the wait function will be internally called upon first access to this property, meaning that accessing this property could cause your program to block until data is available. You are meant to call the wait/try_wait functions or check the complete property first before accessing this.

If an async object is initialized with the default constructor meaning it has no function attached, for example async<string> result; accessing result.value will throw an exception because no data will ever be available in this context.

Example:
void main() {
	// Lets demonstrate the edge cases mentioned above as most examples in the documentation for this class show off this property being used normally.
	async<string> result1(input_box, "type text", "enter a value");
	alert("test", result1.value); // The main thread will block until result1.value is available. Be careful!
	async<sound@> result2; // This is not connected to a function, maybe the object could be reassigned to a result later.
	sound@ s = result2.value; // Will throw an exception!
}

mutex

This multithreading primitive allows for any number of threads to safely access a shared object while avoiding illegal concurrent access.

mutex();

Remarks:

It is not safe for 2 threads to try accessing the same portion of memory at the same time, that especially becomes true when at least one of the threads performs any kind of updates to the memory. One of the common and widely used solutions to this problem is basically to create a lock condition where, if one thread starts modifying a bit of shared data, another thread which wants to access or also modify that data must first wait for the initial accessing thread to complete that data modification before continuing.

The mutex class is one such method of implementing this mechanism. On the surface it is a simple structure that can be in a locked or unlocked state. If thread A calls the lock method on a mutex object which is already locked from thread B, thread A will block execution until thread B calls the unlock method of the mutex object which can then be successfully locked by thread A.

This is the most basic mutex class, though there are other derivatives. Most mutex classes have the ability to try locking a mutex for up to a given timeout in milliseconds before returning false if a lock could not be acquired within the given timeout.

Mutexes or most other threading primitives are not connected by default to any sort of data. You can use one mutex for 10 variables if you want and if it makes sense in your context.

Remember, you are beginning to enter lower level and much more dangerous programming territory when dealing with multiple threads. If you accidentally forget to unlock a mutex when you are done with it for example, any thread that next tries locking that mutex will at the least malfunction but is much more likely to just hang your app forever. Be careful!

Example:

// globals
mutex ding_mutex; // To control exclusive access to a sound object.
sound snd;
thread_event exit_program(THREAD_EVENT_MANUAL_RESET);
// This function plays a ding sound every 250 milliseconds, the sleeping is handled by trying to wait for the thread exit event.
void dinging_thread() {
	while(!exit_program.try_wait(250)) {
		ding_mutex.lock();
		snd.seek(0);
		snd.play();
		ding_mutex.unlock();
	}
}
void main() {
	snd.load("C:/Windows/media/chord.wav");
	show_window("mutex example");
	// Start our function on another thread.
	async<void>(dinging_thread);
	while (!key_pressed(KEY_ESCAPE)) {
		wait(5);
		if (key_pressed(KEY_L)) {
			// This demonstrates that if we lock the mutex on the main thread, the ding sound pauses for a second while it's own ding_mutex.lock() call waits for this thread to unlock the mutex.
			ding_mutex.lock();
			wait(1000);
			ding_mutex.unlock();
		}
		// A more real-world use case might be allowing the sounds volume to safely change.
		if (key_pressed(KEY_UP)) {
			mutex_lock exclusive(ding_mutex); // A simpler way to lock a mutex, the mutex_lock variable called exclusive gets deleted as soon as this if statement scope exits and the mutex is then unlocked.
			snd.volume += 1;
		} else if (key_pressed(KEY_DOWN)) {
			mutex_lock exclusive(ding_mutex);
			snd.volume -= 1; // We can be sure that these volume changes will not take place while the sound is being accessed on another thread.
		}
	}
	exit_program.set(); // Make sure the dinging_thread knows it's time to shut down.
}

methods

lock

Locks the mutex, waiting indefinitely or for a given duration if necessary for the operation to succeed.

  1. void lock();
  2. void lock(uint milliseconds);
Arguments (2):
Remarks:

With the mutex class in particular, it is safe to call the lock function on the same thread multiple times in a row so long as it is matched by the same number of unlock calls. This may not be the case for other types of mutexes.

Beware that if you use the version of the lock function that takes a timeout argument, an exception will be thrown if the timeout expires without a lock acquiring. The version of the function taking 0 arguments waits forever for a lock to succeed.

try_lock

Attempts to lock the mutex.

  1. bool try_lock();

  2. bool try_lock(uint milliseconds);

Arguments (2):
Returns:

bool: true if a lock was acquired, false otherwise.

Remarks:

This is the method to use if you want to try locking a mutex without blocking your programs execution. The 0 argument version of this function will return false immediately if the mutex is already locked on another thread, while the version of the method taking a milliseconds value will wait for the timeout to expire before returning false on failure.

Note that several operating systems do not implement this functionality natively or in an easily accessible manner, such that the version of this function that takes a timeout might enter what amounts to a while(!try_lock()) wait(0); loop while attempting to acquire a lock. If the operating system supports such a timeout functionality natively which will be a lot faster such as those using pthreads, that will be used instead.

Example:
// Press space to perform a calculation, and press enter to see the result only so long as the calculation is not in progress.
thread_event keep_calculating;
bool exit_program = false; // NVGT will wrap atomic flags later, for now we'll piggyback off the keep_calculating event.
mutex calculation_mutex;
int calculation;
void do_calculations() {
	while (true) {
		keep_calculating.wait();
		if (exit_program) return;
		// Lets increase the calculation variable for a bit.
		screen_reader_speak("calculating...", true);
		timer t;
		calculation_mutex.lock();
		while(t.elapsed < 1000) calculation++;
		calculation_mutex.unlock();
		screen_reader_speak("complete", true);
	}
}
void main() {
	async<void>(do_calculations); // Spin up our thread.
	show_window("try_lock example");
	while (!key_pressed(KEY_ESCAPE)) {
		wait(5);
		if (key_pressed(KEY_SPACE)) keep_calculating.set();
		if (key_pressed(KEY_RETURN)) {
			if (calculation_mutex.try_lock()) {
				screen_reader_speak(calculation, true);
				calculation_mutex.unlock();
			} else screen_reader_speak("calculation in progress", true);
		}
	}
	exit_program = true;
	keep_calculating.set();
}
unlock

Unlocks the mutex.

void unlock();

Remarks:

This function does nothing if the mutex is already unlocked.

mutex_lock

Lock a mutex until the current execution scope exits.

  1. mutex_lock(mutex@ mutex_to_lock);
  2. mutex_lock(mutex@ mutex_to_lock, uint milliseconds);
Arguments (2):
* uint milliseconds: The amount of time to wait for a lock to acquire before throwing an exception.
Remarks:

Often it can become tedious or sometimes even unsafe to keep performing mutex.lock and mutex.unlock calls when dealing with mutex, and this object exists to make that task a bit easier to manage.

This class takes advantage of the sure rule that, unless handles become involved, an object created within a code scope will be automatically destroyed as soon as that scope exits.

For example:

if (true) {
	string s = "hello";
} // the s string is destroyed when the program reaches this brace.

In this case, the constructor of the mutex_lock class will automatically call the lock method on whatever mutex you pass to it, while the destructor calls the unlock method. Thus, you can lock a mutex starting at the line of code a mutex_lock object was created, which will automatically unlock whenever the scope it was created in is exited.

The class contains a single method, void unlock(), which allows you to unlock the mutex prematurely before the scope actually exits.

There is a very good reason for using this class sometimes as opposed to calling mutex.lock directly. Consider:

my_mutex.lock();
throw("Oh no, this code is broken!");
my_mutex.unlock();

In this case, mutex.unlock() will never get called because an exception got thrown meaning that the rest of the code down to whatever handles the exception won't execute! However we can do this:

mutex_lock exclusive(my_mutex);
throw("Oh no, this code is broken!");
exclusive.unlock()

The mutex_lock object will be destroyed as part of the exception handler unwinding the stack, because the handler already knows to destroy any objects it encounters along the way. However any code that handles exceptions certainly does not know it should specifically call the unlock method of a mutex, thus you could introduce a deadlock in your code if you lock a mutex and then run code that throws an exception without using this mutex_lock class.

A final hint, it is actually possible to create scopes in Angelscript and in many other languages as well without any preceding conditions, so you do not need an if statement or a while loop to use this class.

int var1 = 2;
string var2 = "hi";
{
	string var3 = "catch me if you can...";
}
string var4 = "Hey, where'd var3 go!";

Functions

thread_sleep

Sleeps the thread it was called from, but can be interrupted.

bool thread_sleep(uint milliseconds);

Arguments:

Returns:

bool: true if the thread slept for the full duration, false if it was interrupted by thread.wake_up.

Remarks:

This function should only be called in the context of threads created within your script.