Oops! I originally called this article “Awake() and Sleep() differences.” It is actually about “Awake() and Start() differences.” 🙂 My bad.
Currently hacking my way through the new Unity3D game engine and encountered a problem I hadn’t run in to before.
For several days the game I am working on has been loading up just fine, no significant code changes in the parts of the code that was causing the problem.
Here are the events as I remember them. For the past four or five days I have had the Unity3D editor open constantly, never closed it down. The game runs fine. Last night I decide to play some quick games of Left 4 Dead at the office on the LAN. I started to suffer odd glitches with my network when running my workstation as the game server so decided to shut down all non-essential applications (everything except for L4D) to try and isolate the issue. This morning when I start up Unity 3D, my game is crashing for no apparent reason, and not only that, but bringing down the whole Unity 3D editor along with it.
Throwing in a few try/catch exception handlers around various bits of code I isolate it to some multi-threaded code in my game. This is the cause of the Unity 3D editor crash and is a well known issue. If you throw an exception on a thread other than the main thread, it will also break the Unity 3D application.
No big deal, put in some more try/catch handling on the multi-threaded part of the code and see what’s going on.
What was happening is that the other thread is attempting to access GameObjects that have not yet been instantiated. Which is weird because the day before I restarted the Unity editor, it all worked fine.
This the tricky part about Unity 3D. The Awake and Start functions are called in a “random order.” Awake acts like the constructor for the GameObject and attached scripts, and Start is called just at the point when a GameObject is about to become alive. Awake is always guaranteed to be called before Start, but only on a particular GameObject. You could have Awake invoked on object 2, then Start invoked on object 1 that was previously instantiated, and then Awake on object 3, and then Start on object 2, and finally Start on object 3. The order of Awake and Start for each object is guaranteed, but not for a group of objects.
If I have a GameObject and script that instantiates a whole bunch of other GameObjects in the Start function, when I instantiate those GameObjects, their Awake functions are called, but their Start functions may not be. When the Start functions are called on the newly created GameObjects is entirely up in the air.
Now enter the multi-threaded portion of the code. The newly created GameObjects exist, Awake has been called on each one of them, but each and every one of them may or may not have had their Start functions invoked. I got lucky before, everything ran correctly. But when I opened up the Unity 3D editor again, the order of execution I was relying on, and made an assumption about, had changed.
The multi-threaded code was relying on a piece of information that only got created inside of the Start function of the GameObject. But the Start function had not yet been called. Moving the part of the code that looks up a value from a table and stores it in the GameObject instance from the Start function to Awake function fixed the problem. A classic race-hazard problem.
This isn’t specifically about multi-threaded code, it is about the difference between Awake and Start. I had confused the two functions in how they worked. I imagined, and I do not know why, that Start was called immediately after Awake, not at some arbitrary point in the future that is unknowable.
This problem could easily be replicated if I had attempted, immediately after instantiating my objects, I had attempted in the same function to read off a value from the newly created object, which would not be set up until the Start function is invoked.
Lesson reinforced: Awake is called immediately upon instantiation of a GameObject, just like it says in the Unity 3D documentation, it is the constructor as far as Unity is concerned. Start is called whenever Unity 3D damn well feels like, in a random order (I suspect the underlying mechanism is a list of GameObjects that need to be started or a tree that emulates the scene hierarchy) that is not guaranteed from one execution of the game to the next.
I just got lucky for a week (should have bought a lottery ticket and raided in WoW for some sweet loot drops if I was that lucky) but then it all came crashing down when I restarted Unity 3D.