Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (2024)

This is a continuation of the first post found here.

Writing the first part has been a very rewarding experience. I’m re-learning howvaluable teaching is for both the writer and reader.

Revisiting my old circuits to explain how they function has already revealedmany areas for improvement. I made some of those improvements while publishingpart 1 and I’ve got a few more to walk through in this post.

Unloader improvements

Adding a dedicated trash train was long overdue. Previously the seed traindoubled as my trash train. Over-filling the seed module manifest would leavelittle to no room to return items to base.

During base expansion I create and destroy many of these logistic stations.Waiting for stations to deconstruct to move the perimeter wall or clean up myrail network cost me hours of wait time & round trips.

I came up with a partial fix which I included in the last blueprint book:

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (1)

This would replace the seed train stop with dedicated trash trains. However,this worked best when I was destroying the outpost, in others it required manualfixups. There is no substitute for building this feature directly into theoutpost:

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (2)

I’m using a new trick here too: buffer chests to replace the requester and steelchests. These have a major advantage over the prior setup as they allow theplayer logistics, construction robots and regular logistics (assemblers,smelters) to pull from the trash chests while a train is en-route.

I frequently found that when refactoring outposts I would flush all my beacons,modules and assemblers through the trash circuit. When using using requester andsteel chests I would be unable to use those items again until a resupply trainarrives (the trash items would be loaded then unloaded immediately, still aPITA).

Using buffer chests in set-requests mode is the best of both worlds. Itbehaves like a requester chest, it will not pull from other buffer chests & itmakes itself available to the logistics network.

The trash inserters do not need to overlap with the module slot inserteralignments so I found there was room for another 4 unloaders (from 4 to 8).

Cleaner index circuit

I found a few ways to shrink the size of the indexer circuit by using a negativemask rather than the *1.0M, >1.0M, -1.0M trick in the previous postremoving 4 combinators.

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (3)

Blueprint.txt
Install the Text Plates mod ifneeded.

There were some concessions made as well: the circuit will no longer pause whenthe train is fully loaded. This was most annoying when standing over it,building it. Most of the time I’m elsewhere.

Let’s explode the circuit and do a walk-through:

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (4)

Blueprint.txt

There are two changes. The indexer circuit will now output everything except thecursor [*each] != [I]. I then [*each] * -1.0M to create a negative maskthat is joined with the WANT constant combinator.

The MISSING combinator takes three input signals:

  • The want signal: {rail: 100, chest: 50, inserter: 10}
  • The negative mask from the indexer: {rail: -1M, inserter: -1M} when chestis selected.
  • The (negative) train contents: {rail: -80, chest: -10}

Which sum to: {rail: -1M, chest: 40, inserter: -1M}.

The MISSING combinator filters negative values with [*each] > 0 ->[*each]. Now we have an isolated item signal of exactly what is missing{chest: 40} which is perfect to calculate the filter inserter hand size andfilter item (HAND, ITEM).

Cons

Index management is a pain. Setting new requests remotely requires a lot offiddling to correctly upgrade the manifests and indexes for each loader.

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (5)

The dream is plonking down an upgraded, self-contained module at the loader andunloader with the circuit making all adjustments needed at both ends.

There’s (at least) two missing pieces:

  • Generating an index from the manifest signal automatically. This would alsoensure the index stays in sync with the manifest items.
  • Balancing items over multiple wagons (otherwise we’d be limited to 40 itemtypes for the entire train). Spoilers: I’ve not solved this yet.

Automatic indexer

Last week I thought this was impossible – and perhaps it should be. Here is acompact, fast, exact, auto-indexing logistic loader circuit that will also resetthe train loader cache, index & index builder on manifest changes.

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (6)

blueprint.txt
Top-left is the request manifest (constant combinator).
Bottom-left is the exact cargo wagon contents.
Right is the index state, built to match the items in the request signal.

The steel chest is used as the persistent storage for the index.

The brainwave for this came after reading a Redditcommenton the first post. I can use a filter inserter to select and isolate a singleitem from a mixed item signal.

Naturally this will only work for item signals and you’ll need those itemswithin the current logistic network while building the index.

Previously I thought this would only be possible by using this Luascriptto generate a massive constant combinator array programmed with all in-gameitems. This would not be robust to non-vanilla items (without re-running thescript in each context), it has a huge footprint (at least 12 combinators) andthe iteration times over ~250 items to use 10 of them was very slow. Themanual index was a huge improvement at the time.

Index building

The procedure is:

  • Iterate over the index range (1..18) with [clock] % 19.
  • Count the number of item types at the current index position with [*each] ->C: 1.
  • If multiple items found ([C]ount > 1):
    • Pause the clock.
    • Insert one of the items into the chest, moving it up into the next slot.
  • Repeat.

Let simulate it with a bit of Rust:

fn main() { let mut db: std::collections::BTreeMap<char, i32> = ['A', 'B', 'C', 'D', 'E', 'F'] .iter() .map(|&item| (item, 1)) .collect(); let mut clock: i32 = 1234; loop { for (k, v) in &db { print!("{}{} ", k, v); } // convert clock signal into index position let index = clock % 19; // find items at this index position let items: Vec<char> = db .iter() .filter_map(|(k, v)| if *v == index { Some(*k) } else { None }) .collect(); let count = items.len(); print!("- index: {}", index); if count > 0 { print!(", items: {}", items.iter().collect::<String>()); } if count > 1 { let first = items[0]; // insert one of the multiple items to move it into the next slot (higher value). db.entry(first).and_modify(|e| *e += 1); println!(", insert: {}", first); } else { // resume clock clock += 1; println!(", resume"); } let mut values: Vec<&i32> = db.values().collect(); values.sort(); values.dedup(); if values.len() == db.len() && count == 0 { println!("done!"); break; } }}
A1 B1 C1 D1 E1 F1 - index: 18, resumeA1 B1 C1 D1 E1 F1 - index: 0, resumeA1 B1 C1 D1 E1 F1 - index: 1, items: ABCDEF, insert: AA2 B1 C1 D1 E1 F1 - index: 1, items: BCDEF, insert: BA2 B2 C1 D1 E1 F1 - index: 1, items: CDEF, insert: CA2 B2 C2 D1 E1 F1 - index: 1, items: DEF, insert: DA2 B2 C2 D2 E1 F1 - index: 1, items: EF, insert: EA2 B2 C2 D2 E2 F1 - index: 1, items: F, resumeA2 B2 C2 D2 E2 F1 - index: 2, items: ABCDE, insert: AA3 B2 C2 D2 E2 F1 - index: 2, items: BCDE, insert: BA3 B3 C2 D2 E2 F1 - index: 2, items: CDE, insert: CA3 B3 C3 D2 E2 F1 - index: 2, items: DE, insert: DA3 B3 C3 D3 E2 F1 - index: 2, items: E, resumeA3 B3 C3 D3 E2 F1 - index: 3, items: ABCD, insert: AA4 B3 C3 D3 E2 F1 - index: 3, items: BCD, insert: BA4 B4 C3 D3 E2 F1 - index: 3, items: CD, insert: CA4 B4 C4 D3 E2 F1 - index: 3, items: D, resumeA4 B4 C4 D3 E2 F1 - index: 4, items: ABC, insert: AA5 B4 C4 D3 E2 F1 - index: 4, items: BC, insert: BA5 B5 C4 D3 E2 F1 - index: 4, items: C, resumeA5 B5 C4 D3 E2 F1 - index: 5, items: AB, insert: AA6 B5 C4 D3 E2 F1 - index: 5, items: B, resumeA6 B5 C4 D3 E2 F1 - index: 6, items: A, resumeA6 B5 C4 D3 E2 F1 - index: 7, resumedone!

We’ve converted our request signal A1 B1 C1 D1 E1 F1 into a unique index A6B5 C4 D3 E2 F1, woo!

Lets match this to the circuit:

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (7)

blueprint.txt

The CLOCK SOURCE, PAUSE?, CLOCK & TO INDEX combinators are familiarelements from prior circuits.

INDEX is the persistent memory where the index is built and INSERTER is howwe shift items up the index slots one by one (hand size: 1).

IF BUILD checks we are in the index building mode, the other mode FLUSH istriggered when changing the constant combinator. When triggered, the FLUSHmode will flush all caches/chests (train loading cache, index chest, requesterchest). Covered more later.

INDEX ITEMS will select all items at the given index position with [*each] ==[I] -> [*each]: 1.

ITEM COUNT will emit C: 1 for each item found at the current index position.If C>1 we pause the clock (PAUSE TO FIX) while we fix this index position.

IF MULTI ITEM will send a signal to the filter inserter and requester chest ifC>1.

Now, with what I’ve explained so far there is a big off-by-one error. The itemat index position 1 will never be inserted into the chest due to the baserequest signal rail: 1, chest: 1, inserter: 1, .., it needs a way toinvalidate the index=1 position. I did this by re-using the clock source dot:1 to occupy that slot and push every other item up a slot. The dot is not anitem and can’t be inserted.

Flush circuit

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (8)

blueprint.txt

This is the second circuit mode which will flush all state after a manifestchange and then allow an index rebuild to start from a known good state (zero).

The FLUSH trigger is a small 3 combinator detection circuit CHANGEDETECTION. Both arithmetic combinators do [*each] ^ 0 -> [*each] to normalizethe request signals. The output of the first is fed into the second then bothfed into the SET ON CHANGE decider with the [*each] != 2 -> S: 1 condition.

In the steady state, each value in the SET ON CHANGE input signal will be 2(one from each arithmetic combinator). On changes, there will be a 1-tick memorydelay in the circuit in which one item will read a value of 1 triggering S: 1and the SR-LATCH to start the flush cycle.

The SR-LATCH in the set state S > 0 will enable the 8 inserters that flushall chests into the active provider chests. The RESET WHEN FLUSHED conditionis true when all active provider chests are empty.

There are two transistors 1 & 2 which will cut off signals to the requesterchests when the FLUSH mode is active.

The indexer circuit in the middle remains the same as the prior circuits.

Station upgrade

And finally upgraded my logistic stations to share the same trash stop with thetrash trains:

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (9)

Included in the blueprint book below.

Blueprint book!

blueprint-book.txt

That’s all for now!

Would love you hear your feedback, corrections, contributions & fixes!

Email: mason.larobina@gmail.com or raise an issue on the GitHubrepo.

For updates, star the repo or follow:

https://github.com/mason-larobina/factorio/commits/master.atom

Logistic Circuits Part 2 - Automatic indexes, trash trains, faster unloaders (2024)
Top Articles
Latest Posts
Article information

Author: Rev. Leonie Wyman

Last Updated:

Views: 5522

Rating: 4.9 / 5 (59 voted)

Reviews: 90% of readers found this page helpful

Author information

Name: Rev. Leonie Wyman

Birthday: 1993-07-01

Address: Suite 763 6272 Lang Bypass, New Xochitlport, VT 72704-3308

Phone: +22014484519944

Job: Banking Officer

Hobby: Sailing, Gaming, Basketball, Calligraphy, Mycology, Astronomy, Juggling

Introduction: My name is Rev. Leonie Wyman, I am a colorful, tasty, splendid, fair, witty, gorgeous, splendid person who loves writing and wants to share my knowledge and understanding with you.