
线程编织
适用于安全多线程模拟的异步处理框架
查看大图ThreadWeave
ThreadWeave 是一个轻量级模拟框架,允许您将开销大的逻辑从 Minecraft 线程移至其他线程,同时保持所有世界修改的线程安全性。
- 本模组不会修改 Minecraft 或修补原版代码。这是供其他模组使用的库
- 所有世界更改仍将在主服务器线程上执行
由于线程间的竞态条件,隔离模拟无法访问实时世界更改,因此它只处理计算等操作。除非您开始制作 AI、反应堆、计算机或更复杂的系统(如物理引擎),否则这听起来可能没什么用。
对世界的执行更改(如放置方块)仍将依赖 Minecraft 线程,以避免产生竞态条件。
Minecraft 模组常受以下问题困扰:
- 主线程上的沉重刻逻辑
- BlockEntity 内的开销计算
- 复杂系统(反应堆、AI、工厂)
ThreadWeave 允许您仅将计算部分移至异步执行,同时保持世界安全性完整。
模拟方法中只允许使用纯数据。
重要限制
模拟方法必须是纯方法:
- 输入 DTO -> 输出 DTO
- 异步执行中不允许有副作用
它是如何工作的?
ThreadWeave 通过将模组的操作分为两部分来工作:数据处理和世界更改。
Minecraft 通常所有操作都在单个线程上运行。这意味着所有计算、AI、机器和方块逻辑都在竞争有限的性能资源。
ThreadWeave 通过安全地将开销大的计算移至后台线程来改变这一点。
- Minecraft 刻
- 从 BlockEntity 提取数据
- 在后台线程中运行模拟
- 获取结果(更新后的值)
- 将更改应用回世界
模组开发者只需定义:
- 使用哪些数据
- 应该进行哪些计算
- 如何应用结果
ThreadWeave 自动执行哪些操作?
- 从 BlockEntity 提取数据
- 在多个线程上运行计算
- 防止对游戏世界的非安全访问
- 安全地将结果应用回世界
- 管理刻时序(正常或加速模拟)
刻模式
WORLD_SYNCED
与 Minecraft 同步运行(每秒 20 次)
用于正常游戏逻辑
ISOLATED
独立于 Minecraft 速度运行
用于可以比游戏本身运行更快或更慢的模拟
安全性
- 从不在后台线程中访问 Minecraft 世界
- 计算期间只使用简单数据
- 所有世界更改都在主线程上安全进行
框架防止对以下内容的非安全访问:
- Level
- 实体
- BlockEntity
- 服务器内部结构
如何使用?
安装指南
repositories {
maven {
url = "https://api.modrinth.com/maven"
}
}
dependencies {
implementation "maven.modrinth:thread-weave:{version}"
}
别忘了在 mods.toml 中定义依赖,否则 Minecraft 会崩溃。
[[dependencies.${mod_id}]]
modId="thread_weave"
type="required"
versionRange="[{version},)"
ordering="AFTER" // 非常重要:在 ThreadWeave 之后加载你的模组
side="BOTH"
例如,你有 BlockEntity(你可以使用自定义类,无论是否是 Minecraft 的类)。
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 - 你需要设置哪些变量将用于异步处理。
public record ReactorData(int heat, int fuel) implements SimulationDelta {}
II - 然后你需要拆分处理过程。只需将所有计算移到一个单独的方法中。这里你需要添加 @SimulatedThread 注解。 TickMode 可以是 Isolated 或 WorldSynced。WorldSynced 将按 Minecraft 的 TPS 运行。Isolated 将以你设置的任何刻速度运行,不依赖 Minecraft。
//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 - 最后,你需要将数据提交给框架。使用静态方法 "submit"。
@Override
public void tick() {
if (level == null || level.isClientSide()) return;
Simulations.submit(this);
System.out.println("heat=" + this.getHeat() + " fuel=" + this.getFuel());
}
IV - 你还需要设置哪些类将被扫描以查找 @SimulatedThread 注解。
public YourMainModClass(IEventBus modEventBus, ModContainer modContainer) {
modEventBus.addListener(this::commonSetup);
Simulations.submitScan(NavMeshSimulation.class);
...
}
你还可以通过 @SimulatedInterpreter 和 @SimulatedExtractor 注解手动控制解释器和提取器。
例如,工作刻:
@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++;
}
上面的例子并非最佳。自定义解释器在按区域处理时更为有用,例如合并结果(烘焙导航网格生成)。
@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());
}
这是巅峰优化吗?
不。还有很多工作要做。你可以在我的 Discord 服务器中关注进展。
例如,大多数 TickingBlockEntity 由于需要 Level(目前)而与此库配合不佳。
P.S: 我稍后会发布 Fabric 移植版和其他版本 [1.7.10 - 26.x]。
正在加载版本记录…

正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。