Перейти к содержимому
Mineforgian

Statestream

Radical performance optimization by reworking BlockState lookups and neighbor block updates.

Загрузки
2K
Подписчики
8
Обновлён
24 мая 2026 г.
Лицензия
MIT

Опубликован 14 мая 2026 г.

StateStream

Radical performance optimization for Minecraft 26.1.2 — zero gameplay changes, guaranteed.

StateStream surgically targets three of the most expensive hot-paths in Minecraft's server loop and replaces them with lean, cache-friendly implementations. Every optimization is independently toggleable and self-disabling on error, so a bad config silently falls back to vanilla rather than causing bugs.


Summary

The Statestream mod removes a lot of unnececary cheks in the game which allows your server to run more smothly. This makes your TPS higher if it issnt at 20 (20 is max). So the mod is especially helpfull if you experience server lag. Even though you dont have less that 20 TPS (20 is max) the mod will still help because your computer has to do fewer calculation. Therefore the mod is extremely helpfull when playing singleplayer or hosting a server/world on your computer as it allows the PC to use more of the CPU to give you higher FPS.

Performance Improvements

FPS (Client Frames Per Second)

StateStream's optimizations live on the server side, but they indirectly improve client FPS in three ways:

1. Fewer server lag spikes → smoother animation interpolation When the server misses a tick (falls below 20 TPS), the client interpolates entity and block positions incorrectly. Rubber-banding, stuttering, and "jittery" movement all trace back to server lag. StateStream keeps the server at 20 TPS more consistently, which the client experiences as a visibly smoother framerate even if your actual GPU FPS hasn't changed.

2. Singleplayer / integrated server (most important for solo players) In singleplayer, the server and client share the same CPU. Every millisecond saved in the server tick is a millisecond returned to the render thread. In a typical survival world:

  • BlockState cache eliminates redundant HashMap traversals during chunk rendering border calculations
  • Reactive ticking reduces time spent in the tick thread, directly lowering frame times

Estimated singleplayer FPS improvement: +3 – 12 FPS at 60 FPS baseline; higher gains where the server is using more of the CPU, higher render distance, multiplayerworlds hosted from same PC as client.

3. Reduced garbage collection pressure The flat long[] cache avoids thousands of boxed Boolean and Integer object allocations per tick that vanilla's generic property system produces. Fewer short-lived objects → fewer GC pauses → fewer frame drops.

**NB! FPS improvments will only be seen when the server/world is running on the same computer as the client, for example when hosting a server for you and your friends localy on your PC or using essentials etc. When the mod is put on a server running externaly you will experience that the server is running smoother rather than your client.


How Each Optimization Works

JIT BlockState Inlining

Every block in Minecraft has one or more BlockStates (a grass block has snowy: true/false, a log has axis: x/y/z, etc.). Vanilla looks up properties through a generic HashMap inside StateHolder — flexible, but slow when called millions of times per tick.

StateStream pre-bakes the five most-queried properties (solid, opaque, hasCollision, isAir, lightEmission) into a flat long[] array at startup, one entry per blockstate ID. A lookup becomes a single array read instead of a map walk. The array is laid out so frequently co-accessed properties share the same CPU cache line.

Reactive Block Ticking

Vanilla samples random.nextInt(4096) per subchunk per tick. In a typical survival world, most of those 4096 positions are air or non-tickable stone. StateStream maintains a registry of only the positions that contain tickable blocks (crops, grass, kelp, ice, etc.) and skips straight to them.

Tick rates are mathematically identical to vanilla. The 1/4096 probability per block per tick is preserved exactly — we don't switch to a round-robin or deterministic schedule. Crop growth rates, ice formation, tree spread — everything is statistically the same, just smoother and using less of the CPU.

Update Culling

When a block changes, Minecraft notifies up to 6 neighboring blocks. Many of those notifications are genuinely necessary (redstone, water flow, falling sand). But some receiver blocks probably do nothing with the notification — they receive it and immediately return.

StateStream lets you register these "inert pairs" in a JSON config file. Before propagating a neighbor notification, it checks the table and skips the call if the receiver is known-inert for that source. The default config ships empty — zero suppression out of the box, maximum safety.


Commands

Requires operator level 2.

/statestream stats      — cache hit rate, suppressed updates, tracked tickable positions
/statestream status     — which modules are active / self-disabled
/statestream reload     — reload inert_pairs.json without restarting
/statestream benchmark  — timed performance scan (results in server log)

Configuration

config/statestream.toml — auto-created with safe defaults on first launch:

[general]
jit_inlining_enabled = true
update_culling_enabled = true
reactive_ticking_enabled = true
debug_logging = false

[culling]
inert_pairs_file = "config/statestream/inert_pairs.json"

All three modules are independently toggleable. Disable any module that conflicts with another mod in a single config line.


FAQ - just that im answering my own questions - heh :(

Will this change how my farms work? No. Tick rates, growth probabilities, and all random-tick behaviour are mathematically identical to vanilla. The 1/4096 probability model is preserved exactly.

Will this break redstone? Not by default. Update Culling ships with zero suppressed pairs. Redstone behavior is entirely unaffected unless you manually add inert pairs (and only after verifying them against vanilla source).

Can this corrupt my world? No. StateStream never writes block data or modifies world state. It only changes how fast the server reads block properties, not what those properties are.

What happens if something goes wrong? The affected module logs a warning and silently disables itself. Your world continues on vanilla behavior for the rest of that session. No crash, no data loss.

Ченджлог

1.0.1Релиз26.1, 26.1.1, 26.1.2 · 24 мая 2026 г.

StateStream Changelog

1.0.1 — Multi-loader support, critical fixes, major performance pass

Loader support

  • NEW: Native NeoForge build (statestream-neoforge-1.0.1.jar) targeting NeoForge 26.1.2.65-beta.
  • NEW: Native Quilt support via quilt.mod.json (also runs under Quilt's Fabric compatibility layer).
  • Refactored project into a loader-agnostic core (com.statestream.*) with separate entrypoints per loader (StateStreamFabric, StateStreamNeoForge).
  • Three distinct jars now shipped: Fabric, Quilt, NeoForge.

Critical bug fixes

  • Fixed silent data corruption when Y > 255. The previous position encoding masked Y to 8 bits, which collided for any block above Y=255 (Overworld extends to Y=320). The registry is now split per-section using section-local Y (4 bits) — collisions are mathematically impossible. New test allEncodingsUniqueWithinSection verifies every (x, z, ly) tuple produces a unique encoding.
  • Fixed registry corruption on every block placement and break. onBlockChanged was always receiving null for the old BlockState, which caused:
    • Duplicate entries when replacing a tickable block with another tickable block
    • Phantom entries (never removed) when a tickable block was destroyed
    • Tick loop wasting cycles on positions that no longer held tickable blocks
    • Old state is now captured at HEAD via a ThreadLocal and consumed at RETURN.
  • Added the /statestream enable <module> and /statestream disable <module> commands that the Modrinth description promised but didn't actually exist. Includes tab-completion for module names.

New feature: per-section tickable index with bitmap skip

The biggest performance win in this release. Each loaded chunk now keeps:

  • One sorted short[] per section containing only tickable positions
  • A 64-bit nonEmptyMask where bit s is set iff section s has at least one tickable block
  • The total tickable count for stats

The random-tick loop walks the mask using Long.numberOfTrailingZeros + mask & (mask-1) — empty sections are skipped at the cost of a single CPU instruction. On a typical Overworld chunk with tickables in only 3 of 24 sections, this is roughly an 8× reduction in section iterations per tick.

Probability parity with vanilla is preserved exactly: each section still rolls randomTickSpeed picks of nextInt(4096) and a tickable block still has P=1/4096 per roll.

Performance fixes

  • AtomicLong cache counters replaced with LongAdder — eliminates LOCK CMPXCHG contention on the per-call hot path.
  • Block state IDs are now cached on the BlockState itself via a @Unique mixin field on BlockBehaviour$BlockStateBase. First read pays a Reference2IntOpenHashMap.getInt(); every subsequent read is a single field load. Without this, the cache was doing a hashmap lookup to save a field lookup — a net loss.
  • onBlockChanged no longer allocates a HashSet per mutation. It now does CAS-based sorted-array binary insertion/removal. Lock-free, single allocation for the new array only.
  • onChunkLoad iterates LevelChunkSection arrays directly instead of calling chunk.getBlockState() ~98 000 times via BlockPos.betweenClosed. Empty sections are skipped at O(1).
  • UpdateCuller.shouldSuppress now has an O(1) empty-set early exit and an O(1) Block reference check (couldSuppressSource) that skips Identifier allocation entirely when the source block isn't involved in any pair. Internal storage switched from Set<String> to Map<Identifier, Set<Identifier>> — no more toString() + concatenation per call.
  • Tick loop now uses a single reusable BlockPos.MutableBlockPos instead of allocating one BlockPos per ticked block per tick.
  • Cache verification moved from per-call to one-shot at populate(). The hot path no longer pays for verification overhead.
  • Long2ObjectOpenHashMap candidacy preserved via ConcurrentHashMap<Long, ChunkEntry> — Long boxes are short-lived and escape-analysis-friendly.

Code quality

  • JSON parsing in UpdateCuller now uses Gson (with the hand-rolled string parser kept as a fallback for hostile input).
  • Config paths resolved via FabricLoader.getConfigDir() / FMLPaths.CONFIGDIR.get() on the respective loader, with a statestream.config.dir system property as the loader-agnostic bridge.
  • Mod version now read via Fabric's / NeoForge's ModContainer API instead of a hand-rolled JSON parser.
  • fabric.mod.json environment changed from "*" to "server" — the mod is no longer required on the client.

Tests

  • Updated for the new section-local encoding API (encodePos(x, z, ly) + matching decodeX/Z/LocalY).
  • New test allEncodingsUniqueWithinSection proving the Y-collision bug cannot recur.
  • New test nonEmptyMaskMatchesSectionContents covering the bitmap-walk semantics.
  • Counter assertions updated from AtomicLong.get() to LongAdder.sum().

Internal

  • New BlockStateIdHolder interface implemented on every BlockState via BlockStateBaseMixin.
  • New ReactiveTickRegistry.ChunkEntry immutable struct (per-section arrays + bitmap + count + minY).
  • New com.statestream.fabric package with the Fabric-specific entrypoint.
  • New neoforge/ Gradle subproject using net.neoforged.moddev 2.0.141.

1.0.0 — Initial release

  • JIT BlockState Inlining (BlockStateCache)
  • Update Culling (UpdateCuller)
  • Reactive Block Ticking (ReactiveTickRegistry)
  • Per-module fallback handler — any unexpected error disables the offending module and restores vanilla behavior
  • TOML config with independent toggles for each module
  • /statestream stats, status, reload, benchmark commands
  • Fabric loader for MC 26.1.2

Комментарии

Загружаем…