并行API

并行API

一个微小的共享API,让机器模组和加速器模组能够互相增强,而无需彼此依赖。

Parallel API 一个小型共享 API,让机器模组和加速模组能够互相加成,而无需彼此依赖。

English 这是什么 Parallel API 是一个小型库模组。它不添加任何方块、物品或配方。它存在的目的是解决一个特定问题:让加速器知道如何加速机器,而加速器无需知道它在与哪个机器模组交互。

如果你是玩家,你安装它很可能是因为另一个模组需要它。这是正常的——这个模组本身不会做任何事情。

它解决的问题 假设你有一个机器模组和一个加速器模组。

如果加速器直接依赖机器模组,会出现三个问题:

加速器只能加速那一个机器模组。之后发布的任何机器模组都得不到任何加成。 每次机器模组更新,加速器都必须重新构建并重新发布。 加速器变成了某个特定机器模组的附属,而不是一个独立的加速器。 还有一个更糟糕的失败情况。如果两个模组各自定义了自己的一份相同接口,加速器注册到其中一个,而机器查询另一个。结果是:

加速静默失效,并且不会报告任何错误。游戏启动,机器运行,倍率始终为 1。

这类 bug 极难排查,因为表面上没有任何异常。

这个模组如何解决它 Parallel API 将接口集中在一个地方,双方都依赖它:

复制 machine mod ─┐ ├─→ Parallel API ←─ accelerator mod machine mod ─┘ 机器模组永远不需要知道加速器的存在。加速器永远不需要为新的机器模组更改一行代码。任何采用此 API 的机器模组都会自动被所有支持它的加速器加成。

面向玩家 无需配置。安装后即可忘记它。 本身不向游戏添加任何内容。没有方块、没有物品、没有配方、没有世界生成。 可以安全地从现有世界添加或移除,取决于依赖它的模组。 前置要求 模组 版本 说明 Minecraft 1.21.1 仅 NeoForge NeoForge 21.1+
Flux Networks Ultimate Addon 1.0.0+ 必需——见下文 Flux Networks 8.0.0+
为什么需要 Flux Networks Ultimate Addon:并行值相乘后会增长得非常快,因此普通整数不足以容纳它们。此 API 使用的大数类型来自 Flux Networks Ultimate Addon 的内嵌 overlay,而不是来自 Flux Networks 本身。由于该类型直接出现在此 API 的公开接口中,缺少该模组会在加载时崩溃,而不是优雅降级。

数值可能变得非常大 并行性是乘法关系。多个加速器通过相乘叠加,而不是相加。

举一个具体的例子:压缩火炬加速器的最高等级贡献 172,186,884 倍。两个加在一起就是该数值的平方——约 2.96e16——远远超出 32 位整数所能容纳的范围(约 2.1e9)。

因此,Parallel API 全程以无界表示形式携带这些值,只在少数真正需要普通整数的边界处收窄一次。

预期情况:放置一个最高等级加速器后,机器界面中应显示恰好 172,186,884 的倍率。如果你看到的是它的平方(约 2.96e16),那么该加速器注册了两次——请向其作者报告。

面向模组开发者 声明依赖:

toml 复制 [[dependencies.${mod_id}]] modId="parallelapi" type="required" versionRange="[1.0.0,)" ordering="AFTER" side="BOTH" 机器模组发布其并行数,并让 API 合并所有已注册来源:

java 复制 AbsoluteInteger result = ParallelismRegistry.apply(machine, baseParallelism); 加速器模组实现一个方法并注册它:

java 复制 private static final ParallelismProvider PROVIDER = MyAccelerator::multiplier; ParallelismRegistry.registerProvider(PROVIDER); 两个会静默失败的错误 这两种情况都会产生一个能加载、能运行,但静默出错的模组。

  1. 注册内联方法引用。注册表按对象身份对 provider 去重,而 Java 每次表达式运行时都会求值为一个新对象。如果注册代码运行两次——开发热重载就足够了——内联引用会注册第二个 provider,倍率就会被应用两次。一个 4x 加速器会静默变成 16x。

将 provider 存储在 static final 字段中,并用标志位保护注册。

  1. 交出缓存值。大数类型是可变的:它的若干操作会就地修改对象。如果 provider 返回自己缓存的实例,任何修改它的调用者都会永久破坏服务器上每台机器看到的倍率。

每次调用返回一个新实例,并在暴露任何缓存值之前先复制。

还有一点需要注意 你的 provider 会在客户端被调用,而不仅仅是在服务端——机器的屏幕在渲染时会请求倍率。方块实体的 level 字段在菜单构建期间也可能为 null。这两种情况都要处理。

中文 这是什么 Parallel API 是一个很小的库模组。它不添加任何方块、物品或配方。

它只解决一个问题:让加速件知道如何加速一台机器,而加速件不需要知道自己在和哪个机器模组打交道。

如果你是玩家,你多半是因为别的模组需要它才装上的。这是正常的——它本身不产生任何游戏内容。

它解决的问题 假设你有一个机器模组和一个加速件模组。

如果加速件直接依赖机器模组,会出现三个问题:

加速件只能加速这一个机器模组。之后出现的其它机器模组它一个都帮不上。 机器模组每次更新,加速件都得跟着重新构建、重新发布。 加速件变成了"某个特定机器的附属",而不是一个独立可用的加速件。 还有更糟的情况。如果两个模组各自定义了一份同名接口,就会出现:加速件注册到其中一份,机器去另一份里查询。结果是:

加速完全不生效,而且不报任何错误。游戏能启动,机器能运行,倍率永远是 1。

这类问题极难排查,因为表面上一切正常。

这个模组如何解决 Parallel API 把接口集中在一处,双方都依赖它:

复制 机器模组 ─┐ ├─→ Parallel API ←─ 加速件模组 机器模组 ─┘ 机器不需要知道加速件的存在,加速件也不需要为新机器改一行代码。任何接入本接口的机器模组,都会自动获得所有支持它的加速件的加成。

对玩家而言 无需配置,装上即可。 本身不向游戏添加任何内容:没有方块、没有物品、没有配方、没有世界生成。 对已有存档可以安全地添加或移除(取决于依赖它的模组)。 前置要求 模组 版本 说明 Minecraft 1.21.1 仅 NeoForge NeoForge 21.1+
Flux Networks Ultimate Addon 1.0.0+ 必需,见下方说明 Flux Networks 8.0.0+
为什么必需 Flux Networks Ultimate Addon: 并行数是相乘关系,增长极快,普通整数装不下。本 API 使用的大数类型来自 Flux Networks Ultimate Addon 的内嵌 overlay,不在 Flux Networks 本体中。由于该类型直接出现在本 API 的公开接口里,缺少这个前置会导致加载阶段崩溃,而不是功能降级。

数值可能非常大 并行数是相乘的,多个加速件叠加是乘法而非加法。

举一个具体的例子:压缩火炬类加速件的最高档提供 172,186,884 倍。两个叠加就是它的平方,约 2.96e16,远超 32 位整数的范围(约 21 亿)。

因此 Parallel API 全程使用无上限的数值表示,只在少数真正必须使用普通整数的边界处收窄一次。

你应该看到什么: 放置一个最高档加速件后,机器界面显示的倍率应当恰好是 172,186,884。如果你看到的是它的平方(约 2.96e16),说明该加速件被重复注册了,请向其作者反馈。

模组开发者说明 声明依赖:

toml 复制 [[dependencies.${mod_id}]] modId="parallelapi" type="required" versionRange="[1.0.0,)" ordering="AFTER" side="BOTH" 机器模组公开自身的并行数,由 API 乘入所有已注册来源:

java 复制 AbsoluteInteger result = ParallelismRegistry.apply(machine, baseParallelism); 加速件模组实现一个方法并注册:

java 复制 private static final ParallelismProvider PROVIDER = MyAccelerator::multiplier; ParallelismRegistry.registerProvider(PROVIDER); 两个会静默失败的错误 这两种写法都会让模组正常加载、正常运行,而结果是错的。

一、内联写方法引用。 注册表按对象身份去重,而 Java 每次求值方法引用都会产生新对象。如果注册代码执行两次(开发时热重载就足够了),内联引用会注册出第二个加速件,倍率被应用两遍——4 倍的加速件悄悄变成 16 倍。

请把 provider 存到 static final 字段,并用标志位保证只注册一次。

二、交出缓存的数值对象。 大数类型是可变的:它的若干运算会就地修改对象。如果 provider 返回自己缓存的那个实例,任何调用方改一下,就会永久污染服务器上所有机器看到的倍率。

请每次返回新实例,对外暴露缓存值前先复制。

还有一点需要注意 你的 provider 会在客户端被调用,不只在服务端——机器界面渲染时会查询倍率。同时,菜单构造期间机器的世界字段可能为空。这两种情况都要处理。