CeleritasBlockTransform
基本信息
| 属性 | 值 |
|---|---|
| 类 | com.gtnewhorizons.angelica.loading.shared.transformers.CeleritasBlockTransform |
| 路径 | src/main/java/com/gtnewhorizons/angelica/loading/shared/transformers/CeleritasBlockTransform.java |
| 行数 | 172 —— shared/transformers/ 中最大的类 |
| 类型 | public final class ... implements Opcodes(:26) |
实现 Opcodes |
:26 直接 implements Opcodes,因此全类可直接用 GETFIELD/PUTFIELD/INVOKESTATIC 常量 |
| 驱动者 | CeleritasBlockTransformer + RFBCeleritasBlockTransformer |
它解决什么问题
1.7.10 的 net.minecraft.block.Block 声明了 6 个 double 包围盒字段(minX/minY/minZ/maxX/maxY/maxZ)。Celeritas 把区块构建搬到多线程后,读 Block 的包围盒字段成为竞态点(一个线程可能正在 setBlockBounds)。
解法:把这 6 个字段从 Block 上删除,把所有 GETFIELD/PUTFIELD 访问重定向到 Angelica 自己的线程安全包装 com.gtnewhorizons.angelica.client.rendering.ThreadedBlockData(:31),它在字段前插入 ThreadedBlockData.get(Block) 把 Block 变成包装对象。
构造期的字段名映射(:47-62)
final List<Pair<String, String>> mappings = ImmutableList.of(
Pair.of("minX", "field_149759_B"), // 左 = MCP 名, 右 = SRG 名
Pair.of("minY", "field_149760_C"),
Pair.of("minZ", "field_149754_D"),
Pair.of("maxX", "field_149755_E"),
Pair.of("maxY", "field_149756_F"),
Pair.of("maxZ", "field_149757_G")
);
for (Pair<String, String> pair : mappings) {
final String name = isObf ? pair.getRight() : pair.getLeft();
this.fieldNameToRedirect.put(name, pair.getLeft()); // 值恒为 MCP 名
}
映射表是构造期依据 isObf 单向建立的:混淆环境用 SRG 名做 key,非混淆用 MCP 名做 key,值永远是 MCP 名。:143 注释「use unobfuscated name」——因为重定向后的字段在 ThreadedBlockData 里是以 MCP 名声明的。
状态字段(:41-45)
| 字段 | 类型 | 用途 |
|---|---|---|
fieldNameToRedirect |
Map<String,String> |
上面的 6 项映射 |
moddedBlockSubclasses |
Set<String>(newSetFromMap(new ConcurrentHashMap<>()),:42) |
已识别的 mod Block 子类 |
blockSubclassExclusions |
同上(:44) |
阴影了包围盒字段、因而不重定向的类 |
cstPoolParser |
ClassConstantPoolParser |
预筛选(:61 用 fieldNameToRedirect.keySet() 构造) |
:43 注释说明 blockSubclassExclusions 的存在理由:「Block 子类的 owner 我们不该重定向,因为它们**遮蔽(shadow)**了我们想重定向的字段」。
公开 API
| 方法 | 行 | 语义 |
|---|---|---|
trackBlockSubclasses(className, superClassName) |
:83-92 |
对每个类都要调(含不打算变换的) |
getTransformerExclusions() |
:94-101 |
4 个 classloader 排除前缀 |
shouldTransform(byte[]) |
:103-105 |
cstPoolParser.find() 预筛选 |
transformClassNode(transformedName, cn) |
:108-117 |
返回是否变更 |
trackBlockSubclasses 的注释(:80-82)是本类最重要的约束:
This method needs to be called for every class, including the ones we don’t want to transform. Vanilla blocks are recognized by name; only modded subclasses need tracking, and FML’s EventSubscriptionTransformer guarantees a modded class’s superclass loads first.
原版 Block 子类识别(:33-39, :64-78)
VanillaBlockExclusions 列 4 项「net.minecraft.block.Block* 之下但不是 Block 子类」的干扰项:
| 排除前缀 | 行 |
|---|---|
net/minecraft/block/IGrowable |
:34 |
net/minecraft/block/ITileEntityProvider |
:35 |
net/minecraft/block/BlockEventData |
:36 |
net/minecraft/block/BlockSourceImpl |
:37 |
net/minecraft/block/material/ |
:38 |
:32 注释:「net.minecraft.block.Block* 下的所有类都是 Block 子类,除了这些」。
transformClassNode 的两个分支(:108-117)
| 条件 | 动作 | 行 |
|---|---|---|
cn.name 恰为 net/minecraft/block/Block |
cn.fields.removeIf(field -> fieldNameToRedirect.containsKey(field.name)) —— 删除 6 个字段声明 |
:110-111 |
| 其它 | trackBlockShadowingFields(cn) —— 检测并记录遮蔽者 |
:112-114 |
之后两者都执行 redirectBlockBoundFields(cn, transformedName)(:115)。
trackBlockShadowingFields(:119-130)只检查已知的 mod 子类(:121),注释 :120 说明理由:「只有 mod 子类能遮蔽;原版只在 Block 里声明这些字段」。
redirectBlockBoundFields 的两条指令序列(:132-171)
命中条件(:138):GETFIELD/PUTFIELD + 名字在映射表 + owner 是已知 Block 子类 + owner 不在排除集。
| 原指令 | 注入序列 |
|---|---|
GETFIELD |
INVOKESTATIC ThreadedBlockData.get(Block)ThreadedBlockData(:146-148) |
PUTFIELD |
DUP2_X1 → POP2 → INVOKESTATIC get → DUP_X2 → POP(:152-163) |
PUTFIELD 分支的栈注释(:150-161)逐行写明:
// FIXME: this code assumes doubles
// Stack: Block, double
DUP2_X1 // Stack: double, Block, double
POP2 // Stack: double, Block
getter // Stack: double, ThreadedBlockData
DUP_X2 // Stack: ThreadedBlockData, double, ThreadedBlockData
POP // Stack: ThreadedBlockData, double
:150 的 // FIXME: this code assumes doubles 是源码自陈的已知限制:若 mod 用 float 声明了一个与包围盒同名的字段,栈平衡会算错,产出坏字节码。
已知问题
- 源码自陈:
PUTFIELD分支假设 double(:150FIXME)。这是本类唯一的显式缺陷声明。 - 对 mod 侧遮蔽字段只能放弃、不能修正(
:124-126):一旦某个 mod Block 子类自己声明了minX之类的字段,整个类被加入blockSubclassExclusions,其所有包围盒访问都不再重定向 —— 包括那些本可以正确重定向的。日志:124告知用户,但无降级方案。 - 排除集是传递的(
:87-90):子类继承自被排除的类时,子类也进排除集。这是正确的(否则会访问到错误的字段),但会逐代放大排除范围。 isVanillaBlockSubclass用startsWith字符串前缀(:65、:68-70):任何恰好叫net/minecraft/block/BlockSomething的 mod 类都会被当作原版 Block 子类,无需真正继承Block。这可能造成误识别。LOG_SPAM是static final(:28):SystemProperties.REDIRECTOR_LOGSPAM在类初始化时固定,无法运行期切换。- 重定向目标
ThreadedBlockData在client/rendering/包(:31):把类加载期的变换与客户端渲染包强耦合,若该包被服务端侧加载会出问题(实际上 AngelicaTweaker 在服务端完全不注入,故安全)。
相关条目
- CeleritasBlockTransformer - FML 侧包装(ASM5)
- RFBCeleritasBlockTransformer - RFB 侧包装(newestAsmVersion)
- TileEntityMarkerTransform - FML 侧由同一个 wrapper 驱动
- shared/AngelicaRedirector - 同一
transform字面量的第 2 处 - AngelicaClassDump - 传入的 dump 单例
- AngelicaTweaker - 服务端不注入的保证
- shared/package-info - 本包的类加载约束