内置原版数据管理器(internal.vanilla)
基本信息
| 属性 | 值 |
|---|---|
| 包 | com.falsepattern.chunk.internal.vanilla(@ApiStatus.Internal,不是 api 包) |
| 文件数 | 10(8 个类 + 1 个 package-info.java;package-info.java 单独计) |
| 基类 | VanillaManager(抽象)、NibbleManager(抽象,继承前者) |
| 具体 manager | 6 个,全部在 ChunkAPI.init 中以 ordering 0 注册 |
| domain | 全部硬编码 "minecraft"(由 VanillaManager.domain() 提供) |
这些类不对外暴露为 API(在 internal 包且标 @ApiStatus.Internal),但它们是理解 ChunkAPI 的关键:原版自己的区块字段被重新实现成 6 个 manager,ChunkAPI 因此能对 Biomes、Blocks、SkyLight 等原版字段做统一的分片打包与自定义压缩。
VanillaManager — 抽象基类
public abstract class VanillaManager implements DataManager
| 方法 | 返回值 | 说明 |
|---|---|---|
domain() |
"minecraft" |
硬编码 |
version() |
"" |
空串,不是 null——故内建 manager 永不被判为"版本不兼容" |
newInstallDescription() |
null |
无提示 |
uninstallMessage() |
"" |
空串 |
versionChangeMessage(String) |
null |
无警告 |
即:内建 manager 在 读档兼容闸门里全部视为"与原版完全兼容"。
NibbleManager — 半字节数组抽象基类
public abstract class NibbleManager extends VanillaManager implements DataManager.PacketDataManager, DataManager.CubicPacketDataManager
| 成员 | 值 / 签名 |
|---|---|
BYTES_PER_SUBCHUNK |
Common.BLOCKS_PER_SUBCHUNK / 2 = 2048 |
getNibbleArray(ExtendedBlockStorage) |
protected abstract NibbleArray — 子类实现 |
maxPacketSize() |
Common.SUBCHUNKS_PER_CHUNK * BYTES_PER_SUBCHUNK = 16 * 2048 = 32768 |
maxPacketSizeCubic() |
BYTES_PER_SUBCHUNK = 2048 |
writeToBuffer(Chunk, int, boolean, ByteBuffer) |
遍历 chunk.getBlockStorageArray(),对 subChunkMask 命中且 subChunk != null 的项 buffer.put(getNibbleArray(subChunk).data, 0, BYTES_PER_SUBCHUNK) |
readFromBuffer(Chunk, int, boolean, ByteBuffer) |
同上,buffer.get(...) |
⚠️ 两个 Chunk 级方法直接对 getNibbleArray(subChunk).data 取值,不判 data 为 null——writeToBuffer 只判了 subChunk != null。半字节数组本身为 null 时会 NPE。
6 个具体 manager
| 类 | id | 实现的接口 | subChunkPrivilegedAccess / chunkPrivilegedAccess |
|---|---|---|---|
BlockIDManager |
blockid |
PacketDataManager + CubicPacketDataManager + BlockPacketDataManager + SubChunkDataManager |
subChunkPrivilegedAccess() → true |
MetadataManager |
metadata |
BlockPacketDataManager + SubChunkDataManager(父类提供两个 Packet 接口) |
true |
SkylightManager |
skylight |
SubChunkDataManager(父类提供两个 Packet 接口) |
true |
BlocklightManager |
blocklight |
SubChunkDataManager(父类提供两个 Packet 接口) |
true |
LightingManager |
lighting |
ChunkDataManager |
chunkPrivilegedAccess() → true |
BiomeManager |
biome |
PacketDataManager + ChunkDataManager |
true |
⚠️ 四个继承 NibbleManager 的类因此都是 PacketDataManager,即使 MetadataManager / BlocklightManager / SkylightManager 的源码里看不到 implements PacketDataManager。这直接决定了 包体积预算。
各 manager 的字段与 NBT 键
所有键都是原版键名(靠特权访问直写),第三方同名字段会被覆盖。
| manager | 落盘 NBT 键 | 类型 / 长度 |
|---|---|---|
BlockIDManager |
Blocks |
byte[],4096(LSB) |
BlockIDManager |
Add |
byte[],2048(MSB,仅当 getBlockMSBArray() != null) |
MetadataManager |
Data |
byte[],2048 |
SkylightManager |
SkyLight |
byte[],2048 |
BlocklightManager |
BlockLight |
byte[],2048 |
LightingManager |
HeightMap |
int[] |
LightingManager |
LightPopulated |
boolean |
BiomeManager |
Biomes |
byte[],256(BYTES_PER_CHUNK = 256) |
BlockIDManager — 方块 ID 的分片打包
唯一实现全部 4 个可序列化接口的类,也是包体积的大头。
| 常量 | 值 |
|---|---|
LSB_BYTES_PER_SUBCHUNK |
4096 |
MSB_BYTES_PER_SUBCHUNK |
2048 |
HEADER_SIZE |
2 |
maxPacketSize() |
2 + 16 × (4096 + 2048) = 98306 |
maxPacketSizeCubic() |
2 + 4096 + 2048 = 6146 |
Chunk 级打包是两趟的(先留 2 字节头,最后回填):遍历 chunk.getBlockStorageArray(),对 subChunkMask 命中的 subChunk 先写 LSB;MSB 非 null 时把 1 << i 记进 msbMask,写完后再 buffer.position(currentPos) 回填 buffer.putShort((short) msbMask)。MSB 为 null 的 subChunk 在包里只占 LSB 的 4096 字节,头里的掩码告知客户端要不要建 MSB 数组——读侧 readFromBuffer 中掩码未命中就 subChunk.setBlockMSBArray(null)。
CubicPacketDataManager 变体则是每 cube 单发:LSB 4096 + 1 字节存在标志 + 有则 2048。
BlockPacketDataManager 两个方向:
| 方法 | 实现 |
|---|---|
writeBlockToPacket(Chunk, int, int, int, S23PacketBlockChange) |
空实现 |
readBlockFromPacket(...) |
空实现 |
writeBlockPacketToBuffer(packet, buffer) |
buffer.writeShort(Block.getIdFromBlock(packet.field_148883_d) & 0xFFF) |
readBlockPacketFromBuffer(packet, buffer) |
packet.field_148883_d = Block.getBlockById(buffer.readUnsignedShort() & 0xFFF) |
即方块 ID 本身从 12 位压缩改成 12 位(原版 S23 用 varint + 坐标 long),SRG 字段 field_148883_d 被硬编码到 public 以便直接赋值(见 Access Transformer)。
MetadataManager — 方块 metadata
| 方法 | 实现 |
|---|---|
getNibbleArray(subChunk) |
subChunk.getMetadataArray() |
writeBlockToPacket / readBlockFromPacket |
空实现 |
writeBlockPacketToBuffer |
buffer.writeByte(packet.field_148884_e & 0xF) |
readBlockPacketFromBuffer |
packet.field_148884_e = buffer.readUnsignedByte() & 0xF |
readSubChunkFromNBT |
subChunk.setBlockMetadataArray(new NibbleArray(nbt.getByteArray("Data"), 4)) |
metadata 从原版的 4 位 varint 改成固定 1 字节。SRG 字段 field_148884_e。
SkylightManager — 唯一带条件分支的内建 manager
id() 为 skylight,继承 NibbleManager。全部 4 个覆写方法都用 chunk.worldObj.provider.hasNoSky(或 fromChunk 的同字段)做守卫:
| 方法 | hasNoSky == true 时 |
|---|---|
writeToBuffer(Chunk, ...) |
跳过 super,即不占用任何包字节 |
readFromBuffer(Chunk, ...) |
跳过 super |
writeSubChunkToNBT |
写 nbt.setByteArray("SkyLight", new byte[subChunk.getBlocklightArray().data.length]) —— 全 0 数组,长度取自 blocklight |
readSubChunkFromNBT |
完全不读,setSkylightArray 不被调用 |
cloneSubChunk |
to.setSkylightArray(null) |
⚠️ writeSubChunkToNBT 的全 0 数组长度取自 getBlocklightArray().data.length 而非 getSkylightArray()——在无天空维度里若 blocklight 与 skylight 长度不同(例如某方块改变了数组深度),写出的长度会错。
BlocklightManager — 源码里的一处笔误
id() 为 blocklight,继承 NibbleManager,行为与 SkylightManager 对称(BlockLight 键、恒 subChunkPrivilegedAccess() == true)。
⚠️ cloneSubChunk 写错了接收者,源码原文:
public void cloneSubChunk(Chunk fromChunk, ExtendedBlockStorage from, ExtendedBlockStorage to) {
from.setBlocklightArray(ArrayUtil.copyArray(from.getBlocklightArray(), to.getBlocklightArray()));
}
参数 from 出现在 setBlocklightArray 的调用侧,而 to 只作为 copyArray 的目标数组出现。对照同包的 SkylightManager.cloneSubChunk(to.setSkylightArray(...))与 MetadataManager.cloneSubChunk(to.setBlockMetadataArray(...)),此处应为 to.。
净效果:克隆时 blocklight 数据留在源 subChunk 上,目标 subChunk 的数组引用不被替换,而源 subChunk 的引用被换成目标数组的内容。原文未修正。
BiomeManager — 群系字节数组
| 项 | 值 |
|---|---|
BYTES_PER_CHUNK |
256 |
maxPacketSize() |
返回 BYTES_PER_CHUNK = 256 |
writeToBuffer(Chunk, int, boolean, ByteBuffer) |
仅当 forceUpdate 为 true 才 buffer.put(chunk.getBiomeArray()) |
readFromBuffer(Chunk, int, boolean, ByteBuffer) |
同上,buffer.get(chunk.getBiomeArray()) |
writeChunkToNBT |
nbt.setByteArray("Biomes", chunk.getBiomeArray()) |
readChunkFromNBT |
if (nbt.hasKey("Biomes", 7)) chunk.setBiomeArray(nbt.getByteArray("Biomes")) |
cloneChunk |
to.setBiomeArray(ArrayUtil.copyArray(from.getBiomeArray(), to.getBiomeArray())) |
⚠️ 这是全 mod 唯一写入前判断 forceUpdate 的 manager——群系数组只在区块首次全量同步时发,后续增量更新不发。
⚠️ 不要把这个类当成本 mod 定义的"群系":它不定义任何群系,只是把原版 Chunk.getBiomeArray() 的 256 字节搬进打包框架。本 wiki 因此没有 biome/ 目录。
LightingManager — 唯一纯 chunk 级 manager
id() 为 lighting,只实现 ChunkDataManager,不参与任何网络同步:
| 方法 | 实现 |
|---|---|
writeChunkToNBT |
nbt.setIntArray("HeightMap", chunk.heightMap) + nbt.setBoolean("LightPopulated", chunk.isLightPopulated) |
readChunkFromNBT |
从 "HeightMap" / "LightPopulated" 读回 |
cloneChunk |
to.heightMap = ArrayUtil.copyArray(from.heightMap, to.heightMap);to.isLightPopulated = from.isLightPopulated |
⚠️ 直接读写 chunk.heightMap 与 chunk.isLightPopulated 两个非 public 字段——这正是 ChunkMixin 里 @Shadow 出这两个字段的原因(见 Mixin 清单)。
注册入口
com.falsepattern.chunk.internal.ChunkAPI 是全 mod 唯一的 @Mod 类:
@Mod(modid = Tags.MOD_ID, version = Tags.MOD_VERSION, name = Tags.MOD_NAME,
acceptedMinecraftVersions = "[1.7.10]")
public class ChunkAPI {
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
DataRegistry.registerDataManager(new BlockIDManager(), 0);
DataRegistry.registerDataManager(new MetadataManager(), 0);
DataRegistry.registerDataManager(new LightingManager(), 0);
DataRegistry.registerDataManager(new BlocklightManager(), 0);
DataRegistry.registerDataManager(new SkylightManager(), 0);
DataRegistry.registerDataManager(new BiomeManager(), 0);
}
}
整个 @Mod.EventHandler 全部内容就是这 6 行注册,没有任何代理、渲染、网络注册。注意 acceptedMinecraftVersions = "[1.7.10]"。
⚠️ Tags 类(internal.Tags,含 MOD_ID/MOD_NAME/MOD_VERSION/ROOT_PKG)不在源码里——由 build.gradle.kts 的 tokens { tokenClass = "internal.Tags" } 在构建期生成,同理 mcmod.info 里的 ${modId} / ${modName} / ${modVersion} / ${minecraftVersion} 也是构建期替换的占位符。
相关条目
- API 接口 — 6 个 manager 接口的定义
- 数据注册表 — 注册流程、ordering、包体积预算
- 序列化流水线 — 各 manager 方法的调用时机
- Mixin 清单 — 提供这些 manager 需要的非 public 字段访问
- Access Transformer —
NibbleArray字段的公开化