Project at Unity: DOTS Tornado simulation
During my time at unity, before Covid hit, we were given a week of training in the new paradigm meant to improve performance, networking, and determinism in the Unity game engine: DOTS. DOTS stands for Data Oriented Tech Stack, and it is a combination of the following things:
ECS: Entity Component System. This is the separation of game code into Components, which is data that is stored linearly in memory in order to get the performance improvements from caching, Systems, which is code that operates on those components in a linear fashion, and Entities, which are IDs that do not store any data, but is linked to the components. The operation on data using this paradigm drastically speeds up execution especially on huge numbers of objects, but it can be tricky to get into the mindset of designing your code around them, as the way ECS works is a bit complicated.
Mathematics: a deterministic math library that works with Burst (more on that later).
Jobs: Unity's system for multithreading. You can schedule and launch jobs which will run the systems from your ECS, and they will multithread themselves optimally.
Burst compilation: a system that allows you to compile C# functions, with some limitations, to highly optimized native code for a huge increase in speed.
All of these systems require you to rethink how you write code, and our task was to take code written in traditional paradigms and refactor it (or possibly reprogram it completely) to use these paradigms and increase in performance. After our 2 days of lectures we had 3 days to do as much as we could to get feature completeness (something most groups did not get because of the feature complexity of the projects we were supposed to translate).
Our group chose the tornado physics simulation for the level of challenge it presented and how generally cool it is.
This is the project we were given to start with - pre-DOTS, this project did not scale well. Here's what happens when you increase the number of objects by a factor of 10:
There were a lot of things we had to implement: the generations of the towers, the collision physics of the bars with the ground, the physics force of the tornado on the bars, and the bars connection to each other + the ability to break under enough force. Some of the math we were able to keep, but the physics keeping the bars together was a linear operation and could not be multithreaded. Due to that and the ways the bars were stored not directly converting easily to ECS, we had to rewrite the physics to an extent.
We also replaced the tornado particles with sharks because why not.
We had 3 days to do this but our team thrived in a high stress environment, and running right up into the end of the 3rd day, we got all the systems working to an extent. The buildings were a bit floppy due to the new physics; I went back to the project post-training and added an additional physics iteration, because it turns out with a huge amount of objects we were actually being bottlenecked by the draw calls (which was because of the renderer we were provided with). Because those jobs ran parallel to the CPU's rendering preparations, we could schedule extra physics iterations with almost no affect on performance.
In the end the final biggest challenge was the connections and physics keeping the bars together and structured into towers; due to the interdependence we had to do this by storing references to other entities, which is not ideal because accessing their data resulted in cache misses. However, with DOTS it still wound up being incredibly faster than the original regardless. In the end we unfortunately did not have time to fully optimize the actual generation of the towers (since the system that connects them is an N^2 algorithm, unfortunately it does not scale well with the number of objects.) However, that step is still significantly faster in the new version, and after that, the amount of objects the program can handle at a consistent framerate is drastically improved over the original.
Here was our final result: