promotional bannermobile promotional banner

TFMG Electricity Performance Patch

Fixes the per-tick electrical network rebuild loop and the quadratic network update in Create: The Factory Must Grow.
Back to Files

tfmgfix-2.1.0

File nametfmgfix-2.1.0.jar
Uploader
mzgmzg
Uploaded
Aug 12, 2026
Downloads
299
Size
8.6 KB
Mod Loaders
NeoForge
File ID
8632508
Type
R
Release
Supported game versions
  • 1.21.1

Curse Maven Snippet

NeoForge

implementation "curse.maven:tfmg-electricity-performance-patch-1624822:8632508"

Learn more about Curse Maven

What's new

2.1.0 — for TFMG 1.2.2

Same target as 2.0.0: TFMG 1.2.2 only. Stay on tfmgfix-1.2.0.jar if you are still on TFMG 1.2.0. Everything 2.0.0 fixed is still fixed; this adds one thing and changes nothing else.

Fixed: building a network is quadratic, and it happens inside a tick

Walk into a large electrical build and the game stops for a moment. Measured on a live server with an instrumented client: one client tick of 2381 ms, and further ones of 336, 102, 73, 72, 61 and 45 ms, every one of them inside Level.tickBlockEntities. Sampled stacks named the same path in five stalls out of six.

ElectricalNetwork.add is the only way a block joins a network, and it is written like this:

public void add(IElectric e) {
    List<Long> ids = new ArrayList<>();                  // a fresh list, every call
    members.forEach(m -> ids.add(m.getData().getId()));  // every member, every id boxed
    if (ids.contains(e.getData().getId())) return;       // linear scan
    members.add(e);
}

One membership test walks the whole network and allocates one boxed Long per member. The caller is IElectric.onConnected, which recurses across the entire grid, and that is reached from onPlaced, which tickElectricity runs whenever connectNextTick is set — and readElectricity sets it, so every electrical block asks for a full rebuild as its chunk loads. Rebuilding a grid of n blocks therefore costs O(n²) time and allocates on the order of n² objects: for a two-thousand-block grid, four million of them, in one tick, on the game thread.

The replacement compares the same field, in the same order, against the same live members, and returns the same answer for every possible input — without the list, without the boxing, and stopping at the first match.

Measured after the change, same instrument, same kind of walk: worst tick 2381 → 570 ms, and the tail 336 → 89, 103 → 64, 73 → 47. About a quarter of what it was.

Why it is not an O(1) lookup, which is what it obviously wants to be

Because the thing being compared is not an identity. ElectricBlockValues.getId() returns the field electricalNetworkId — the id of the network the block currently believes it belongs to — and that field is written at runtime by IElectric.setNetwork(long) and IElectric.onPlaced(), both of which run inside the very recursion that calls add. Scanning the constant pools of all 678 classes in the jar confirms those two, plus the constructor, are every write there is.

So a cached set of member ids goes stale silently, and the size of members does not change when it does. Concretely: member A joins with id 5; something calls setNetwork(9) on A; add(B) arrives with B also on 9. The original rebuilds the id list from the live members, sees 9, and refuses B. A cache holding {5} sees no 9 and adds B to a network the original would have left it out of — a different grid, a different voltage, a different answer to who has power.

That is a mechanics change wearing the costume of an optimisation, so it is not in this build. The remaining cost is still O(n) per join by design. Making the membership test independent of a mutable field decides which blocks land in which network, and a third-party patch may not decide that — it belongs upstream, together with readElectricity scheduling a full rebuild per block on chunk load.

Unchanged

Everything from 2.0.0: the deferred-action queue that was never emptied, the quadratic network update (getCableCurrent computed once per update instead of once per cable), the large switch reconnect throttle, and the three goggle tooltips that crash the client when a multiblock loses its controller.

The cached cable current was re-verified against the same trap this release documents: nothing reachable from the third pass of updateNetwork writes electricalNetworkId or calls setNetwork / onPlaced, so no member can move to another network mid-pass and the cached value stays correct.

Verified before release

  • members is a plain ArrayList, so the indexed loop is O(1) per step and not accidentally quadratic on a linked list.
  • Every getData() in the jar is a three-instruction getter (aload_0; getfield; areturn) — four implementations, all pure. So evaluating the incoming id before the walk instead of after it cannot change anything, and nothing can modify the list while the walk is in progress.
  • Long.equals and == agree on every long.
  • Injection points checked against tfmg-1.2.2.jar before compiling; the jar carries no local paths and credits its author.

This mod has no additional files