compat 派发机制
[!INFO] Git Commit:
93da3e1| Updated: 2026-10-01
本 mod 不硬编码 compat 列表,而是靠 @Compat 注解 + FML 的 ASMDataTable 在运行期扫描,
再用反射逐个实例化。全部逻辑集中在 StructureCompat.java、Compat.java 两个文件。
@Compat 注解
Compat.java 是一个只有单个成员的运行时注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Compat {
String[] value();
}
value() 是依赖的 modid 列表,不是类名。注意 RetentionPolicy.CLASS——注解写入 class 文件但不保留到
运行期反射,这正是它必须通过 FML 的 ASM 扫描而非 Class.forName 查注解的原因。
两阶段流程
阶段一:preInit 收集候选
for (ASMData d : event.getAsmData().getAll(Compat.class.getName())) {
if (((List<String>) d.getAnnotationInfo().get("value")).stream().allMatch(Loader::isModLoaded)) {
list.add(d.getObjectName());
}
}
compats = list;
StructureCompat.java:42-50。getAll() 扫描本 jar 中所有带 @Compat 的类型,对每个类型取出 value()
里的 modid 并要求全部 Loader.isModLoaded 为真,才把类名存进 compats。任何一个依赖 mod
缺失,该 compat 就整条被跳过——这是全有全无(AND)门控,不是 OR。
阶段二:postInit 反射实例化
for (String compat : compats) {
try {
Class.forName(compat).getConstructor().newInstance();
success += 1;
} catch (InvocationTargetException e) {
LOG.error("Compat activation errored!", e.getTargetException());
} catch (ReflectiveOperationException e) {
LOG.error("Cannot load!", e);
}
}
StructureCompat.java:59-69。注册逻辑全部写在每个 compat 类的构造函数里,无参构造即"启用"。
Class.forName 使用调用者的类加载器(StructureCompat 的 loader),所以能解析到
api 引入的 StructureLib 类型与 implementation 引入的其它 mod 类型。
全部 9 个 compat 及其门控 modid
| compat 类 | 门控 modid | 作用 |
|---|---|---|
CompatBaubles |
Baubles |
饰品栏物品提供者 |
CompatForestry |
Forestry |
林业背包物品提取器 |
CompatBackpackMod |
Backpack |
仅对末影背包开放自动取物 |
CompatAdventureBackpack |
adventurebackpack |
穿戴背包物品提供者 + 提取器 |
CompatAppliedEnergistics |
appliedenergistics2 |
AE2 无线终端 + 便携单元提取器 |
CompatWCT |
ae2wct |
WCT 无线合成终端提取器 |
CompatAE2FC |
ae2wct |
AE2FC 无线终端提取器 |
CompatRailcraft |
Railcraft(Railcraft.MOD_ID 常量) |
6 组多方块结构信息 |
CompatBogoSorter |
bogosorter |
中键整理事件拦截 |
两处值得注意的门控取值
CompatAE2FC的门控是ae2wct而不是 AE2FluidCraft 自己的 modid。 源码里写的是@Compat("ae2wct")(CompatAE2FC.java:20)。即 AE2FC 的兼容层只有在 WCT 也装的情况下才会加载。 由于dependencies.gradle中AE2FluidCraft-Rework传递性依赖WirelessCraftingTerminal(且显式exclude module: 'Baubles'),实际打包中两者必然同时存在,所以该门控成立。CompatRailcraft用的是编译期常量@Compat(Railcraft.MOD_ID)(CompatRailcraft.java:37), 而其余 8 个都是字符串字面量。两者运行时等价,但 Railcraft 的取值来自被依赖 jar 而非本仓库。
门控 modid 的大小写
上表中同时存在 Baubles、Forestry、Backpack、Railcraft 等首字母大写的 id,以及
adventurebackpack、appliedenergistics2、ae2wct、bogosorter 等全小写的 id。
Loader.isModLoaded 在 1.7.10 中是大小写不敏感的(内部按小写规范化),因此两种写法都能命中,
但风格上并不统一——@Mod 注解的 modid 惯例是小写,这里并不成立。
扩展方式
新增一个兼容层只需三步,无需改动 StructureCompat:
- 新建类,加
@Compat("目标modid"); - 把注册逻辑写进 public 无参构造函数;
- 在
dependencies.gradle里把对应 mod 加为implementation(或compileOnly)。
无需修改任何中央列表——这是 @Retention(CLASS) + ASMDataTable 扫描的收益。