Wait, what am I doing?
Published on Monday, 23. May 2022Last week, I built a voxel-based sand simulation. I also wrote a post about why this wasn't a great topic for my blog. While editing said post, I felt myself disagreeing with what I said in there. So this week, I set out to write a devlog post in which I wanted to address some of the simulation's performance problems and use it as an opportunity to explain entity component systems and compare them to the more traditional, object-oriented style of programming.
In terms of performance, there are two glaring issues with the simulation. First, the core loop that simulates the voxels runs for every voxel, even the ones that are already on the ground and no longer active. Second, each voxel uses a separate object that is rendered. For large hills, this is a waste of resources. The traditional approach to address the latter is to subdivide the world into chunks and to dynamically generate one mesh per chunk. This reduces the number of objects that need to be rendered. Doing this seemed like more effort than I wanted to spend on this, so I came up with another approach. I wanted to see how far I can go if I just remove all rendering components from voxels that are surrounded by other voxels. This means I had two ideas to improve the performance. Implementing them, however, came with some challenges.
Bevy, the game engine I wrote the simulation with, uses an entity component system (ECS). The central design philosophy behind an ECS is that you capture all the data for your scene objects as components and model your entities by combining all the components it needs. The actual game logic is then implemented using systems. For each system you define what components it requires. Then, the system is run for all entities that have these components. To disable the system for any voxel that is no longer simulated, I added a Simulated
component to the system requirements that I used for the simulation. Next, I added some logic to detect when a voxel finished simulation. The simulation for a voxel is finished when it either reaches the ground (i.e. its y-coordinate is 0) or the simulation for every voxel below it is also finished. Unfortunately, checking if an entity has a component currently isn't (easily) possible in Bevy, which means I ended up manually tracking which voxels were deactivated. And even then, finding this formulation took me a few tries and approaches that didn't work. Optimising the rendering went similar. I just added a new system to remove all render components for a voxel that was surrounded by other voxels. This first attempt didn't work. It didn't work because some voxels are only invisible temporarily. Say we have a voxel A, and a voxel T falls on top and a voxel L to the left of it. Now voxel A is invisible and the render component gets removed. But both L and T are still simulated. And in the next simulation step L falls further down, making place for T to fall down as well and revealing A again, now an empty space.
At this point, I took a step back. Fixing this problem is easy enough, and would only require a few more lookups to check the voxel simulation state. But at this point I had been working on this optimisation for several hours. And I wasn't even sure it would help. The real solution to optimise a scene would be to create a goal (e.g. render 1000 voxels with 30 fps in the WebAssembly build) and profile the build, looking for bottlenecks to resolve. Profiling this scene would have been the first time for me to do something like this, and it was a rabbithole I just didn't want to get into this week. So I looked for shortcuts, with the only effect that I spend more time than I wanted on solving problems that might not even help.
Looking at my buggy "fix" of the rendering gave me enough pause to think. The truth is, I don't really care about voxels or writing a more sophisticated voxel simulation. The project was a fun start to get back into 3D programming, and showed me how much I've missed it, but it's not what I want to work on.
And if I'm not doing it right, I shouldn't do it at all.
On the upside, this means, I might start to explore other game ideas I find more interesting. So stay tuned for that.