线程编织

线程编织

异步处理框架,用于安全的多线程模拟

ThreadWeave 是一个轻量级模拟框架,它允许你将高开销的逻辑从 Minecraft 线程移至其他线程执行,同时保证所有世界修改的线程安全。

- 此模组不会修改 Minecraft 或修补原版代码。这是一个为其他模组提供的库

  • 所有世界变更仍在主服务器线程上执行

隔离模拟由于线程竞态条件而无法直接访问动态世界(Live Level),因此它仅处理计算等任务。这听起来可能没什么用,除非你开始制作 AI、反应堆、计算机或更复杂的系统(如物理引擎)。

对世界执行的变化(例如放置方块)仍然依赖 Minecraft 线程,以避免产生竞态条件。

Minecraft 模组经常面临以下问题:

  • 主线程上的高负载刻逻辑
  • BlockEntity 内的繁重计算
  • 复杂系统(反应堆、AI、工厂)

ThreadWeave 允许你仅将计算部分移至异步执行,同时保持世界操作的安全性。

只允许在模拟方法中使用纯数据。

重要限制

模拟方法必须是纯方法:

  • 输入 DTO -> 输出 DTO
  • 异步执行中不允许产生副作用

工作原理?

ThreadWeave 通过将模组的操作分离为两部分来工作:数据处理和世界变更。

Minecraft 通常将所有内容在单个线程上运行。这意味着所有计算、AI、机器和方块逻辑都在争夺同一有限的性能资源。

ThreadWeave 通过将繁重计算安全地移至后台线程来改变这一点。

  1. Minecraft 刻
  2. 从 BlockEntity 提取数据
  3. 在后台线程中运行模拟
  4. 获取结果(更新后的值)
  5. 将变更应用回世界

模组作者只需定义:

  • 使用哪些数据
  • 应执行哪些计算
  • 如何应用结果

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]。