ThreadWeave is a lightweight simulation framework that allows you to move expensive logic from Minecraft thread into other threads while keeping all world modifications thread-safe
Isolated simulation does not have Live Level changing access due to race-conditions on threads so it only processes calculations and e.t.c. This may sound useless unless you start to make AI, reactors, computers or more complicated systems like physics Engines.
Execution changes to world like placing blocks will still depend on Minecraft Thread to not create race conditions
Minecraft mods often suffer from:
ThreadWeave allows you to move only the computation part into async execution while keeping world safety intact
Only pure data is allowed inside simulation methods
Simulation methods must be pure methods:
ThreadWeave works by separating what your mod does into two parts: data processing and world changes
Minecraft normally runs everything on a single thread. That means all calculations, AI, machines, and block logic compete for the same limited performance budget
ThreadWeave changes this by safely moving heavy calculations to background threads
Modder only defines:
What ThreadWeave does automatically?
WORLD_SYNCED
Runs in sync with Minecraft (20 times per second)
Used for normal game logic
ISOLATED
Runs independently of Minecraft speed
Used for simulations that can run faster or slower than the game itself
Framework prevents unsafe access to:
repositories {
maven {
url = "https://api.modrinth.com/maven"
}
}
dependencies {
implementation "maven.modrinth:thread-weave:{version}"
}
Do not forget to define dependency in mods.toml or you will get minecraft crashed
[[dependencies.${mod_id}]]
modId="thread_weave"
type="required"
versionRange="[{version},)"
ordering="AFTER" // VERY important to load your mod AFTER ThreadWeave
side="BOTH"
For example you have BlockEntity (you can use custom classes. No matter it's minecraft or not)
public class ReactorBlockBE extends BlockEntity implements TickableBE {
private int heat;
private int fuel;
public ReactorBlockBE(BlockPos pos, BlockState state) {
super(ThreadBlockEntities.REACTOR_BE.get(), pos, state);
}
@Override
public void tick() {
if (level == null || level.isClientSide()) return;
heat+=20;
fuel-=1;
}
}
I - You need to set what variables will be used in async-processing
public record ReactorData(int heat, int fuel) implements SimulationDelta {}
II - Then you need to split your processing. Simply take all of your calculations and move it to other method. Here you need to set @SimulatedThread annotation. TickMode can be Isolated or WorldSynced. WorldSynced will run at minecraft TPS. Isolated will run at any tick speed you set without minecraft dependency
//value = MOD_ID + ":reactor"
//value = "my_mod:reactor"
@SimulatedThread(value = MOD_ID + ":reactor", mode = TickMode.WORLD_SYNCED)
public ReactorData simulate(ReactorData data) {
int heat = data.heat() + 20;
int fuel = data.fuel() - 1;
return new ReactorData(heat, fuel);
}
III - Last you need to submit your data to framework. Use static method "submit"
@Override
public void tick() {
if (level == null || level.isClientSide()) return;
Simulations.submit(this);
System.out.println("heat=" + this.getHeat() + " fuel=" + this.getFuel());
}
IV - Also you need set what classes that will be scanned for @SimulatedThread annotation
public YourMainModClass(IEventBus modEventBus, ModContainer modContainer) {
modEventBus.addListener(this::commonSetup);
Simulations.submitScan(NavMeshSimulation.class);
...
}
You can also manually control interpreter and extractor by @SimulatedInterpreter and @SimulatedExtractor annotations
For example: work ticks
@SimulatedThread(value = MOD_ID + ":reactor", mode = TickMode.WORLD_SYNCED)
public ReactorData simulate(ReactorData data) {
int heat = data.heat() + 20;
int fuel = data.fuel() - 1;
return new ReactorData(heat, fuel);
}
@SimulatedInterpreter
public void interpreter(NavData data) {
this.heat = data.heat();
this.fuel = data.fuel();
workTicks++;
}
Example above is not the best. Custom interpreter is much usable in per-region processing when you need to merge result (Baked Navmesh generation)
@SimulatedThread(value = MOD_ID + ":navgraph", mode = TickMode.WORLD_SYNCED)
public NavData simulate(NavProcessorData data) {
if (processor.isEmpty()) return null;
return processor.build(navLevel);
}
@SimulatedInterpreter
public void interpreter(NavData data) {
if (data == null) return;
for (NavNode node : data.toRemove()) {
navLevel.nodes.remove(node.id());
}
navLevel.nodes.putAll(data.nodes());
navLevel.portals.putAll(data.portals());
navLevel.chunkIndices.putAll(data.chunkIndices());
}
No. There is still much work to do. You can follow progress in my discord server
For example most of TickingBlockEntities is not really good with this library due to Level require (Yet)
P.S: I will post a fabric port + other versions later [1.7.10 - 26.x]