Friday Facts #322 - New Particle system

Posted by Klonan, Rseding on 2019-11-22

Release plans Klonan

This week we released version 0.17.79, and marked it stable. Internally we have been calling this 'Stable 3', and the main feature was the new tooltips we showed in FFF-318.

There is one constraint we put on ourselves when we started this more swift feature release schedule: We want to avoid breaking mods. This is easy enough in principle, don't start renaming things, don't remove API features, etc. However as we develop further, there are certain features and improvements that we can't realistically do in a way that won't break mods, such as the new Character GUI (FFF-289), and color correction (FFF-320).

It is for this reason that we are going to accumulate some of these mod breaking changes, and release them all at once. Since it will definitely be breaking mods, we will bump the major version number, so it will be 0.18.0.

We have already internally started merging in these 0.18 features into our master branch, so we will not be doing any more 0.17 releases (unless something absolutely catastrophic is discovered).

The problem with particles Klonan

Particles have been in the game for as long as anyone can remember, and they are pretty simple all things considered. They are a small mostly decorative entity, that we use to add a bit of visual gratuity to the death and dying of bugs and machines.

For instance, Biters are full of blood, so when they die, they make quite a splash. Lets try to count how many particles we spawn in a typical dying explosion.

In this clip, the blood particle sprite has been replaced with a debug visualisation, to make counting easier

So it's quite a few. The Biter spawned 427 particles, and the Spawner 749. Well they don't persist very long, and they're only decorative, so it's all fine right?

One key word I would like to highlight in the previous description, is that they are an entity. When kovarex and slpwnd started making Factorio, they made a robust system for managing game objects and their interactions - the entity system - and everything that has a physical representation in the game world was built on top of this system. As the game grew bigger, it became obvious, the entity system is too heavy weight for some things, and we can get better performance by creating more specialized systems. This led to removing items on belts from the entity system in 0.12, and doing the same for smoke and terrain decoratives in later versions. Despite most other games or game engines having very efficient particle systems from the early stages of development, particles in Factorio are still piggybacking off the entity system in 0.17.

This means that particles are registered on the game surface in the same generic way as everything else. This also means, that they are iterated through when doing entity searches. So what sort of engine actions do entity searches?

  • Area trigger effects - such as grenade, flamethrower stream damage, atomic bomb, poison capsule.
  • Pathfinding - To check if a path can traverse a given tile.
  • Movement collision checks - such as Characters, Units, Projectiles.
  • And many others...

As you can imagine, having thousands of extra entities to iterate through every tick, can start slowing down the simulation. The worst case these days is defending your walls with flamethrowers. In the image below, all entities are highlighted with a debug visualization.

If you lost count, this scene contains 15,689 entities

Flamethrowers vs Biters are pretty much the perfect example of the problem:
  • The biters need to check every tick when they move if they collide with anything.
  • When the flamethrower streams land, they do splash damage, which is an area trigger effect.
  • The flamethrower stream also creates the fire on the ground, which does a collision check.
  • Every 10 ticks, the Fire on the ground do damage with an area trigger effect.
  • The flamethrowers kill biters very efficiently, so a lot of particles spawn in a very short amount of time.
This combination leads to some significant slowdowns later in the game when big groups come knocking. Also, since we improved the pathfinding, the problem is even worse in the latest versions of 0.17.

So how do we solve it?

Particle Optimization - Technical Rseding

When Posila first talked to me about doing a re-work of how Particles function in the game I had a lot of ideas. Not all of them worked out in the end but they sounded nice:

  1. They wouldn't be entities.
  2. They would work like smoke does (stored in contiguous blocks of memory).
  3. They wouldn't need to be updated each tick if they didn't effect the game state.

In the end I found that it wasn't worth the added complexity to make #3 work.

The process

Particles needed to not be entities. Something being an entity has a lot of memory and performance overhead that particles just didn't need. Unfortunately when I implemented artillery I made the manual-targeting marker a particle. The reasons why don't matter any more, but it meant I had to add migrations to handle that and then migrations to handle removing all of the entity particles.

I stripped out all of the extra data/logic from particles that they no longer needed - reducing the size of each particle in memory from 224 bytes to 64 bytes. As a side effect of making them not entities it also reduces the amount of information that has to be saved in the save file. However, in most cases particles don't exist long enough to end up in the save file so that didn't really matter.

I needed some place to store/work with the particles runtime as they are created, exist for some short amount of time, and go away. Most stuff in the game ends up being stored on a given chunk (a 32x32 area of the world). In the case of particles I didn't care at all about any of the other stuff on a given chunk so I didn't want to stick them there. Instead, I made a separate thing that functions very closely to chunks and called it "particle chunk". The key differences being: they only exist when there are particles to update on them and the only data they hold are particles. That meant when the game needs to go over each particle to update them, all it has to do is go over all of the "particle chunks" that exist and run update. Additionally since I had full control of these new particle chunks I can recycle them in memory as needed to avoid spending extra time allocating and de-allocating memory as particles come and go.

The end result is a nice performance boost, reduced memory usage, and simplification of the logic around how particles work. A simple unscientific test we performed was nuking the same biter base in the old system and the new system, and recording what the max update tick time was.

The circular ring of entities around the edge are the 'Atomic bomb wave' projectiles.

Old system:

  • Max Entity update = 7ms.
  • Entity count (Excluding projectiles) = 7769

New system:

  • Max Entity update = 2.4ms.
  • Max Particle update = 1.7ms
  • Entity count (Excluding projectiles) = 786

This is not very scientific or highly controlled, but just to give an idea of the scale of the improvement. A 10x reduction in the number of created entities, and about a 40% reduction in max update time. Having the new optimized particle system also means the GFX team can go a bit more crazy with particle effects in the future...

As always, let us know what you think on our forum.