Multi-threaded programming

The vrecko core runs in a single thread. This thread resolves message sending, ability codes, display and all other actions.

Each ability can presume that at the time its code is executed (either in the update() method or when receiving a message/request), the rest of the program is stopped and it is safe to make almost any changes to the scene. An exception is deleting itself, which is not recommended.

You can however create your own thread at any time using a common Windows function CreateThread(). In the secondary threads, you have to be careful about the operations you use because not nearly all of them are safe and can cause the application to crash. The basic operations are explained below, along with how to handle them:

 Safe operations

Writing to log

Using the Logger class methods is safe (and the same should apply to the standard C++ stream output operators std::cout and std::cerr). However, the text is saved in a buffer and it will only appear in the file after the printing is done in the next snapshot of the main thread. This way the secondary thread can run very fast and doesn’t wait for writing into the file.

Sending messages

This paragraph only deals with sending messages, not requests – see the next section for more information regarding sending requests.

The code fully supports multi-threading but the messages are not delivered immediately – instead they are put aside and processed in the next snapshot of the main thread. This stops the recipient from executing its code in the secondary thread, which usually doesn’t wait, and could cause the program to crash by trying to change an object in the scene.

Going through all objects in the scene

Warning! A naïve approach by reading the object map (using world->getScenePtr()->getEOMap()) and iterating through all objects will cause the application to crash if anything else adds or removes an object at the same time.

Instead it is necessary to use the Scene::EOIteratorHelper class, which can go through all objects and can deal with changes made while it is working. The usage is simple, but it is necessary to follow certain rules – for more information, see the file vrecko/Scene.h which contains with extensive commentary on this class.

Be cautious – while going through the object in this way is safe, adding or removing objects in the secondary thread is not recommended.

 Semi-safe operations

Sending requests

Warning! Requests are processed similarly to function calls, therefore they are executed immediately. If the recipient doesn’t expect its code to be executed in a secondary thread, it could cause application crashes. However if this is accounted for, there shouldn’t be any problems with using requests in any thread.

Setting object position

Warning! This operation is usually unsafe, but you can create an instance of MTMatrixTransform ability and assign it to the object. If it’s always used for setting and reading the position, everything will work in any thread.

This ability caches the set matrix and returns it on request. It synchronizes with the actual object position once each frame in the main thread.

Auxiliary classes

Apart form the aforementioned Scene::EOIteratorHelper class used for going through objects, there is also an option to use locking mechanisms provided by the content of the vrecko/MTLock.h file, i.e.:

  • the global MTLock class for locking,
  • MTAutoLock class for an even simpler use of MTLock,
  • MTLockSingleProcess class for locking purposes within a single process (faster but restricted),
  • MTReadWriteLock and MTReadWriteLockEx classes for locking for readers and writers
  • and MTTwoPrioLimitedLock for locking with two priority levels.

SystemInfo ability

Allows to name threads and restrict their functionality to selected processors/CPU cores, simplifying the work with threads. The class declaration is located in the base/SystemInfo.h file.

It can display the processor usage by individual threads and limit individual threads to certain processors or processor cores using AffinityMask (external link).

Note: Affinity Mask is a number denoting a bit mask, where a bit set to 1 means that the thread can run on a given processor/CPU core. Each thread will only run on a single processor at a given time, but Windows scheduler switches threads between the processors according to the current system load. Experienced programmers may find it useful to limit individual threads to certain processors, so that Windows doesn’t waste time switching between the processors or to better limit the load on the processor by some threads (e.g. all threads including the main will run on one processor, while the thread for collision detection on Phantom, which needs to run at 1000 Hz frequency, will run on a different processor.)

Every thread can name itself using the following line:

base::SystemInfo::TryToSendThreadCreatedEvent(dwTmpId, "MyThreadName");

It will then appear under this name in the SystemInfo logs. Moreover, if SystemInfo has in the XML configuration file specified AffinityMask for named threads, the AffinityMask will be used if a match in names is detected.