Links
Tags
Creators
Details
0.15.0-beta.1
Compatibility
Changes
v0.15.0-beta.1 — Async chunk load (working build, off by default)
0.15.0-beta.1 is the first async-chunk-load prerelease that runs on a real production server. The previous public prerelease v0.13.0-beta.1 shipped a jar that omitted the MixinExtras dependency, so enabling async loading on a real server crashed it on a missing class; this build fixes the packaging, ships a self-contained jar, and rewrites the async-load implementation to be non-blocking. The feature is still off by default and opt-in, Forge 1.20.1 build only. Back up your entire world folder before enabling it
Key fix: MixinExtras packaging
- Async load's instruction injection is built on MixinExtras
@WrapOperation. The v0.13.0-beta.1 jar did not bundle that dependency, so a real server threwClassNotFoundExceptionwhen loading the async-load mixins (the dev environment ships the dependency, so this was only ever validated in dev and the problem stayed hidden) - This build ships a self-contained jar (MixinExtras embedded) — install and go, no manual dependency to add. This is the first async-load build that can actually be validated in production
What it does
- Vanilla deserializes a loading chunk's on-disk bytes into game objects (
ChunkSerializer.read) on the main thread; with a high view-distance and players moving or teleporting fast, this becomes a main-thread burden - When enabled, the pure deserialization runs on a background load worker; the main thread keeps only what must run there: POI consistency, light sections, and
ChunkDataEvent.Loadreplay - The real payoff is on production servers (view-distance 10-12 + many players): the freed main-thread time becomes TPS headroom, smoother loading, room for more players
- It is not a flight-speed silver bullet: single-player flying at max view-distance is bound by vanilla's single-threaded chunk pipeline, which off-thread deserialization cannot help — that part cannot be parallelized without breaking mod compatibility
What it does not touch
- This optimizes the disk serialization boundary of existing chunks (read from disk, written to disk), not worldgen. Generating never-before-seen terrain is untouched: brand-new chunks never pass through
ChunkSerializer.read, so async load only kicks in for chunks that were previously generated and saved
Changes since v0.13.0-beta.1
- Non-blocking future chain: async load's internals were rewritten from "
@WrapOperation+joinblock-and-wait" into a non-blocking future chain, so the main thread no longer stalls waiting on a background parse - In-flight throttle (
LoadInFlightLimiter): caps deserializations in flight so a batch finishing at once cannot land all its main-thread replay in a single tick;maxInFlightdefault 32 → 128, and hot-adjustable - Fine-grained Codec lock: the parse path's concurrency guard is narrowed to the Codec dispatch cache alone, cutting contention between workers
- Now includes the v0.12.1 SavedData deep-copy spike fix (issue #12): oversized
.datsaves no longer deep-copy the whole tag on the main thread - Adds the online chunk-restore coordination layer (
SaveCoordination): lets the companion BetterBackup restore a single chunk or an area in place without stopping the server; on a standalone BAS install this is a dormant low-level capability — the matching command lives in BetterBackup
Configuration ([load] section, off by default)
load.enabled(default false): master switch for async loading, independent of the save-sidegeneral.enabledload.loadEventCompatMode(default PARTIAL): PARTIAL = deserialize on a worker, POI/light/event back on the main thread; FULL = the entire read stays on the main thread (equivalent to the feature being off, zero behavior delta — a per-config fallback if a mod misbehaves)load.maxInFlight(default 128, hot-adjustable): cap on deserializations submitted to workers concurrently, so a batch finishing at once cannot land all its main-thread replay in a single tick; raise per server until a burst reappearsload.loadMaxRetries(default 1): worker-thread re-attempts after a parse throws, before falling back to a vanilla main-thread readworkers.loadWorkerThreads(default 2): load worker count; deserialization is largely single-thread bound, 2 covers typical loads; raise on high-core machines with high view-distance or fast movement
Safety and compatibility
- Data safety: if an off-thread parse fails, the same bytes are re-read on the main thread as a fallback — no data is lost in any case
- One-flag kill switch:
loadEventCompatMode=FULLorload.enabled=falsereturns to vanilla load behavior instantly; config hot-reloads, no restart needed - Mixin compatibility: every chunk-read injection point uses
@WrapOperation, coexisting with mods (e.g. architectury) that touch the same instructions rather than claiming them exclusively - Pick one against other mods that also load chunks off-thread (C2ME-Forge); all other mods are orthogonal and do not conflict
Why it is a beta
- Verified on a real 137-mod heavyweight pack (Create / immersive vehicles and other block-entity-dense content): self-contained jar, async loading on, TPS locked at 19.99 while flying at 450 m/s, heavy chunk deserialization confirmed off the main thread, zero worker errors, zero degrade, zero data-loss signals
- But "repeated save/reload in dense areas" and "sustained concurrent multiplayer" are not yet exhaustively verified, hence off by default and marked pre-release — back up first, roll out to a small group, and report issues
Upgrading
- Drop-in jar replacement; this build's jar embeds MixinExtras and installs as-is — do not keep using the old v0.13.0-beta.1 jar (missing dependency will crash)
- Async loading stays off and does not change existing save behavior; to try the new feature, set
load.enabledto true — and back up your world first
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:WAXR3mM5:xF0tRjna"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:WAXR3mM5:xF0tRjna"
}

