Crave For Games


You are in the Article list. If you want a full list of the StoneDrop develompent log notes you can go to the DevLog page.
Multithreading (Unity3d, C#)

Goal: perform heavy computations in the backgoround w/o affecting game performance and responsiveness on Android, iOS, Standalone and WebGL build targets in Unity.

Approach: custom class based on BackgroundWorker and Coroutines, the source code is attached.

 

Because I have plans to make a pretty smart AI which will be using relatively heavy computations, I've decided to make it possible to run them in a background.

 

The first thing you encounter in the google in such a case is Unity's Coroutines (docs.unity3d.com/M...[1]). But they are performed NOT in parallel with the main application thread, but within it. The execution is just smeared across multiple frames. The coroutines have pros and cons. The advantage is the ability to use Unity API in them (i.e. interact with physics engine, animations, particles, transforms, rendering pipeline, etc. in one word - do everything you can do in Unity). But there are two main disadvantages of using coroutines. First - you as a programmer have to break the whole necessary work into parts that will be executed one at a frame and put "yield return null;" between those parts. Second disadvantage is the immediate corrolary of the first one: you can't (easely) use full computational power of fast mobile devices (or other high-performance build targets) and ensure your game won't be lagging on the slow ones at the same time because once again the amount of computations per frame is fixed.

(Actually you can implement pretty complicated performance management system with conditional "yield return null;"'s in your algorithms, but it will be very clumsy and will require a lot of patience to tune not mentioning unavoidable waste of cpu power for it to operate)

Taking into account the cons of the coroutines I've decided to use dedicated thread for the heavy computations. In such an approach the resources spend on the computations will be managed by the operating system and it'll use all the available ones left from the main Unity thread.

 

Unity doesn't provide any multithreading API but that doesn't mean we cannot use one from the C#. In particular there are Thread (msdn.microsoft.com...[2]) and BackgroundWorker (msdn.microsoft.com...[3]) classes that will allow you to gain the power of multithreading in you game. My personal choice for the StoneDrop is the BackgroundWorker because in my (turn-based) game there will be only one active player at a time, thus I really need only one background thread, but the Thread class is not reusable.

 

It's easy (at first glance) to use BackgroundWorker: it's an object with three delegates (figure 1). The first one is the computations being computed in a background thread; the second one used to report a progress or to pass intermediate results from the background thread to the main one; the third one is a callback that will be called in the main thread right after the background one completes the computations. Also you have an ability to cancel the computations from the main thread while the background one is busy (don't forget to periodically check the flag CancellationPending in your computations delegate and return immediately if it's true).

160201_bw_proper.jpg

Figure 1. Example of BackgroundWorker usage in a regular C# application

 

BUT! The BackgroundWorker works in Unity not the way you would expect. All three delegates will run in the background thread as shown on the figure 2 (seems like Unity doesn't create a synchronization context and the BackgroundWorker doesn't know which thread is the main one). The other problem is multithreading is not supported in WebGL builds (docs.unity3d.com/M...[4]).

160201_bw_unity.jpg

Figure 2. How the BackgroundWorker works in Unity

 

Taking everything above in consideration I've decided to write my own class-wrapper which will use a multithreading if it's supported on the platform and fallbacks to the coroutines if it is not. Obviously I still has to write the algorithms with coroutines (i.e. "yield return null") in mind, but this time I'm sure the coroutines will run in a browser on a desktop, so no need to scale the computations intensity in a wide range (and besides, the WebGL is not my primary target at all, it's just for demo on a web-site).

The source code of the wrapper is here: goo.gl/itaqKf[5]

The usage (figure 3):

160201_ubw_usage.jpg

Figure 3. Usage example for UnityBackgroundWorker class

1. Create an empty GameObject on the scene and attach the UnityBackgroundWorker component,

2. Setup the desired update time - how frequently the check for the background thread finished execution will be performed,

3. To run something in the background use one of the functions

UnityBackgroundWorker.Run(task, callback, progress); UnityBackgroundWorker.Run(task, argument, callback, progress);

where task is a method with computations which returns IEnumerator and take in no parameters or one parameter of type object; argument is a parameter that will be passed to the task; callback will be fired in the main thread after the computations finished; progress is an optional delegate Action which will run in the main thread every time in the computations the line "yield return new Progress(float progress);" will be executed,

4. To stop the execution use

UnityBackgroundWorker.Stop();

5. Use

yield return null;

in your computations and UnityBackgroundWorker will automatically check if the computations were cancelled with Stop() function and abort it if it was,

6. Use

yield return new UnityBackgroundWorker.Wait(3f);

to suspend the execution for 3 seconds,

7. Use

yield return new UnityBackgroundWorker.Progress(50f);

to trigger the "callback" execution (with argument 50f) in the main thread,

8. Don't run multiple computations at the same time.

 

UPD June 2016: The UnityBackgroundWorker class was enhanced, so now it's possible to schedule multiple computations and different modes of synchronization (in regard to blocking progress messages) are available. But I won't go into details (except you have to replace Logger.Say(int, message); with Debug.LogWarning(message);), you can look at the recent source code here: paste.ubuntu.com/1...[6]

 

Link 1: http://docs.unity3d.com/Manual/Coroutines.html

Link 2: https://msdn.microsoft.com/en-us/library/aa645740(v=vs.71).aspx

Link 3: https://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

Link 4: http://docs.unity3d.com/Manual/webgl-performance.html

Link 5: https://goo.gl/itaqKf

Link 6: http://paste.ubuntu.com/15943190/

This article in social networks: