Completely Combinator Controlled Trains! + 1.1k spm base

Smart setups of railway stations, intelligent routing, solutions to complex train-routing problems.
Please provide - only if it makes sense of course - a blueprint of your creation.
farcast
Long Handed Inserter
Long Handed Inserter
Posts: 87
Joined: Fri Jul 06, 2018 8:25 am
Contact:

Completely Combinator Controlled Trains! + 1.1k spm base

Post by farcast »

I gave myself the goal of controlling every aspect of trains with combinators. Pathing, scheduling, and wait conditions, all handled entirely with custom logic. Every train gets the exact same schedule, every stop gets the same name, and I need to make sure resources get delivered to the right place in the right amounts. Far easier said than done, but it's not impossible.


Directing trains with stops

Before everything else, one problem must be solved. How to control which direction a train picks with combinators? The mechanism used here is the same as used by RattlemBones and knightelite. Each junction exit has it's own Stop with the same name as the train's current destination. The exit the train needs to take is calculated with combinators somehow, then the Stop on the other exit is disabled. The train must now route to the correct exit, since that has the closest Stop. When the train is confirmed to be in the correct exit, but before it stops at the Stop, that Stop is disabled, forcing the train to re-route to the next way-point.

Like so, the train's exact route can be controlled entirely via combinators, and trains only need to slow down a bit when going through a junction.


Pathing algorithm

Next problem: How to calculate which exit the train needs to take? My solution: Have each station represented by a signal that propagates from junction to junction, exit to entrance, starting from the station represented by that signal. The value starts arbitrarily high, then decreases after every junction it goes through. The value emitted by the entrance of a junction is the highest value received by the two connected exits, minus some weight. Trains are assigned the signal representing its destination somehow, then the exit receiving the highest value for that signal is the exit the train needs to take. The signal is followed from entrance to exit all the way to the destination. With the "Each" wildcard, it's possible to calculate every ideal route to every destination simultaneously!

Perhaps not the most computationally efficient algorithm, but fine tuning train routes is easily done by controlling the weight subtracted from the path signals.

Such fine tuning includes:
-a manually adjustable constant for biasing particular routes.
-the longer a train is waiting at the entrance, the more the weight is increased, so trains can go around jams if possible.
-increasing the weight for every train heading to the junction, to make trains prefer less busy routes.


Passing train data

So, trains are assigned a signal to follow, I'll get to how that's done later, but now I need a way to have this information follow the train from junction to junction. I came up with two methods. First is to have a memory cell for every block in-between the junctions. When a rail signal turns yellow, the memory cell in front would be overwritten with the contents of the memory cell behind. Second is to have a queue memory block right at the entrance of the junction. When a train exits a junction, it's information is pushed to the queue at the next junction.

The first method is far more robust, and in many ways the more sensible option. I use this method for passing data throughout a station. I picked the second method for junctions because it's more interesting, and can work as a history of the last few trains that passed through, which has helped immensely for debugging, even if the exact conditions for sending data and loading the next train's data were a pain to get right.


Refueling

Trains need fuel. Thankfully this system has a way to re-direct trains that need fuel to a refueling station, rather than needing to ship fuel to almost every station. Inspired by RattlemBones, trains have a fuel counter signal that gets passed along side the path signal. The counter is decreased by a manually set amount that would ideally be roughly proportional to the distance to the next junction when a juction is passed. When the counter drops below 0, the junctions will direct the train to the refueling station path signal.


Stations

Now for path assignment. I could just give stations a fixed destination to assign to trains, but that's too simple! Path assignment should be just as complicated as the junction, if not more! I think I definitely succeeded at that.

At it's simplest, empty trains wait at a depot until both an unloading station needs a train load of material and a loading station has enough of that material for a full train. Then the depot sends a train to the loading station, the loading station sends it to the unloading station, and the unloading station sends trains back to the depot where it all starts again. Of course, behind that simple description is endless complexity. Some of it was inevitable, a lot of it I brought upon myself.


Selector

Like the junction, there's one problem that needs to be solved before everything else: which train should go, and which station should it go to? After all, there's more than one train, and multiple loading/unloading stations for a particular material. They can all suddenly be ready at the same time, but only one of each must be selected. I could just cycle through a list of IDs and trigger the depot/station when the ID matches. That would be plenty fast enough, but I thought of a more interesting solution: pick the lowest ID. This can be done with an average time of log(n)... per selection. While this is more responsive in the case of many stations making infrequent requests, to go through every request when every station is requesting takes something like nlog(n) time. For comparison, the list method would need n time.


Resource queue

Next is what to do when a depot/station is selected? For stations, they enter a global queue for their particular resource. Every resource (signal) has a separate queue.

Unloading stations enter the queue whenever they're low on whatever it is they need.

Loading stations have to wait until the resource they load is being queued for before they queue for the empty train resource, and at most as many of the same type of loading station can queue up as there are unloading stations queued for that resource plus some global constant. This is to prevent every empty train from being filled with stone.

A depot waits until a queue forms for a resource that matches one of its trains. When selected, the depot sends a signal to the station that's first in line for the matching resource, causing the station to leave the queue and send its path signal to be assigned to a train. If a loading station was signaled, that station signals the unloading station first in line for the resource being loaded and stores the path signal received, to be assigned to a train when it leaves the station.

One of the goals I set out to achieve was to eliminate the possibility of one or two busy stations hogging all of a resource. Every unloading stop is guaranteed to inevitably get a train so long as there is any production, no matter how bogged down the system might be. I couldn't do the same for loading stops, sadly. Sure, this is a problem that's only a problem when you have the much bigger problem of a lack of production, or too slow of a system, or not enough trains. It's also a problem that tends to fix itself with enough time as stuff backs up, but solving it anyway has been an interesting challenge.


Station design

For the overall station design, I managed to make a modular system where you can mix and match any number of load and unload stations of different resources, with them all sharing a common waiting bay. I somehow made this work despite the fact that an iron train would happily go to a copper stop if allowed.


Service stations

On top of the normal stations, I also made a separate system for service stations. Mixed item stations with a single train moving stuff around. There's a construction train, a wall train, and a trash train that takes excess material back to the mall. Refueling stations also use service station slots.


Dashboard
The right side of the dashboard listens to global communications to track the average ticks per train for each resource and displays how it compares to what it should be for an adjustable SPM. The base constants are the wagons per minute x100 for 1000 SPM as calculated by factoriolab, only including what gets transported by train. Two other constants control what percentage of 1000 SPM is used as the center line, and how many percent a lamp represents. On the left is a heat map showing how busy each station currently is. Top right lights up when stations are trying to use the global network, and makes a noise when one gets selected. I chose kick for unloaders, hi-hat for loaders, and snare for depots. Top middle is a circular bar for how much steam power is being used, with the lights in the middle spinning around at a proportional rate.

User guide
I can only assume I'm the only one crazy enough to actually use this, so I made a 1.1k spm factory with this system. I'm proud to say I even managed to fix enough bugs to get it running error free for 10 hours, with a decent UPS too!
Check out the full map here!
Base pictures
Change log
The save is completely vanilla, though I used a mod to delete chunks to reduce the file size.
I recommend having the show-rail-paths debug option enabled to better see what's going on with the trains.
Attachments
Completely combinator controlled trains v1.0.1.txt
(185.34 KiB) Downloaded 153 times
1.1k SPM Completely Combinator Controlled Trains by farcast.zip
(31.83 MiB) Downloaded 197 times
Efficient inefficient design.
Post Reply

Return to “Railway Setups”