内置原版数据管理器(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} 也是构建期替换的占位符。

相关条目