IntegrationNode(appeng.integration.IntegrationNode)
基本信息
| 属性 | 值 |
|---|---|
| 源码 | appeng/integration/IntegrationNode.java |
| 类声明 | 21,public final class |
| 构造器 | :34,四参数 (String displayName, String modID, IntegrationType shortName, String name) |
| 持有者 | IntegrationRegistry.modules(IntegrationRegistry.java:27) |
| 初始状态 | state = IntegrationStage.PRE_INIT(:26) |
职责
单个兼容目标的运行时状态机:判断是否启用 → 反射加载模块类 →
按 PRE_INIT / INIT / POST_INIT 三段调用 → 失败则隔离。
字段
| 字段 | 行号 | 说明 |
|---|---|---|
displayName |
23 | 用于配置项名(IntegrationNode.java:68 去掉空格) |
modID |
24 | 可为 null(见 IntegrationType) |
shortName |
25 | 其实是 IntegrationType 枚举,不是短名 |
state |
26 | 当前阶段 |
failedStage |
27 | 失败发生在哪一段 |
exception |
28 | 失败异常 |
name |
29 | 模块类的全限定名 |
classValue |
30 | 反射拿到的 Class |
instance |
31 | 模块实例 |
mod |
32 | 同一实例,类型为 IIntegrationModule |
启用判定(:63-91 的 PRE_INIT 分支)
顺序严格:
AEConfig.instance.addCustomCategoryComment("ModIntegration", ...)(:64-66)—— 首次运行才加配置注释,说明值域是AUTO/ON/OFF。- 读配置:
AEConfig.instance.get("ModIntegration", displayName.replace(" ", ""), "AUTO")(:67-68)。默认AUTO。 - 三分支(
:70-79):
| 配置值 | enabled |
|---|---|
ON(忽略大小写) |
true |
OFF(忽略大小写) |
false |
其它(含 AUTO) |
modID == null || ModAPIManager.INSTANCE.hasAPI(modID) || Loader.isModLoaded(modID) |
enabled→ 反射加载(:82-85);否则throw new ModNotInstalled(modID)(:87)。
⚠️ 目标 mod 没装时是否安全跳过:是。Loader.isModLoaded 返回 false
→ 抛 ModNotInstalled → :102 捕获 → state = FAILED → 仅打 info 日志
(:112 明确排除 ModNotInstalled 不走 AELog.integration)。
其余 26 个目标全部走这条路,不会因缺 mod 而崩游戏。
⚠️ 但配置项名是 displayName.replace(" ", "")(:68)——
例如 CoFHWrench 的配置键是 CoFHWrench,
IC2 的是 IC2,RF 的是 RedstoneFluxPower-Tiles,
RFItem 的是 RedstoneFluxPower-Items。两个 CoFHAPI 目标配置键不同。
反射加载的 4 条硬约束
// IntegrationNode.java:82-85
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. + 常量名(PACKAGE_PREFIX,IntegrationRegistry.java:25) |
ClassNotFoundException |
| 2 | implements IIntegrationModule |
ClassCastException |
| 3 | 有公开无参构造器 | NoSuchMethodException |
| 4 | 有 public static instance 字段(getField 只返回 public 字段) |
NoSuchFieldException |
RB(:40)与 CraftGuide(:48)违反约束 1 ——
appeng/integration/modules/RB.java 与 CraftGuide.java 不存在,
运行时必失败。详见 IntegrationType。
阶段跃迁的幂等保护
// IntegrationNode.java:56-59
if (this.getState() != IntegrationStage.FAILED) {
if (this.getState().ordinal() > stage.ordinal()) {
return;
}
FAILED之后所有后续阶段调用直接跳过(:56)- 已推进到更后阶段时,重复调早阶段直接返回(
:57-59)
IntegrationRegistry.init() 因此把 PRE_INIT 与 INIT 分两轮遍历
(IntegrationRegistry.java:42-48)是安全的。
isActive() 的副作用
// IntegrationNode.java:47-53
boolean isActive() {
if (this.getState() == IntegrationStage.PRE_INIT) {
this.call(IntegrationStage.PRE_INIT);
}
return this.getState() != IntegrationStage.FAILED;
}
getState() != FAILED 即为「启用」——
PRE_INIT 本身也算启用状态(不是 READY 才算)。
⚠️ 「查询」会触发加载:IntegrationRegistry.isEnabled(name)(:73)
→ node.isActive() → 可能当场 loadClass + newInstance。
getInstanceIfEnabled(:88)同样。这在
ASMIntegration 的类转换阶段是可行的
(所有目标此时都已 add),但意味着查询不是纯函数。
日志时机
call(stage) 尾部(:109-118)只在 stage == POST_INIT 时打印:
| 情况 | 输出 | 行号 |
|---|---|---|
失败且原因是 ModNotInstalled |
AELog.info(displayName + " - Integration Disabled") |
111 |
| 失败(其它原因) | 同上 + AELog.integration(exception) |
111 + 113 |
| 成功 | AELog.info(displayName + " - Integration Enable") |
116 |
措辞是 Enable(无 d),源码原文如此。
toString() 与包私有访问器
toString()(:42-45)格式为 SHORTNAME:STATE。
getInstance(:121)、getShortName(:130)、getState(:134)、
isActive(:47)、call(:55)都是包私有 ——
只有 IntegrationRegistry(同包)能用。