Page 1 of 1

Bag of trains tutorial

Posted: Wed Dec 04, 2024 6:23 pm
by waterBear
This is a beginner tutorial for getting a "bag of trains" setup going in vanilla. It is meant to provide a short and easy path to a working and robust implementation, not necessarily the fanciest or best implementation. People who've been playing Factorio for a long time could piece together how to do "bag of trains" from reading the FFF, but not everyone has the time or mental energy. Moreover, while the circuit details aren't hard, they weren't explicitly spelled out in the FFF articles. It is easy to get started, but also easy for a beginner to make mistakes.

Enough jabber, on to the implementation.

Notes: All trains are assumed top be 1-4 trains, meaning 1 locomotive and 4 cargo or fluid carriages.

The core idea is this: Do as little with circuits as possible and let the Factorio game engine do as much as possible. That's it.

To achieve that, we are going to let Factorio decide things like which train to dispatch and where it goes. We aren't going to think about calculating how many trains go to a stop or which train to send. First up, you need two kinds of trains for a basic setup, fluid trains and cargo trains. We are going to look at fluid trains, since cargo follows the same rules but with chests instead of storage tanks. The reason you need distinct types of trains is that fluid transportation requires different types of carriages and loading mechanisms - it's just easier to use 2 types of train.

Every fluid loading station has the same name: fluid load. Every "requester" station has a name of the form "<item symbol> unload". You need to use the item symbol in the name for the interrupts to work correctly. We'll get to that.

A short detour on how interrupts work: When a train is ready to depart its current stop, it will check for interrupts and set a temporary station for whichever it finds. That is the moment interrupts happen - not while the train is out and about or even during a stop, but only at the end of a stop.

Fluid unloading

Here is what your fluid unloading stop looks like:
Image


We need 4 combinators in total and they are hooked up in a very particular way. The left-most combinator calculates the quantity of fluid needed to fill all the tanks, stored in the variable N for "need".
Image

The next combinator calculates how many fuild trains are required to deliver the needed amount. That gets output to the variable T for "trains." Factorio, sensibly, uses C style division, so for example 5 divided by 3 is 1 since 5 = 3 * 1 + 2. Integer division returns the "quotient", or the number of times one number "goes into" the other. In the screenshot, our T variable is 0 since 11 / 12 = 0 * 12 + 11. The value of T is used to set the train limit on the stop. Why? If we don't set limits, then any train dispatched to a station of this type will just stop at the closest station of that type. We have to "close off" stations with 0 need so that trains intended for more distant stations don't go to a station that doesn't need it.
Image

Now here is where things start to get un-intuitive if this is your first time doing this. The next combinator reads the value C from the train stop, which is the number of trains that are currently stopped at the station plus the number that are on the way. We have to subtract C from T to prevent excess trains from getting dispatched to this stop. We store the value T - C in a variable named after the requested fluid. This will eventually go onto the circuit network (probably transmitted by radar) to request trains carrying the desired liquid. In this example, we're requesting molten iron.
Image

The final decider combinator is just a check to ensure that the requested number of trainloads of the desired fluid is a positive number. Under normal operation this won't go negative, but if it does for any reason (such as playing around with the circuits or train interrupts) you don't want a station transmitting a negative need, as this will quite literally cancel out positive need from other stations.
Image

Finally, these are the signals read from and sent to the stop, just for clarity.
Image

And that's it for the requester stations. Whew!

Fluid loading

What do fluid loading stations look like? Well if you have only one loading station for a given fluid, they're trivially easy. You read the fluid count from the circuit network and set the train limit to be that number. The most basic single-supplier stop looks like this:
Image

The limit on the station is set like discussed:
Image

What if you have multiple stops that provide that fluid? Well if the circuit network has the signal "2 molten iron" then every stop would set its limit to 2 trains. That will result in 2 trains being deployed to every loading station, which will obviously cause oversupply. So for multiple loading points, we use a round-robin system to assign train limits. Here is what that looks like for a molten copper station with two loading points.
Image


The constant combinator provides the signal C which is set to the number of stops of this type. In this example, we have two loading stations.
Image

The first combinator adds 1 (which is C - 1) to the value of the molten copper signal which is, you'll recall, the number of trains of molten copper being requested. In the screenshots that value is 0, since no copper is currently being requested.
Image

Finally, we divide that number by C, the number of loading stops. If 1 train is being requested, then 1 + (C - 1) = C and so C / C is 1. This stop would then have a train limit of exactly 1.
Image

For our second station, there is only one combinator.
Image

This time we don't add anything and just divide by C = 2. Why? Think: If the molten copper equals 1, then the first station has limit 1 and the second station has limit 1 / 2 = 0. If molten copper equals 2, then the first station sets the limit to (2 + 1) / 2 = 3 / 2 = 1 (remember, this is integer division) and the second station sets its limit to 2 / 2 = 1. So the two stations each have limit 1, which is correct. Imagine you have molten copper equal to 3. Then station 1 has limit equal to (3 + 1) / 2 = 2, and station 2 has limit 3 / 2 = 1, which again is correct. You can see that this is indeed working in "round robin" fashion. As the request goes up, we increase the limit on each station one at a time.
Image

If you're still following along, I'm impressed. :)

Fluid logistics train

Now let's take a look at the fluid logistics train itself. The "Temporary" station is not one that we set. It's caused by an interrupt. So this train has only one permanent stop on its schedule! That is the "fluid load" station (and remember that all of our loading stations have the same name, no matter if they provide molten copper or natural gas). All the other logic comes from the interrupts.
Image

There are those confusing interrupts! Magically, we only need 3. The first is pretty easy to understand. That causes the train to go get fuel when it runs out. The use of "all" versus "any" locomotives fully fueled makes no difference since we're using 1 locomotive per train in this example. Note that we have the "allow interrupting other interrupts" checkbox ticked, and this is the only interrupt that we do that for. This means that it takes priority over other interrupts. After all, if the train is low on fuel, refueling has to be the #1 priority.
Image

This one is also pretty easy to understand. If no loading destinations are open and the train has no cargo, it should go to the waiting station for a few seconds before checking again for an open loading station.
Image

Here is where all the magic happens. Remember that we named all of our requester stops the same way: <item> unload. The reason is that this interrupt uses a wildcard. The condition says that if an item is detected in the train, the next thing it will do is look for a stop with the name <item> unload, go there, and wait until all <item>s have been unloaded. This is an interrupt, so the train checks for the presence of an item after finishing at the current stop.
Image

Here is how a typical fluid logistics train will spend its day. You might build it at the refueling station. It will load up with fuel, then try to visit the only stop on its schedule: the fluid load stop. If none are available (meaning either the train limits are all 0 or they're all full up with trains) then the wait interrupt triggers and the train goes to the fluid wait stop. It hangs out until it gets its turn at the stop, then waits until a fluid loading station becomes available. The train goes to "fluid load" and is filled up with something - any fluid, potentially. Let's say it receives natural gas. Once filled, it looks at the interrupts to see where to go. It goes to "<natural gas> unload" until empty, then the whole thing starts over.

That's more than enough verbiage for now. If you have questions, corrections, or suggestions, please post a reply. I will try to patch this post if changes need to be made, hoping to keep it correct.

Changelog:
12/4/24 - fixed broken image links, added section titles, added picture of requester stop, minor text edits.
12/5/24 - added a note about "interrupting other interrupts".

Re: Bag of trains tutorial

Posted: Thu Dec 05, 2024 1:03 pm
by jaylawl
I just wanted to say that this is a nice tutorial. Somebody has to. :)

Re: Bag of trains tutorial

Posted: Thu Dec 05, 2024 1:46 pm
by waterBear
jaylawl wrote: Thu Dec 05, 2024 1:03 pm I just wanted to say that this is a nice tutorial. Somebody has to. :)
Thank you very much for the compliment! I hope it actually helps some people.

Re: Bag of trains tutorial

Posted: Thu Dec 05, 2024 10:35 pm
by porcupine
Reading over your example, I'm a little confused about one thing:

With "fluid load" as your fixed stop, and the "fluid wait" station interrupt set to let the train clear after 15 seconds, if a train is full of fluid, and then goes to the wait station, 15 seconds pass, and it's released... Doesn't it just return to the fluid load station, and then circle back to the wait station in perpetuity (assuming the unload station remains perpetually full say)?

I'm not sure I caught what prevented that loop, if you're releasing the trains from the wait station after an arbitrary amount of time?

Re: Bag of trains tutorial

Posted: Fri Dec 06, 2024 3:25 pm
by waterBear
porcupine wrote: Thu Dec 05, 2024 10:35 pm Reading over your example, I'm a little confused about one thing:

With "fluid load" as your fixed stop, and the "fluid wait" station interrupt set to let the train clear after 15 seconds, if a train is full of fluid, and then goes to the wait station, 15 seconds pass, and it's released... Doesn't it just return to the fluid load station, and then circle back to the wait station in perpetuity (assuming the unload station remains perpetually full say)?

I'm not sure I caught what prevented that loop, if you're releasing the trains from the wait station after an arbitrary amount of time?
Good question. What prevents it is the interrupt. Once the train is carrying any fluid at all, at the end of the loading stop, the interrupt will trigger and cause it go to <fluid type> unload next. Under normal operation, the train will never visit the wait station while carrying anything at all.

It might be a little confusing because my screenshot shows the interrupt station "fluid wait." That only gets added to the schedule temporarily, when the train has nothing in it and no loading stations are available.

Re: Bag of trains tutorial

Posted: Fri Dec 06, 2024 5:36 pm
by porcupine
It is indeed confusing, as I don't see anything in the logic that prevents that loop from occurring if the unload stations are full...

The "fluid load" is open-ended (IE: no restrictions)
The "fluid wait" interrupts if there's no path to station (unload), with a 15 second timer to clear the waiting station
The "fluid unload" occurs if there's any fluid.

So basically this creates a circle between fluid wait and unload 100% of the time (IE:
- wait for 15 seconds
- try to leave
- interrupt picks up on the fact there's fluid
- try to go to unload, destination full, go back to wait
)?

Breaking it down, I guess that makes sense, though admittedly I would have expected that to cause the train to simply loop around the wait station in circles every 15 seconds (after breaking it down/re-reading). You're saying the full interrupt logic occurs & executes before the train moves whatsoever?


So if that's the case, my interrupt would have worked, if I just set an arbitrary timer instead of telling it to wait till a get or drop station was unoccupied?

Drat, I just changed 'em all last night to non-parameterized wait interrupts, specific to each resource. I wonder what the performance implications between the two are...

Re: Bag of trains tutorial

Posted: Sat Dec 07, 2024 10:09 pm
by waterBear
porcupine wrote: Fri Dec 06, 2024 5:36 pm It is indeed confusing, as I don't see anything in the logic that prevents that loop from occurring if the unload stations are full...

The "fluid load" is open-ended (IE: no restrictions)
The "fluid wait" interrupts if there's no path to station (unload), with a 15 second timer to clear the waiting station
The "fluid unload" occurs if there's any fluid.

So basically this creates a circle between fluid wait and unload 100% of the time (IE:
- wait for 15 seconds
- try to leave
- interrupt picks up on the fact there's fluid
- try to go to unload, destination full, go back to wait
)?

Breaking it down, I guess that makes sense, though admittedly I would have expected that to cause the train to simply loop around the wait station in circles every 15 seconds (after breaking it down/re-reading). You're saying the full interrupt logic occurs & executes before the train moves whatsoever?


So if that's the case, my interrupt would have worked, if I just set an arbitrary timer instead of telling it to wait till a get or drop station was unoccupied?

Drat, I just changed 'em all last night to non-parameterized wait interrupts, specific to each resource. I wonder what the performance implications between the two are...
Maybe I misunderstood your question, but "fluid load" is never "open ended." There is always a restriction. That is what prevents trains from picking up things which aren't needed.

The requester stops keep track of how many trains have been dispatched - that is the signal C in the requester stop section. They will reduce their request to account for that fact.

You can also try the blueprints in the post and see for yourself if it loops. It's always possible I forgot some additional details from my build while copying it over to the forums.

Edit to add: You can find the fluid load limit setting in the second image under the section titled "fluid loading."

Re: Bag of trains tutorial

Posted: Wed Dec 11, 2024 9:22 pm
by porcupine
Naw, it looks like this was my error: I was looking at your train schedule bits, without catching the outpost setup (IE: delivery stations calling, instead of having trains stocking up regardless of whether there was an existing call or not).

This differs from my usual setup, where the trains are effectively pre-loaded for when there's a call, means unloads happen a lot faster (no load/travel first), but trains will become an over-full buffer if you have more production than consumption (which you normally have with at least one resource obviously).

I found the format a bit hard to follow (big pictures, less text, less spacing, so harder to segment the post into individual/identifiable chunks), but that's on me, not you :).

Re: Bag of trains tutorial

Posted: Wed Dec 11, 2024 11:29 pm
by waterBear
porcupine wrote: Wed Dec 11, 2024 9:22 pm Naw, it looks like this was my error: I was looking at your train schedule bits, without catching the outpost setup (IE: delivery stations calling, instead of having trains stocking up regardless of whether there was an existing call or not).

This differs from my usual setup, where the trains are effectively pre-loaded for when there's a call, means unloads happen a lot faster (no load/travel first), but trains will become an over-full buffer if you have more production than consumption (which you normally have with at least one resource obviously).

I found the format a bit hard to follow (big pictures, less text, less spacing, so harder to segment the post into individual/identifiable chunks), but that's on me, not you :).
Thank you for the feedback :)

I want the post to be helpful and useful and understanding why it's not landing is actually interesting to me and helpful. At some point when I'm feeling motivated I think I'll probably do an editing and formatting overhaul to make the post better based on your feedback.

I've been using this setup for a while now and it works very well, but you're right. There is a noticeable delay between initial request and delivery. However there is no starvation as long as your buffer is big. The stations request however many trains they need to fill the buffer's current capacity, so if you use legendary steel chests you'll get a huge queue of trains coming which solved the problem for me in the rare case when I needed. Working at roughly 2k science packs per minute, about 8.6k SPM. So no data beyond that scale yet.

Re: Bag of trains tutorial

Posted: Thu Dec 12, 2024 12:48 am
by wizcreations
I haven't actually tested this yet, but with the new fluid mechanics, is it still faster to use trains for long distances? I'm wondering if this is still needed

Re: Bag of trains tutorial

Posted: Fri Dec 13, 2024 1:11 pm
by waterBear
wizcreations wrote: Thu Dec 12, 2024 12:48 am I haven't actually tested this yet, but with the new fluid mechanics, is it still faster to use trains for long distances? I'm wondering if this is still needed
Someone had to ask eventually :)

That is a design decision that is totally up to you, but this system works equally well for solid items. Fluids are just easier to show, since you only need one wire from the storage tanks.

The biggest benefit is flexibility. Once you've got this system going, it works like a logistic train network.

As for fluids specifically, keep in mind that once you place a pump, you are capped to 1200/s flow (more for higher quality pumps). It is not hard for a single high quality foundry with beacons to approach that limit. Currently my trains are fed by 3 legendary pumps on each car (that's 12 pumps). I haven't calculated the throughput, but you'd need quite a lot of pipelines and pumps to do the same thing over long distances.

Re: Bag of trains tutorial

Posted: Sat Dec 14, 2024 4:49 am
by porcupine
waterBear wrote: Wed Dec 11, 2024 11:29 pm I want the post to be helpful and useful and understanding why it's not landing is actually interesting to me and helpful. At some point when I'm feeling motivated I think I'll probably do an editing and formatting overhaul to make the post better based on your feedback.
I think what made it hard for me to read/follow was:
- Images are huge, would suit better as much smaller images (halfway between current and thumbnails), they're almost an entire screen (vertically) in size, some are more then that. I'm on a 43" monitor, so that's pretty overwhelming.
- Text compared to images is sparse
- Lack of any spacing, it's just continuous, could use spacer lines/paragraph format/etc.

Because of the above, it just blurred into one long run-on sentence essentially, at least that'd be my take on why I had trouble following, might not apply to/for all of course!