Friday Facts #364 - 1.1 stable

Posted by kovarex, Klonan, boskid, Rseding on 2021-01-29

1.1 stable kovarex

Hello, we have a stable version!

When we were releasing the 1.0 FFF-360, we actually stated that there were "around 150 bugs on the forums and around 80 internal tasks to be solved". These were obviously minor issues, things hard to reproduce or very rare problems. In other words, it was quite reasonably stable, which normally goes without saying when it comes to Factorio stable versions. But it proved to be a mistake wording it this way, since some media picked up on it and presented it as a "fairly bugged release".

So I'm pretty thrilled to finally get to the point, where we actually have 0 known issues and 0 active bug reports on the forums. Its like cleaning the kitchen properly, so you can start cooking something fresh. More about that next week!

For now, we want to go over some of the features of the 1.1 that you might have missed until now if you've been sticking with stable 1.0.


Blueprint flip Klonan

It was requested a lot over the years, and we always said no. The reason being, well not everything can flip, things like oil recipes, rail signals, pumpjacks etc. Due to these asymmetries, if you flipped a blueprint with these entities, the result would be non-functional in the best case, and cause complete chaos in others.

But then kovarex was playing and really really wanted to just copy and flip his train unloading setup (just your typical Inserters, chests, and belts). So he decided to just add it, and prevented the flip problems by just disallowing flipping blueprints that wouldn't flip properly.


Spidertron control Klonan

We added a few last minute nice to have features to the Spiders for 1.1. This is in addition to the things we demonstrated in FFF-362.

Spider waypoints

It was super frustrating trying to navigate our quite watery playtesting map with the spiders. They would always get caught on the edge of some lake. Queuing move commands seemed like a pretty obvious fix, and it wasn't much work in the end.

Spider follow command Klonan

Another frustration was trying to control multiple spiders in combat. The remote works fine for a handful of spiders, but once you get into the double digits, managing the spider remotes and spiders was just a hassle.

So alongside the waypoints, kovarex added the feature of letting spiders follow entities... and even other spiders...


Smart belt building Klonan

With the Lane locked belt building added in 1.1.0, there was some wonky/undefined behavior related to rotating the belt while building.

So we fixed the bug by extending the feature set of belt building, with the new 'Smart belt building'.

And with a great feature, we give ourselves the oppourtunity to do a little feature creep. So we added the anti-frustration feature of the automatic underground belt traversal.


Multithreaded belts boskid

In the constant battle with the optimizations to let players build bigger bases, quite often we do small changes that give performance gains in the range of 1-2%. Recently I started looking into the possibility of multithreading the transport belt logic. This is one of the more significant performance drains and it is still being done single threaded, resulting in a huge potential performance gain.

Transport belts were already quite optimized (FFF-176). One could simply say "make it multithreaded" but there are a lot of technical challenges to solve before this is even possible in order to not cause any issues when running in multiplayer. Basically, to ensure it will still be deterministic.

Fig. 1

Trying to do naive multi-threading with all the transport lines being updated at the same time doesn't work. In Fig. 1: depending on which transport line would be updated first, the copper plate could land before or after the iron plate. This could be fixed by using all sorts of synchronization primitives, but using them would increase the complexity of the code to the point where nobody would want to maintain it, and mainly it would not guarantee the code would be faster or would not desync.

Transport line groups

After some observations I noticed a trivial fact: not all transport lines can interact with each other.

Fig. 2

In Fig. 2, the left transport line on the horizontal belts can never receive items from the right line of the horizontal belts or any items from the side-loading. This gave me a simple nice idea. We can group transport lines in a way, that lines from one group don't interact with any line from any other group. This means, we can avoid all the synchronization logic between transport lines. When one thread updates one group, other groups can be updated by other threads independently at the same time.

Each color is a different group of transport lines. Each group is updated in parallel.

Maintaining those groups in itself was a challenge: when a player builds or rotates a transport belt, underground belt, or splitter, transport lines will change their connections and some groups may now be connected, and they need to be merged. The opposite is also true: the player is able to remove some connections making the group contain multiple components which can be extracted to separate groups. While groups merging has to be done immediately (having multiple groups being able to interact with each other would create race conditions), splitting does not.

Wake-up lists

By far the largest issue we faced was that the transport lines aren't entirely independent. We have a sleep/wakeup mechanism in the game since version 0.9 and it goes like this: Inserters taking from belts may become inactive when there is nothing happening on both transport lines to save UPS. An inactive inserter would be stuck on its own in that position if there would not be any way to wake it up. In this case, the Inserter registers on the transport lines so that when a new item comes to it, it can be woken up to check if it needs to pick up any items.

Since the 2 transport lines going through a single belt can belong to different groups, those groups are not independent because both could try to wakeup/put to sleep the same inserter. In that case, the inserter becomes the shared state for both groups and has to be properly dealt with. Transport lines cannot simply wakeup that inserter, because it could be woken up by another thread, and activation order is important as it also defines the order in which Inserters will be updated. If Inserters would activate in a different order, desyncs would happen.

To solve this, when working, the threads don't wake the entities immediately, but instead add the wakeup requests to a list to be processed later. After all threads are done, the main thread collects and merges all those requests, and wakes up the entities in a deterministic way based on the group update order number.

With the wakeup lists and other similar cases handled properly, I started comparing some belt based megabases which are capable of producing 10k science per minute and I noticed that transport belt update times dropped from 4ms to 1.6ms which in total update time gives between 20 to 40% overall performance gain.


New Train Overview GUI Klonan

The beginning

The original Trains GUI was added in 0.13, and only had cosmetic changes all the way to 1.1.0.

Trains GUI in 0.13 (First release).

0.17

1.1.0

The basic design is clear:

  • It is a list of all Trains.
  • You can see the schedule.
  • You can click the button to open the Train.
  • You can search by schedule.

At the time it was a big step forward, as there was no other good way of interacting with trains. For instance you couldn't open them from the map view, there wasn't even zoom to world, and the Locomotive GUI was very limited.

During the old days, the mindset was, that the players would have just a few trains or 10-20 max in an extreme case, so we didn't really see a need to categorize them much.

The problem

With time, the game evolved, factories were growing, and subsequently the number of train kept going up. The insufficiency of the GUI became very clear during our internal 1.1 playtesting. We played for a whole week and extensively used the new train limits. We had over 100 trains and over 200 train stops.

Once we finished I asked the rest of the team "Did anybody opened the Trains GUI even once?". The answer was "No".

While playtesting I also became highly familiar with what features I wished the Trains GUI had, and what questions it should answer:

  • How many trains do we have?
  • How many stops do we have?
  • What is the cumulative train limit of all the stops of a certain name?
  • Am I keeping up with the demand for the given item?
  • And mainly, do I have enough trains for this route?

With all this in mind, I got to work on the new Trains GUI.

Our playtesting map after 40 hours.

The new Trains GUI

I started by making a mockup mod in Lua. It allowed me to quickly iterate on the design and content on the GUI much faster than C++ would have allowed. After 2 days I basically had a finalised design ready. After we reviewed the mockups, we agreed it was a massive improvement, and decided to make it properly. I took it as 'Christmas homework' to write the new Train GUI into the engine in C++ (even though programming isn't much my area of expertise). After the New year, it was ready, so we did the usual QA and released it in 1.1.8.

There are 2 tabs of the new Trains GUI, and each follows a simple but meaningful design principle:

  • Trains tab: A list of Trains categorised by their Schedule.
  • Stations tab: A list of Train stops categories by their name.
...and honestly from there it just kinda all fell into place. But let me explain some of the details...

Trains tab

I would say the most controversial part here is removing the Schedule display under each Train map, and replacing it with the 'State description button'. With the schedule displayed on the side, it is almost completely redundant information. The state button provides much more precise and relevant information, you can see which stop it is going to, how far away it is, and also click on the state button to open the map at the specific train stop it is going to.

The nice thing is, that whenever you have some wrong or inconsistent schedule, you can easily notice it in this list.

Stations tab

Since the stations tab is completely new, there are no changes that people can disagree with it. The aim with this tab was to try to present the information about the train limit of each station in some easy to digest way.

The remark in the form of <Reservations>/<Train limit> describes the station state. When I look at the Dropoff+Pickup station of a single product, I can easily identify 3 basic states:

  • Not enough trains: when the sum of <Reservations> is too low compared to the sum of the <Train limits>.
  • Not enough input: when almost all of the trains are in the Pickup waiting to be loaded.
  • Backed up: when <Reservation>=<Train limit> in the Dropoff where the trains are waiting to be unloaded.


Save game speed Rseding

There are many things in Factorio I've optimized over the years; runtime performance, startup time, saving, loading, even quit speed. It gets more and more difficult to find anything to improve on without changing game features – which mostly I try to avoid. Autosave time has always been one of those "I wish it could be faster" but never finding anything that stuck out or gave any measurable improvements.

Several years ago I parallelized the iterate-the-game and the compress-and-save-to-disk (FFF-201) however there hasn't been any major improvements since then. The issue I would always run into was: I could make one of those two processes faster but the other always bottlenecked and so the overall speed didn’t change. Every few months I would go back to it and try to think of new ways to make it faster but always ran into that same issue.

Try it and see

The other week I was doing that again and I wondered just how much faster the save process would be if I could just 'pretend' the compress and write to disk didn't happen. It turned out; a lot. The larger the save got the more time got spent waiting for the compress and write to disk. After thinking for a while I decided to instead of save the map information in one big compressed file, to split it into multiple compressed files. It's not a new concept but we hadn't done it before in Factorio. A quick 30 minute implementation later showed that without a lot of changes – it just worked.

The end result meant the compress-and-write-to-disk parallel processing never blocked the main iterate-the-game logic. That alone made the entire save process around 20% faster and as a side result any improvements I made to the main logic gave direct further improvements. In the testing I performed the larger saves saw upwards of 2x improvements to save times.


Thank you for tuning in, let us know what you think at the usual places.