Skip to content

ghasto69/Threaded_Tracks

Repository files navigation

Threaded Tracks

A Fabric mod for Minecraft 1.20.1 that moves the Create mod's track-graph updates off the server thread. On large rail networks, placing or breaking a single track in vanilla Create stalls the tick with work that scales with the size of the whole network — graph merges, split detection, a crossing-scan over every edge on the server, and signal-group reflows. This mod runs all of that on a dedicated worker thread; the server thread only pays a sub-millisecond capture and a tiny apply step.

Targets Create Fabric 0.5.1 (f–j) — the mod's depends range is >=0.5.1-f <0.6 — and is compatible with Create: Steam 'n' Rails 1.7.2-beta, the legacy channel built against Create 0.5.1.j (S'n'R's main 1.7.x channel requires Create 6). All S'n'R track types — custom materials, monorails, crossings — go through the same intercepted code path; verified against the 1.20/dev sources: S'n'R has no mixins into the graph mutation core.

How it works

Every track placement/removal in Create funnels through two static methods (TrackPropagator.onRailAdded/onRailRemoved). This mod intercepts them and splits the work into three stages:

capture (server thread)   →   compute (worker thread)   →   finalize (server thread)
world connectivity BFS,       faithful port of Create's      swap cloned containers in,
O(local section)              algorithm against cloned       replay sync packets, trains,
                              graph containers, O(network)   signal updates — O(changes)

The worker never mutates shared state: it works on clones of the graph's container maps (nodes/edges/points objects are shared by reference, so trains keep valid references), and records everything else — sync packets, train re-homing, signal group writes, block-entity invalidations — as effects the server thread applies at the next tick boundary. Application order equals vanilla's execution order. Batches are transactional (all-or-nothing), and any failure re-executes the ops through the untouched vanilla code path, so the worst case is exactly vanilla behavior, never a broken graph.

Consistency barriers drain the queue where synchronous state matters: before Create saves the railway data (no ghost tracks after a crash), before stations/signals/observers/S'n'R switches resolve their target edge, when third-party code mutates the graph directly, and on shutdown.

Details: docs/DESIGN.md.

Measured behavior

On the test server (Create Fabric 0.5.1-j + S'n'R 1.7.2-beta, headless): placements, removals, merges, splits, S'n'R oak/monorail tracks and a 64-op burst with interleaved break/replace all apply asynchronously — 78 async ops, 0 fallbacks, 0 capture misses, with final graph topology identical to a vanilla control run of the same command sequence, and full persistence across a server restart. Typical per-op cost: ~0.4–1 ms capture (server thread), ~0.3–6 ms compute (worker), ~90–400 µs finalize (server thread). On big networks the compute share is what grows — and that is the part that no longer runs on the tick.

Commands & config

/threaded-tracks status | flush | on | off (permission level 2).

config/threaded-tracks.json:

key default meaning
enabled true master switch (also /threaded-tracks on/off)
cancelClientPropagation true drop the integrated client's redundant (and racy) propagator calls into the server manager
maxConsecutiveFailures 3 worker errors before the engine disables itself for the session
batchLimit 256 max ops per worker batch
captureBudget 4000 max track locations captured per op (vanilla walks are bounded at 1000); retries use 4×
logOpTimings false log per-batch stage timings
flushWarnMillis 5000 warn interval while a barrier waits on the worker

Building & running

./gradlew build          # jar lands in build/libs/
./gradlew runServer      # dev server with Create + Steam 'n' Rails on 1.20.1

Note for the dev runtime: loom strips the Jar-in-Jar nesting out of dependency mods, so build.gradle compiles against the maven artifacts non-transitively and runs against the production Modrinth jars plus Create 0.5.1-j's bundled dependencies (Flywheel 0.6, Registrate, Porting Lib 2.3.8, etc.) declared explicitly, mirroring the shipped jar's META-INF/jars.

For production-accurate testing, drop build/libs/threaded-tracks-<version>.jar into a Fabric server's mods/ next to create-fabric and fabric-api (and optionally Steam 'n' Rails).

Scope & known limitations

  • Server side only. The client's replica maintenance (including its own split recomputation) still runs on the client main thread — same technique applies, future work.
  • Train pathfinding (Navigation.findPath) still runs on the server thread — the queue/effects infrastructure here is the foundation for moving it later.
  • Graph changes become visible to trains/targeting 1–2 ticks after the block change (vanilla Create already defers placement propagation by one tick). Interactive flows that need immediate consistency (signal/station placement, saves) are covered by flush barriers.
  • Ports of Create's algorithms are bug-for-bug faithful to Create Fabric 0.5.1-j, including its quirks (e.g. redundant per-edge sync on unsignalled networks — reproduces identically in vanilla control runs).
  • Create 0.5.1's lazy curve geometry (BezierConnection.resolve) is not safe to first-trigger off-thread, so the mod force-resolves every loaded curve on the server thread after Create loads its railway data, and every captured turn during capture (a few µs per curve, once).

License

MIT for this mod's own code (see LICENSE). Portions of the graph algorithms are ports of Create (MIT) code, adapted to a clone-and-defer execution model.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages