integration/ 兼容层框架(framework)

覆盖范围

appeng.integration 包根目录的 7 个 .java 文件。

实测:find integration -maxdepth 1 -name '*.java' | wc -l = 7。

文件 行号 职责
IntegrationType.java 13 枚举。27 个常量,每个常量 = 一个兼容目标(modid + 显示名 + 侧)
IntegrationNode.java 21 单个目标的运行时状态机。反射加载模块、判断是否启用、三段式调用
IntegrationRegistry.java 21 枚举单例(INSTANCE,:23)。持有 Map<IntegrationType, IntegrationNode>,驱动三阶段
IntegrationStage.java 13 枚举。PRE_INIT / INIT / POST_INIT / FAILED / READY
IntegrationSide.java 13 枚举。CLIENT / SERVER / BOTH(3 个)
IIntegrationModule.java 13 接口。只有 2 个方法:init() throws Throwable(:15)、postInit()(:17)
IntegrationHelper.java 13 工具类。唯一方法 testClassExistence(Object, Class)(:15)

核心机制:ASM 构造器注入

这是本兼容层最容易被漏掉的一点。没有任何地方显式调用 IntegrationRegistry.INSTANCE.add(...) 来注册具体目标。

唯一的 add 调用点在 ASM 类转换器里:

// appeng/transformer/asm/ASMIntegration.java:48-51
public ASMIntegration() {
    for (final IntegrationType type : IntegrationType.values()) {
        IntegrationRegistry.INSTANCE.add(type);
    }
}

ASMIntegration(:36)实现 net.minecraft.launchwrapper.IClassTransformer, 由 FML 在类加载期调用。因此全部 27 个目标无条件进入注册表, 不依赖源码里任何一处 add() 调用。

⚠️ 这也意味着:IntegrationType 里多写一个常量、但没有对应的 appeng/integration/modules/<Name>.java 文件,运行时会失败 —— 见 IntegrationType 的两个死常量。

三阶段生命周期

FML 构造期   ASMIntegration()  →  add(每个 IntegrationType)     ASMIntegration.java:48-51

preInit      IntegrationRegistry.INSTANCE.init()                core/AppEng.java:198
             └─ 对每个 node 调 call(PRE_INIT)
                   ├─ 读配置 "ModIntegration" / <显示名去掉空格>    IntegrationNode.java:67-68
                   ├─ 判定 enabled = ON / OFF / AUTO(默认)          IntegrationNode.java:70-79
                   ├─ enabled ? loadClass + newInstance + 写 instance 字段   :82-85
                   │           : throw new ModNotInstalled(modID)          :87
                   └─ state = INIT                                    :90
             └─ 对每个 node 调 call(INIT)
                   └─ mod.init()  →  state = POST_INIT               :92-95

postInit     IntegrationRegistry.INSTANCE.postInit()            core/AppEng.java:211
             └─ 对每个 node 调 call(POST_INIT)
                   ├─ mod.postInit()  →  state = READY              :96-99
                   └─ 打印 "Integration Enable" / "Integration Disabled"  :109-118

失败处理:catch (Throwable t)(IntegrationNode.java:102)记录 failedStage 与 exception,置 state = FAILED(:103-105)。 ModNotInstalled 只打 info 不上报(:112),其它异常走 AELog.integration(...)(:113)。

侧过滤

IntegrationRegistry.add(IntegrationType)(:29-39):

条件 行为 行号
type.side == CLIENT 且物理侧是 SERVER 直接 return,不加入 30-32
type.side == SERVER 且物理侧是 CLIENT 直接 return,不加入 34-36

判定用 FMLLaunchHandler.side()(cpw.mods.fml.relauncher.Side)。

27 个目标里 3 个是 CLIENT 侧

IntegrationType.InvTweaks(:44)、IntegrationType.NEI(:46)、 IntegrationType.CraftGuide(:48)。其余 24 个是 BOTH。 IntegrationSide.SERVER 这个常量在 27 个目标里一个都没用到。

反射加载的硬性约定

IntegrationNode.call(PRE_INIT)(:82-85)做三件事,任何一步失败都会 把整个目标置为 FAILED:

this.classValue = this.getClass().getClassLoader().loadClass(this.name);
this.mod = (IIntegrationModule) this.classValue.getConstructor().newInstance();
final Field f = this.classValue.getField("instance");
f.set(this.classValue, this.setInstance(this.mod));

推论 —— 每个模块必须满足:

  1. 类名 = appeng.integration.modules. + IntegrationType 常量名 (前缀 PACKAGE_PREFIX 定义在 IntegrationRegistry.java:25)
  2. 实现 IIntegrationModule
  3. 有公开无参构造器
  4. 有 public static 名为 instance 的字段(getField 只找 public 字段)
  5. 构造器把 this 赋给 instance

查询 API

方法 行号 失败行为
isEnabled(IntegrationType) IntegrationRegistry.java:73 false
getInstance(IntegrationType) 79 throw new IllegalStateException(:85)
getInstanceIfEnabled(IntegrationType) 88 返回 null(:90-92)
getStatus() 57 逗号分隔的 NAME:ON / NAME:OFF 串(:65-66)

⚠️ getInstance 抛异常而 getInstanceIfEnabled 返回 null —— 调用方必须用对,否则未安装的 mod 会直接崩游戏。

isEnabled 会触发 node.isActive()(IntegrationNode.java:47-53), 后者在 PRE_INIT 时就地补做一次 call(PRE_INIT)(:48-50)。 所以「查询是否启用」本身可能有副作用。

相关条目