TileEntityMarkerTransform

基本信息

属性 值
类 com.gtnewhorizons.angelica.loading.shared.transformers.TileEntityMarkerTransform
路径 src/main/java/com/gtnewhorizons/angelica/loading/shared/transformers/TileEntityMarkerTransform.java
行数 137
类型 public final class(:20)
公开常量 3 个 marker 位掩码(:22-24)
内部类型 private record Marker(int bit, String method, String desc, String iface)(:43)
内部类 private final class MarkerScanner extends ClassVisitor(:116-136)
驱动者 CeleritasBlockTransformer 与 RFBCeleritasBlockTransformer

类级 Javadoc 说明的问题(:15-18)

Marks TileEntity subclasses that declare getDescriptionPacket, shouldRenderInPass or getDistanceFrom with a corresponding interface, so runtime code can test with instanceof instead of reflection.

即:把「这个 TileEntity 是否覆写了某方法」从运行期反射变成类加载期打标记 + 运行期 instanceof。这是典型的反射性能优化。

3 个 marker(:22-24)

位常量 值 生成的接口
MARK_DESCRIPTION_PACKET 1 << 0 SendsDescriptionPacket
MARK_SHOULD_RENDER_IN_PASS 1 << 1 OverridesShouldRenderInPass
MARK_GET_DISTANCE_FROM 1 << 2 OverridesGetDistanceFrom

目标方法表(:47-50)

marker 方法名 混淆名 描述符 接口 FQN
DESCRIPTION_PACKET getDescriptionPacket func_145844_m ()Lnet/minecraft/network/Packet; com/gtnewhorizons/angelica/mixins/interfaces/SendsDescriptionPacket
SHOULD_RENDER_IN_PASS shouldRenderInPass (无混淆名) (I)Z .../OverridesShouldRenderInPass
GET_DISTANCE_FROM getDistanceFrom func_145835_a (DDD)D .../OverridesGetDistanceFrom

只有 2 个方法需要混淆名分支 —— shouldRenderInPass 是 Forge 添加的方法,不存在混淆名,构造时直接写字面量。

构造参数 asmApi 的必要性(:36-42)

/**
 * A visitor refuses to visit attributes newer than its api level, such as the NestMember one javac emits for
 * classes with nested classes, so the caller passes the highest level its class loader supports.
 */
private final int asmApi;

这是本类唯一有解释性注释的字段,也是两条加载路径行为差异的根因:

Wrapper asmApi 取值 后果
CeleritasBlockTransformer :22 Opcodes.ASM5(硬编码) 带嵌套类的 TileEntity 的 NestMember 属性被拒绝访问
RFBCeleritasBlockTransformer :25 RetroFuturaBootstrap.API.newestAsmVersion() 正确处理

4 个公开/包级方法

方法 行 语义
track(className, superClassName) :62-64 父类是 TileEntity 则记录子类
markersFor(className, classBytes) :66-72 返回缺失的 marker 位掩码(declared & ~alreadyPresent)
addMarkers(byte[], int) :74-92 字节码版:用 ClassVisitor 覆写 visit 追加接口
addMarkers(ClassNode, int) :94-96 AST 版:Collections.addAll(cn.interfaces, ...)

isTileEntity(:58-60)

className != null && (className.startsWith(TILE_ENTITY) || tileEntities.contains(className))

TILE_ENTITY = "net/minecraft/tileentity/TileEntity"(:35)。用 startsWith 前缀识别原版(与 CeleritasBlockTransform.isVanillaBlockSubclass 同一手法),用 tileEntities 集合识别 mod 的。

markersFor 的两级早退(:67)

if (TILE_ENTITY.equals(className) || !isTileEntity(className) || !prefilter.find(classBytes)) return 0;
条件 理由
类就是 TileEntity 本身 跳过基类自身
不是 TileEntity 子类 跳过无关类
常量池预筛选未命中 无需建 ClassReader

预筛选用 SCAN_FLAGS = SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES(:36)—— 只读方法声明,跳过字节码。

幂等性设计

MarkerScanner(:116-136)用两个累加器:

字段 行 含义
declared :118、:133 类里声明了哪些 marker 方法(bitOf(access, name, desc))
alreadyPresent :119、:128 类已实现哪些 marker 接口(bitOf(ifaceName))

:71 的 return scanner.declared & ~scanner.alreadyPresent; —— 返回「已声明但未标记」的部分,天然幂等:重复处理不会重复加接口。

bitOf(int access, String name, String desc)(:98-102)有过滤:(access & ACC_PUBLIC) == 0 || (access & ACC_STATIC) != 0 直接返回 0 —— 即只认 public 非 static 的方法。私有/静态的同名方法不算覆写。

已知问题

  1. FML 侧 ASM5 硬编码的后果(见上表):含嵌套类的 TileEntity 会被 ClassVisitor 拒访 NestMember 属性。这是两条加载路径间可观测的行为差异,且只在 FML 路径出现。
  2. shouldRenderInPass 无混淆名(:48):若某 mod 在混淆环境下把 Forge 方法也重命名,本 marker 静默失效。构造器对 3 个 marker 用了 2 种不同处理方式,没有统一规则。
  3. startsWith(TILE_ENTITY) 误识别:任何类名以 net/minecraft/tileentity/TileEntity 开头的类(无论是否真的继承)都被当作 TileEntity。与 CeleritasBlockTransform :65 的同类手法共享这个风险。
  4. markersFor 被重复调用:shouldTransformClass(RFB :69)与 transformClassIfNeeded(RFB :81)各调一次,每次都 new ClassReader(classBytes).accept(scanner, SCAN_FLAGS)(:70)。FML 侧靠 markers 局部变量只算一次(CeleritasBlockTransformer :42),RFB 路径白白扫两遍。
  5. 接口定义在 mixins/interfaces/ 包(:26-28):本类生成的是运行期可见接口,而这些接口定义在 mixin 源集相关的包里。物理位置与「面向外部 mod 的标记 API」的语义不太匹配,且仓库内无文档说明这 3 个接口是否为公开 API。

相关条目