The Lost Archives #3: Technical Tuesday - Midweek Madness
The basic run-down #
This week has been quite hectic in terms of features. We started on the fire system mid last week and it has already been rewritten entirely. Originally it was quite simple. We had a master script called “Fire Manager” which managed all the data of fires across the map. This Fire Manager class would then spawn a fire object and also add it to a Dictionary called checkFireDictionary. It then looped over all the fire objects in the dictionary and ran their logic.
In our system, and this still remains even after the rewrite, fire spreads by checking its surroundings. It can only do this when it has reached a certain size. After reaching this size it looks at the tiles around it and first checks whether the tile is a valid tile. A valid tile here is a tile not empty, so it actually exists, and has a flammability greater than 0. If this check passes, then it makes a random check against the flammability to dictate whether it should actually spread to the block or not. This means that when a fire spreads over tiles with low flammability, it spreads slower as the check fails more often. Lastly fires damage to tiles they inhabit so eventually those tiles break, leaving rubble and a slowly dwindling flame. This is the basics of the fire system.
OPTIMISE, OPTIMISE, OPTIMISE! #
The problem was that it was incredibly slow. There was a multitude of nested for loops which did not scale in a fast manner. After a while it was evident that we needed an alternate solution which would be more optimal. Dillan, my colleague, suggested we could store the flame objects on the outer edge of a flame in a list and only iterate through that. I doubted if that solution was the right one though as flames needed to die out eventually so we still need to keep track of flames within the meat of the flame.
This is where I decided to multithread the fire. I have never had experience with multithreading or coding it, so it seemed quite daunting at first. Multithreaded code can be quite a hassle to manage, and there can be many bugs which are not simple to debug. However, Unity’s Job system came in and saved the day. Unity’s job system as described by Unity themselves is a system that “allows users to write multithreaded code that interacts well with the rest of Unity and makes it easier to write correct code”. Using the job system and a great tutorial by infallible code (they are a great channel you should definitely check them out), I was able to take a crack at multithreading the fire system.
This required a heavy rewrite of the entire system. The original system was quite component based, but with the job system, everything needs to be data based since you can’t pass strongly typed classes or unmanaged data types to a job. Firstly I had to create a struct inside the FireObject class to get around the whole “no strongly typed classes” rule. In this struct (called Data), I had to migrate all the fire logic from a function in the main Fire Manager class into the struct. This required a lot of changes to the references as the code used to be in a for loop iterating over a dictionary, and now it was inside a struct in a class, only concerned with itself.
I also had to create the job and job handler etc etc. By the end of it I had all the infrastructure I needed. Most of the code was still the same, but the code around it; the code holding it up changed drastically. It wasn’t all done though. This was merely the easy part. By far the hardest part was debugging. For one, I forgot to consider the second rule of the job system, no passing of unmanaged data types! Here I was sitting at my desk reading through the documentation and dozens of forum posts trying to figure out why the job wouldn’t start. I even commented out all the variables in the class. I however forgot to comment them out of the constructor of the class, which was where the errors kept persisting. I eventually figured this out and cracked down on all the bad data types that I was passing into the Fire object class. Some of them required more intensive workarounds than others but it got all done after a few hours.
Next was the problem of certain functions only being able to run in the main thread, which required even more work arounds. All in all, I ended up with a few extra static lists which stored all the data that I couldn’t reference inside the job, and some of those lists acted as a buffer, needed when creating and destroying buffers.
When analysing performance, the multithread system nearly triples performance. There are a few bugs here and there which need fixing, and the system overall needs some tweaking, but so far the effort in implementing a multithreaded system has paid off in many ways, and the knowledge gained is irreplaceable.
Once the bugs are sorted, I am off to implement the multiple floors. It has already been 80% implemented, but as you can see, flames have taken over my life, at least for a week or so. Nothing can beat figuring out the multithreading at 3am in the morning.
Until Next time. That’s me signing off,