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 声明了一个与包围盒同名的字段,栈平衡会算错,产出坏字节码。

已知问题

  1. 源码自陈:PUTFIELD 分支假设 double(:150 FIXME)。这是本类唯一的显式缺陷声明。
  2. 对 mod 侧遮蔽字段只能放弃、不能修正(:124-126):一旦某个 mod Block 子类自己声明了 minX 之类的字段,整个类被加入 blockSubclassExclusions,其所有包围盒访问都不再重定向 —— 包括那些本可以正确重定向的。日志 :124 告知用户,但无降级方案。
  3. 排除集是传递的(:87-90):子类继承自被排除的类时,子类也进排除集。这是正确的(否则会访问到错误的字段),但会逐代放大排除范围。
  4. isVanillaBlockSubclass 用 startsWith 字符串前缀(:65、:68-70):任何恰好叫 net/minecraft/block/BlockSomething 的 mod 类都会被当作原版 Block 子类,无需真正继承 Block。这可能造成误识别。
  5. LOG_SPAM 是 static final(:28):SystemProperties.REDIRECTOR_LOGSPAM 在类初始化时固定,无法运行期切换。
  6. 重定向目标 ThreadedBlockData 在 client/rendering/ 包(:31):把类加载期的变换与客户端渲染包强耦合,若该包被服务端侧加载会出问题(实际上 AngelicaTweaker 在服务端完全不注入,故安全)。

相关条目