Friday Facts #326 - Particle emitter & Data cache

Posted by Allaizn, Rseding, Klonan on 2019-12-20

More particle optimisations Allaizn

Rseding's recent optimisations of the particle system (FFF-322) made particles much more lightweight than they were before, but it still left particles as rather complex beasts. A quick summary of the possible actions a particle can make during it's update:

  • Move their own position.
  • Advance their animation to another frame.
  • Land in water and apply a trigger.
  • Apply another trigger with a certain frequency.
  • Remove themselves from the game world once their life time ends.
What makes this complex is that triggers are general purpose systems that can do all kinds of things, including creating and destroying entities, fire, smoke and other particles as well as playing sounds or recursively applying even more triggers. In other words: applying a trigger is an "anything can happen" situation and thus totally unpredictable, which in turn makes optimisations extremely hard.

The particle emitter

The base game and most mods don't use particularly crazy triggers when creating particles - the goal is usually to just spawn in a bunch of small animated textures and make them fly around on screen (which is somewhat ironically what is usually called "particle"). An idea for further optimisations of particles was hence to create a kind of "simple" particle, which couldn't apply all kinds of triggers to allow handling them in bulk, which is usually faster than handling them individually. This bulk handling would be done by a thing called a "particle emitter", whose whole job is to create, update, draw and finally destroy the simple particles it manages, with the idea being that a biter dying wouldn't have to spawn hundreds of particles, but only a single/few emitters.

But this is not all: simple particles are not able to change any other game state, and would thus only get updated to maintain their own internal values - mainly their position and velocity. A small physics exercise later the idea was born to not update the particles at all - you can compute their current position from their starting one after all! Even better: if the particles aren't ever rendered, then there's no point in creating them in the first place, so there's no reason to do that until the emitter comes into draw distance - millions of biters dying in gigantic blood fountains offscreen would thus basically not matter at all for your frame and update time!

A visualisation of the emitter in action: the red box represents the actual screen.
Particles managed by emitters outside the screen region simply don't exist at all.

The particles themselves not being allowed to affect gamestate has another benefit: in a multiplayer game, each player only has to generate the particles they see themselves, instead of those that are visible by anyone. This also suggests not using the emitter's update function, but it's draw one instead, which yields even more benefits due to the draw function being called during the render prepare phase, which runs on as many threads as you allow it to have.

However, all of this doesn't just magically work correctly, and there are edge cases that need handling. For example: what happens if an emitter is created offscreen and then comes into view distance? What happens if you save and reload? What happens if you save and reload with a mod set that doesn't have the particles defined any more? It would be very odd to see your rocket silo explode in uncountable bits, see how they fly and crash into the ground - then save and reload and see everything again because the particle effect restarted.

Handling these kinds of issues took some time and thankfully only increased the systems internal complexity marginally, allowing me to focus on expanding it's features. Currently, the following things are supported to be present on an emitter:

  • Handling simple particles with individual random starting positions and velocities.
  • Handling simple particle streams via normal and instant tails as shown in FFF-325.
  • Handling simple particles with a smoke trail behind them (FFF-325 has some examples of this, but the effect already existed beforehand).
  • Handling simple particles impacting the ground by potentially being replaced with a water splash when hitting water.
Particle emitters have two main restrictions:
  • They only handle a single particle type (and technically associated smoke and water splashes). Making an assembling machine burst into metallic blobs and oil splashes would thus require two emitters.
  • The particles managed by an emitter cannot fly too far away from the emitter (which itself will never move), because we need to know how far outside the draw distance to search for emitters that may want to render their particles.

A demo particle animation showing off all effects at once - all of these are managed by a single emitter.

Startup time - Data Cache Rseding

Game startup time (time to reach the main menu) is just as important to us as compile time (see FFF-206). With how frequently we compile and launch the game to test things, every extra bit of time spent waiting for the game to load is wasted time.

There are 2 main parts of the Factorio startup process:

  1. Go over each enabled mod and collect the prototype data it defines/generates (the 'data stage').
  2. Load and process the sprites that the game needs to run.

This is a familiar sight to those who play with a lot of mods.

In the past we made an experimental setting which would cache the loading and processing of the sprites, so we never need to wait for it when nothing around them has changed. However, the game still had the process all of the 'data stage' every time the game would start.

During normal development that wasn't really an issue - it would happen in a fraction of a second in most cases. However, as the game has grown, so has the amount of stuff that gets processed during the data stage. Additionally, for every mod enabled that has anything in this stage, the time would roughly double. Recently I started to wonder what it would take to make the same kind of caching system we have for the sprite loading for the data stage.

Since mostly the results are the same between restarts, it would mean it didn't need to do most of the work - and should be faster. After working on it for about day I had a working prototype; but it wasn't actually any faster with just the base game. Not wanting to quit just yet I spent some time with the profiler and managed to find a few areas that I could optimize and reduced the time the caching logic was spending by about half. So, it finally had some benefit for the base game (although quite small).

What I didn't expect was just how much of an improvement it was going to have for the modded case. What used to take 25 seconds in my testing took only 4 with the new cache setting enabled. The time savings gets even better as the number of enabled mods increases. The setting is still disabled by default because it's highly experimental, but if it ends up stable enough, we might turn it on by default.

Christmas mods Klonan

This is the last FFF before Christmas, so I thought we would celebrate some of the mods which aim to create some holiday spirit in the game.

Alien biomes

Alien biomes snowy terrain is just beautiful. In the map gen settings you can crank up the 'Cold Climates' option, so your whole world is just a cozy winter wonderland.

Holiday artillery

We must also remember to share the love to our biter friends, this mod will lets you deliver gifts far and wide, and embellishes the Artillery Gift delivery turret/wagon with a lovely red and green paint job.

Christmas tree

And of course, no winter factory is complete without a lovely Christmas tree.

We wish you a merry Christmas, and as always, let us know what you think on our forum.