Summary
I’m pretty happy with this as a general-purpose threading technique in Unity Projects. Using Enumerators in this way helps a lot with thread management, and while this isn’t the same application I was looking at before, I’m probably going to go back and look at having the flowfield threads use a similar enumerator system when syncing with the main loop.
Complete ThreadWorker Class
I’ve uploaded the ThreadWorker Class to Github. It’s similar to what is presented here, with a few extra features:
- The ThreadLoop catches exceptions and forwards them to Unity’s Log, otherwise exceptions in the threads will end the thread silently.
- Pausing the simulation in Editor will pause the threads as well.
Implementing Pause, Resume, Abort
In our case, we wanted to be able pause threads whenever we go into a level and resume when returning to the level select menu, so the our Island Generator has calls to thread pause and resume hooked into its OnDisable()
and OnEnable()
MonoBehaviour functions, so when we deactivate the GameObject, all the threads get paused. Abort is triggered from OnDestroy()
.
Aborting Several Threads with Blocking
The current system I’m using for abort blocking is encapsulated, so if you want to abort a number of threads, you will need to abort and wait for each one in turn. It might be preferable to call Abort(false)
on all threads and then use WaitHandle.WaitAll()
to cause less blocking.
Running your Enumerators outside of Threads
The main reason I stumbled upon the idea of using Enumerators in threads is that Oskar had already developed the generation algorithms in enumerators and was running them in coroutines. One interesting thing that he was doing was wrapping the algorithm in another Enumerator that controlled how much time the coroutine can run for, per coroutine update, using something like the following:
IEnumerator TimedEnumerator(IEnumerator enumerator, float maxTime) { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); stopwatch.Start(); while (enumerator.MoveNext()) { if(stopwatch.ElapsedMilliseconds > maxTime) { yield return null; stopwatch.Reset(); stopwatch.Start(); } } }
Which will continuously run your Enumerator for around maxTime
per coroutine update (actually until the first yield after maxTime). It should be clear that this is entirely compatible with our threaded enumerator, and means that we can easily pull our thread implementation into the main thread, without it blocking for several seconds. This is useful for debug visualization with Gizmos and profiling (which Unity does not support outside of the main thread).