2.9.16
Coherent GT
A modern user interface library for games
Asynchrous mode

Coherent GT has support for multithreading which can greatly enhance your game's performance at an added complexity cost.

When using the async mode, most calls to Coherent GT are deferred and executed on another thread. We guarantee that your callbacks and event listeners will still be run on the main thread (the thread that they are registered on, that is).

Furthermore, Coherent GT will never deadlock but race conditions are unavoidable and as such the user must protect against them by himself.

Usage

Enabling the async mode is as simple as setting Coherent::UIGT::SystemSettings::RunAsynchronous to true when creating the system:

SystemSettings settings;
settings.RunAsynchronous = true;
..
auto system = InitializeUIGTSystem(licenseKey, settings, severity, logHandler);

Note that some methods are always synchronous and will block your main thread no matter what the current mode is. These methods are marked with a warning in the documentation. Some of them are:

Communication with JavaScript

Asynchronicty implies that the JavaScript execution is also deferred on the UI thread. This may require some changes if your existing code depends on the fact that up to now, callbacks were executed in the same stack.

Consider the following communication:

C++:

void Bar()
{
    // Code
}

// During initialization
view->BindCall(Coherent::UIGT::MakeHandler("bar", Bar));

// During your main loop
view->TriggerEvent("foo");

JS:

engine.on("foo", function fooHandler() {
    // Code
    engine.trigger("bar");
});

Calling view->TriggerEvent("foo") in synchronous mode will execute the methods in the following simplified callstack:

Callstack
Bar
JavaScript::fooHandler
View::TriggerEvent
GameLoop

In async mode you get no such guarantee. fooHandler will be executed whenever the UI thread gets to it and Bar will be executed during the first Coherent::UIGT::View::Layout or Coherent::UIGT::View::ExecuteJSTimers after fooHandler has completed. The following diagram demonstrates that:

async_vs_sync.png
Async vs Sync execution
Warning
When unregistering JavaScript events, you will immediately stop receiving them. Because of the asynchronicity, the last sent events from JavaScript may be lost. Make sure that all important events are receved before unregistering.

Remove implicit dependencies on synchronous mode

Let's look at a more concrete example

C++:

void StartGame()
{
    // Initialize the game
}

// During initialization
view->BindCall(Coherent::UIGT::MakeHandler("StartGame", StartGame));

JS:

startButton.addEventListener("click", function () {
    engine.trigger("StartGame");
    doSomethingAssumingTheGameHasStarted();
});

If you are running in a synchronous mode, this code will work fine. When the button is clicked, the engine will call StartGame immediately, which will in turn initialize the game. Thus, by the time engine.call("StartGame") returns you can assume that everything is ready.

This assumption does not hold in asynchronous mode as the execution of StartGame will be delayed. To cope with this, use another event to notify JS that everything is ready:

C++:

void StartGame()
{
    // Initialize the game
    view->TriggerEvent("GameStarted");
}

// During initialization
view->BindCall(Coherent::UIGT::MakeHandler("StartGame", StartGame));

JS:

startButton.addEventListener("click", function () {
    engine.call("StartGame");
});

engine.on("GameStarted", function () {
    doSomethingAssumingTheGameHasStarted();
});

Usage Summary

  • If your code depends on the assumption that Coherent GT works synchronously, small changes to it will be required.
  • Working with the asynchronous mode resembles the model that most web applications use. You might think about the game as the server and your UI as its client
  • Multithreading requires thorough understanding of the threading model in order to minimize the errors and maximize the profits.

Optimizing for asynchronicity

In order to get the maximum performance from the async mode, try to issue the update calls to Coherent GT (Coherent::UIGT::UISystem::Advance, Coherent::UIGT::View::Layout) as early as possible in your game loop and paint the view as late as possible. This allows the UI thread to do as much work as possible before its results are actually needed.

Fencing and thread synchronization

With the async mode it is possible that a call to Coherent GT hasn't completed yet even though it looks like so. For example:

view->Resize(newWidth, newHeight);
view->Paint(frameId);

There's great chance that the UI thread won't resize the view in time which means that you'll paint the view using its old dimensions. Most of the time this won't be a problem because by the next frame the resize will be done. Still, if you need to synchronize with the UI thread, Coherent GT provides a solution through the fencing API:

view->Resize(newWidth, newHeight);
// Make sure that the view has been resized prior to the next paint
auto fence = gtsystem->Fence();
...
gtsystem->WaitFor(fence);
view->Paint(frameId);

Coherent::UIGT::UISystem::Fence and Coherent::UIGT::UISystem::WaitFor do nothing when invoked on a synchronous system so feel safe to use them anywhere and change the mode without changing any of your code.

Asynchronous uninitialization

You may not initialize two different instances of Coherent::UIGT::UISystem with different synchronicity mode during the same application run. Doing so will result in your HTML code rendered instead of your actual views.

Additionaly, due to the former limitation, if you plan on uninitializing the current UISystem and initializing a new one (e.g. in order to change other system settings) you must pass false to Coherent::UIGT::UISystem::Uninitialize(bool shouldTerminateUIThread).

Warning
When you are done with the library, remember to call Coherent::UIGT::UISystem::Uninitialize with true to terminate the UI thread or call FreeUIGTLibraryResources. Forgetting to do that will leak all objects still alive in the UIThread.

Detailed explanation

Knowing what Coherent GT does under the hood might be useful if you run into any problems.

If you are to call Coherent::UIGT::View::Layout for example, instead of running the layout immediately, Coherent GT will now post a job task to another thread (the UI thread) via a standard single-producer / single-consumer queue. The method will immediately return the id of the rendering commands it is going to record, so you can use it to later call Paint with it. The UI thread waits for any tasks to be posted and executes them in order. Note that Coherent will wait for the previous call to layout for this view to complete before issuing the next command in the interest of preventing overtasking the UI thread.

The UI thread can communicate to the main thread via another task queue. For example, when the time for execution of your event handlers (registered via Coherent::UIGT::View::RegisterForEvent or through your custom view listener) comes, the UI thread will ask the main thread to run the callback. This guarantees that your code runs only on the main thread. Most of these events will be triggered during the call to Coherent::UIGT::View::Layout so make sure to call that if you need to be notified about any events going on.

Note
Rendering (Coherent::UIGT::ViewRenderer) works almost the same way whatever the system's synchronicity and you can still paint the view on your rendering thread without changes. The only difference is that in asynchronous mode if you call Paint immediately after getting the id of the rendering commands from Layout, the Paint method may have to wait while the rendering commands are fully recorded.