Greetings!
I’ve been implementing a second thread of execution in my current game project for the last few days. I had tried to do this in my previous set of code, but either had constant crashes or half the game not working because that second thread wasn’t ticking over. At that time there was almost no instruction online as to how threads worked in NME, but that has changed recently thanks to the (always excellent) Joshua Granick, whose blog post, “Using Threads with NME“, lists out some basic scenarios for communication between threads. There’s also some useful nuggets of information in this forum thread.
In my new set of core code I was able to avoid a lot of the problems that plagued my first attempt at threading — albeit accidently. After my first experience I had no interest in adding threads initially, it was only after reading those sources that I decided to try it out again, and it’s worked really well. So, for any one interested in how I’m implementing a second thread I thought I’d outline my structure.
My game engine (it feels rather grandious to call it that, but it’s the best description I’ve got) basically consists of several core “Manager” classes, and a load of interfaces/asbtract classes that the Managers use. I class Box2D’s B2World as my “collisionManager” for example, with others including “AnimManager”, “EventManager”, “SceneManager” etc. Nearly every Manager has an update method which must be called every frame. This makes dividing processing between threads fairly simple: I can just push some of the Managers update() calls in to the second thread and be done with it! If only threading were that simple…
The main rule of threading in NME is that the DisplayList is untouchable by anything but the main thread. You cannot addChild/removeChild an object if it is being displayed on screen. Similarly you cannot redraw any on screen object from the second thread, because eventually you try to redraw at the same instant the main thread is trying to draw to the screen, and your app crashes. So in deciding which Managers to move to the second thread the only stipulation is that they can’t draw anything or alter the DisplayList during their update.
First of all, let’s discuss the basic implementation. I have a class called ManagerThread which contains only a few basic functions to start the execution and send messages (we’ll discuss that later,) with the primary function being run(). A reference to the main thread is passed in in the constructor and stored (again, to facilitate messaging,) and the new cpp.vm.Thread object is created and starts executing run(), which contains a dumb while loop, and uses Sys.sleep to lock the thread to ~60fps.
private static var mainThread:Thread; private static var thisThread:Thread; private static inline var TARGET_INTERVAL:Float = 1000 / 60;public function new(mainT:Thread) {mainThread = mainT; thisThread = Thread.create(run);} public function run():Void { var prevTime:Int = 0; var step:Int; var ftimer:Float; while (true) { step = Lib.getTimer() - prevTime; if (!Global.paused) { if (Global.collisionMgr != null) { Global.collisionMgr.step(step, 2, 2); } Global.objectMgr.update(step); Global.animMgr.update(step); } prevTime += step; ftimer = (TARGET_INTERVAL - (Lib.getTimer() - prevTime)); prevTime = Lib.getTimer(); if (ftimer > 0) Sys.sleep( ftimer / 1000 ); } }
So now the second thread is spinning, what do we do about those rules? The objectMgr.update() call in particular can do just about anything, and at the time when I was implementing this it was running all my sprite animations and therefore, redrawing a whole lot. This caused problems: The game ran, but after a while of having an animated object on screen it would crash out completely, which isn’t brilliant.
I didn’t want to waste time engineering some perfect solution; with the second thread working so well otherwise I was desperate to get the game stable. My first thought was to try using inter-thread messaging, available from ThreadInstance.sendMessage(msg:Dynamic), and this proved reliable. To keep the messaging simple and clean (rather than just passing any old thing around in messages, which seems so very wrong) I created an Enum which holds definitions for a number of tasks, and allows you to pass variables as parameters.
enum ThreadTalk { UNLOAD_OBJ(pid:Int); ADD_OBJ(oid:Int, target:nme.display.Sprite); ADD_OBJ_AT(oid:Int, target:nme.display.Sprite, index:Int); CALL_FUNCTION(f:Void->Void); UPDATE_SPRITESHEET_DRAW(oid:Int); }
Then, in the main threads update() function:
private function update(e:Event):Void { var msg:ThreadTalk; while ((msg = Thread.readMessage(false)) != null) executeThreadMessage(msg); } public static function executeThreadMessage(msg:ThreadTalk) { switch(msg) { case ThreadTalk.ADD_OBJ(id, tgt): tgt.addChild(Global.objectMgr.get(id)); case ThreadTalk.ADD_OBJ_AT(oid, target, index): target.addChildAt(Global.objectMgr.get(oid),index); // etc ...
As you can see, all my sprite animations are done by passing redraw messages back to the main thread, as well as adding and removing any objects that are spawned in the second thread (projectiles from weapons, mainly). There are still more problems to solve though.
I lied earlier, ManagerThread contains another function… Namely doThreadSafeUnload(objID:Int). This method is necessary to resolve the inconsistent states that arise when you’re waiting for the other thread to remove an object, but that second thread keeps ticking on and updating using this invalid data. This function aims to remove the object from the core Managers before passing the message to the main thread to remove it from the DisplayList.
The nice thing about this structure is that it’s super-easy to run the game single threaded (or in a non CPP target, such as Flash) with a few compiler conditionals: Wrap all the multithreaded code within ManagerThread in “#if cpp // #else”, duplicate the methods and link them directly to your executeThreadMessage function (or in the case of threadSafeUnload, to the object’s unload() function, if your implementation permits) and finish it all off with a “#end”. My “Flash-safe” single threaded version of ManagerThread looks like this:
public function new() { } public static function queueInstruction(t:ThreadTalk) { Global.executeThreadMessage(t); } public static function doThreadSafeUnload(id:Int) { Global.objectMgr.get(id).unload(); }
…and boom, multi threading in HaXe NME! No doubt this isn’t the best or even a particularly good way to handle it, but it seems to be working pretty well for me.
I put together a very quick performance test which just puts a whole load of my animated sprites on screen (the animation is handled by thread 2) to demonstrate the effectiveness of a second thread. In the shot below you see a Windows build with 70 sprites on screen, with the main thread showing an ideal 60fps and the second thread running just 10 frames below normal. I also added a quick counter to show how many messages the main thread is receiving every second (well, every 60 frames, so give or take a little), and you can see it’s clocking up about 20 requests per frame.