数据注册表(DataRegistry / DataRegistryImpl)
基本信息
| 属性 | 值 |
|---|---|
| 对外类 | com.falsepattern.chunk.api.DataRegistry(@ApiStatus.NonExtendable) |
| 实现类 | com.falsepattern.chunk.internal.DataRegistryImpl(558 行,internal 包,标注 @ApiStatus.Internal) |
| 注册键 | domain + ":" + id,如 minecraft:blockid |
| 注册窗口 | 只允许在 LoaderState.INITIALIZATION(init 阶段) |
| 停用窗口 | 只允许在 LoaderState.POSTINITIALIZATION(postInit 阶段) |
注册:时机与规则
DataRegistryImpl.registerDataManager(DataManager, int) 依次做四件事:
- 查阶段——
Loader.instance().getLoaderState() != LoaderState.INITIALIZATION即抛IllegalStateException("ChunkDataManager registration is not allowed at this time! Please register your ChunkDataManager in the init phase.") - 查重——
managersUnordered.containsKey(id)即抛IllegalArgumentException("ChunkDataManager " + manager + " has a duplicate id!") - 查停用名单——若
disabledManagers已含该 id,静默 return(注释:“If the manager was disabled, do not add it to the list of managers.”),让"先停用、后注册"两种写法都成立 - 按能力分流——见下
按 instanceof 分流到 6 个集合
| 集合 | 装入条件 | 元素类型 |
|---|---|---|
managersUnordered / managers |
总是 | OrderedManager / 有序集合 |
packetManagers |
instanceof PacketDataManager |
PacketManagerInfo(含 maxPacketSize) |
cubicPacketManagers |
instanceof CubicPacketDataManager |
CubicPacketManagerInfo |
blockPacketManagers |
instanceof BlockPacketDataManager |
DataManager.BlockPacketDataManager |
NBTManagers |
instanceof StorageDataManager |
DataManager.StorageDataManager |
chunkNBTManagers |
StorageDataManager 且 instanceof ChunkDataManager |
有序 map |
subChunkNBTManagers |
StorageDataManager 且 instanceof SubChunkDataManager |
有序 map |
⚠️ BlockIDManager 一个类同时命中 PacketDataManager + CubicPacketDataManager + BlockPacketDataManager + SubChunkDataManager 四条分流(见 内置原版数据管理器)。
ordering 排序索引
registerDataManager 的第二个参数。源码注释给出的完整约定:
"The natural ordering index for the data manager when iterating the list of managers. ChunkAPI, Lumi, RPLE, and EndlessIDs all use 0. Negative numbers are sorted earlier. Positive numbers are sorted later. Use 0 unless you specifically need to do something later. As a convention, do this in increments of 1000 so that other people can order “between” your manager and the base managers."
内建 6 个 manager 全部传 0(ChunkAPI.java 的 init 方法)。@Deprecated 的单参重载同样隐式传 0。
排序由 DualMap 实现:内部同时维护一个 HashMap<String, T>(无序、按 id 查)与一个 TreeMap<OrderedManager, T>(有序),put/remove 时比较两边返回值,不同则 throw new AssertionError()——即两张表必须永远一致。
停用(disable)
disableDataManager(String domain, String id):
- 查阶段——非
POSTINITIALIZATION抛IllegalStateException,消息是 “ChunkDataManager disabling is not allowed at this time! Please disable any ChunkDataManagers in the postInit phase.” - 打 debug 日志并附带一份
new Throwable()栈,原文:"Disabling ChunkDataManager " + id + " in domain " + domain + “. See the stacktrace for the source of this event.\nThis is NOT an error.” - 从
managersUnordered摘除;若存在则同步清理managers并从全部 6 个能力集合中移除,同时回退maxPacketSize/maxPacketSizeCubic累加值(按同样的4 + id.length + 4 + maxSize公式减去) - 把 id 加入
disabledManagers,使之后的注册尝试被静默忽略
包体积预算:maxPacketSize 累加
静态字段初值 maxPacketSize = 4 与 maxPacketSizeCubic = 4。每注册一个 packet manager 就累加:
maxPacketSize += 4 + id.getBytes(UTF_8).length + 4 + maxSize
这个值最终被 S21PacketChunkData.func_149275_c() 返回(见 Mixin 清单),决定了区块压缩/解压缓冲区的大小。注册期只算一次,所以 manager 必须在 init 阶段定型。
内置 6 个 manager 各自的 maxPacketSize() / maxPacketSizeCubic() 实测值:
| manager | 注册键 | maxPacketSize() |
maxPacketSizeCubic() |
|---|---|---|---|
BlockIDManager |
minecraft:blockid |
98306 | 6146 |
MetadataManager |
minecraft:metadata |
32768 | 2048 |
BlocklightManager |
minecraft:blocklight |
32768 | 2048 |
SkylightManager |
minecraft:skylight |
32768 | 2048 |
BiomeManager |
minecraft:biome |
256 | — |
⚠️ 这是 BlockIDManager 一家就占了预算的 50%:98306 来自 HEADER_SIZE(2) + 16 × (LSB 4096 + MSB 2048)。上表数值均由源码常量推算;按 4 + Σ(4 + id长度 + 4 + maxSize) 公式,5 个 packet manager 累加后 maxPacketSize = 197018。
查询
| 方法 | 返回 |
|---|---|
getRegisteredManagers() |
Set<String>,Collections.unmodifiableSet(managersUnordered.keySet())。在 DataRegistry 上标了 @Deprecated |
getRegisteredManagersOrdered() |
SortedSet<OrderedManager>,Collections.unmodifiableSortedSet(managers)。推荐用这个,它同时带排序索引 |
落盘 NBT 的命名空间
createManagerNBT / getManagerNBT 决定非特权 manager 的数据落在哪:
- 非特权(
privileged = false):root → root[domain] → root[domain][id],逐层getCompoundTag/setTag,不存在就新建 - 特权(
privileged = true):直接返回root本身,不加任何命名空间
内建 6 个 manager 全部是特权(见 内置原版数据管理器),因为它们重实现原版字段名(Biomes、Blocks、Add、Data、SkyLight、BlockLight、HeightMap、LightPopulated、TerrainPopulated、LastUpdate 等)。第三方 manager 若不特权,存档里就会出现 mymod:mymanager 这样的子标签。
未知 manager 的降级处理
readFromBuffer / readFromBufferCubic 遇到不认识的注册键时:
- 打 error:"Received data for unknown PacketDataManager " + id + “. Skipping.”
buf.position(buf.position() + length)跳过该段,不抛异常——即服务端有新 manager、客户端没有时,存档与网络包仍然可用(缺的那部分数据静默丢失)- 长度超过登记的
maxPacketSize也只打 error:"Received packet larger than max size for PacketDataManager " + id + “! Continuing anyways, things might break!”
readBlockPacketFromBuffer 则没有这层保护:blockPacketManagers.get(id) 返回 null 时直接 NPE。
源码中的不一致
| 位置 | 现象 |
|---|---|
S22PacketMultiBlockChangeMixin 的 writePacketData |
subPackets == null 分支写的是 data.writeInt(0),而正常分支写的是 data.writeVarIntToBuffer(subPackets.length)——两个分支的编码方式不一致,读侧也按 varInt 解析。原文未修正 |
DataRegistry.getRegisteredManagers() |
标了 @Deprecated 但 DataRegistryImpl 侧的同名方法没有;且 disableDataManager / getRegisteredManagers 等均未标 @Contract |
ChunkAPI.java 内建注册顺序 |
6 个 manager 全传 ordering 0,实际遍历序退化成按 id 字符串字典序(blockid → blocklight → biome → metadata → skylight),并非源码里的书写顺序 |
相关条目
- API 接口 —
DataManager六接口与DataRegistry门面定义 - 序列化流水线 —
DataRegistryImpl的各分发方法在何时被调用 - 核心 mod 与读档闸门 —
readLevelDat/writeLevelDat如何用StorageDataManager的版本方法做兼容检查 - 内置原版数据管理器 — 6 个内建 manager 的注册键与特权访问