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));
推论 —— 每个模块必须满足:
- 类名 =
appeng.integration.modules.+IntegrationType常量名 (前缀PACKAGE_PREFIX定义在IntegrationRegistry.java:25) - 实现
IIntegrationModule - 有公开无参构造器
- 有
public static名为instance的字段(getField只找 public 字段) - 构造器把
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)。
所以「查询是否启用」本身可能有副作用。