
线程编织
异步处理框架,用于安全的多线程模拟
ThreadWeave 是一个轻量级模拟框架,它允许你将高开销的逻辑从 Minecraft 线程移至其他线程执行,同时保证所有世界修改的线程安全。
- 此模组不会修改 Minecraft 或修补原版代码。这是一个为其他模组提供的库
- 所有世界变更仍在主服务器线程上执行
隔离模拟由于线程竞态条件而无法直接访问动态世界(Live Level),因此它仅处理计算等任务。这听起来可能没什么用,除非你开始制作 AI、反应堆、计算机或更复杂的系统(如物理引擎)。
对世界执行的变化(例如放置方块)仍然依赖 Minecraft 线程,以避免产生竞态条件。
Minecraft 模组经常面临以下问题:
- 主线程上的高负载刻逻辑
- BlockEntity 内的繁重计算
- 复杂系统(反应堆、AI、工厂)
ThreadWeave 允许你仅将计算部分移至异步执行,同时保持世界操作的安全性。
只允许在模拟方法中使用纯数据。
重要限制
模拟方法必须是纯方法:
- 输入 DTO -> 输出 DTO
- 异步执行中不允许产生副作用
工作原理?
ThreadWeave 通过将模组的操作分离为两部分来工作:数据处理和世界变更。
Minecraft 通常将所有内容在单个线程上运行。这意味着所有计算、AI、机器和方块逻辑都在争夺同一有限的性能资源。
ThreadWeave 通过将繁重计算安全地移至后台线程来改变这一点。
- Minecraft 刻
- 从 BlockEntity 提取数据
- 在后台线程中运行模拟
- 获取结果(更新后的值)
- 将变更应用回世界
模组作者只需定义:
- 使用哪些数据
- 应执行哪些计算
- 如何应用结果
ThreadWeave 会自动完成哪些工作?
- 从 BlockEntity 提取数据
- 在多个线程上运行计算
- 防止对游戏世界的不安全访问
- 安全地将结果应用回去
- 管理刻的时序(普通或加速模拟)
刻模式
WORLD_SYNCED
与 Minecraft 同步运行(每秒 20 次)。
用于普通游戏逻辑。
ISOLATED
独立于 Minecraft 速度运行。
用于可以比游戏本身运行得更快或更慢的模拟。
安全性
- 后台线程永远不会访问 Minecraft 世界
- 计算期间仅使用简单数据
- 所有世界变更都在主线程上安全执行
框架防止对以下内容的不安全访问:
- Level
- Entities
- BlockEntities
- 服务器内部
如何使用?
安装指南
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());
}</code></pre>
IV - 同时,你需要指定哪些类将被扫描以查找 @SimulatedThread 注解。
public YourMainModClass(IEventBus modEventBus, ModContainer modContainer) {
modEventBus.addListener(this::commonSetup);
Simulations.submitScan(NavMeshSimulation.class);
...
}
你也可以通过 @SimulatedInterpreter 和 @SimulatedExtractor 注解手动控制解释器(Interpreter)和提取器(Extractor)。
例如:工作刻(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++;
}</code></pre>
上面的例子并不是最佳实践。自定义解释器在需要合并结果的按区域处理(例如 Baked Navmesh 生成)中更有用。
@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());
}</code></pre>
这算是极限优化吗?
不。还有很多工作要做。你可以在我的 Discord 服务器上跟踪进度。
例如,大多数 TickingBlockEntities 目前与此库配合得并不好,因为需要 Level 访问(目前如此)。
附注:我稍后会发布 Fabric 移植版和其他版本 [1.7.10 - 26.x]。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。