Minecraft 原版存档区(MinecraftSaveSection)

基本信息

属性 值
实现类 cubicchunks.regionlib.impl.save.MinecraftSaveSection
继承 SaveSection<MinecraftSaveSection, MinecraftChunkLocation>
key 类型 MinecraftChunkLocation(实现 IKey<MinecraftChunkLocation>)
每区域条目数 ENTRIES_PER_REGION = (1 << 5) * (1 << 5) = 1024
区域文件名 r.<regX>.<regZ>.<extension>,extension ∈ {mcr, mca}
扇区大小 4096(唯一一个非 512 的)
头表字段 扇区位图(4 B/条)+ 时间戳(4 B/条,TimeUnit.SECONDS)
回退层 无(只包一个 IRegionProvider)

这是本 mod 里唯一直接对应 Minecraft 原版存档格式的变体:文件名、扇区大小、时间戳头表都对齐 Anvil 区域文件,因此可以被原版 / 其它 1.7.10 工具读写。

MinecraftRegionType 枚举

public enum MinecraftRegionType { MCR, MCA }

createAt 里用 type.name().toLowerCase() 取小写后交给 MinecraftChunkLocation.Provider:

枚举 区域名 指向的存档
MCR r.<regX>.<regZ>.mcr 旧版(Minecraft 1.2 及更早)单区块区域格式
MCA r.<regX>.<regZ>.mca 1.4+ 带索引区的 Anvil 区域格式

枚举只有这 2 个值,type.name().toLowerCase() 意味着无法用同一个 MinecraftSaveSection 同时读写 mcr 与 mca——扩展名写死在 MinecraftChunkLocation 的构造参数里。

createAt(Path, MinecraftRegionType)

public static MinecraftSaveSection createAt(Path directory, MinecraftRegionType type) {
    return new MinecraftSaveSection(new SharedCachedRegionProvider<>(
            new SimpleRegionFactory<>(new MinecraftChunkLocation.Provider(type.name().toLowerCase()), directory,
                    (keyProvider, regionKey) -> Region.<MinecraftChunkLocation>builder()
                            .setDirectory(directory)
                            .setSectorSize(4096)
                            .setKeyProvider(keyProvider)
                            .setRegionKey(regionKey)
                            .addHeaderEntry(new TimestampHeaderEntryProvider<>(TimeUnit.SECONDS))
                            .build(),
                    (keyProvider, regionKey) -> Files.exists(directory.resolve(regionKey.getName())))));
}

与 SaveSection2D / SaveSection3D 的 createAt 有三处关键差异:

差异点 本类 2D / 3D 版
setSectorSize 4096 512
头表字段 额外 addHeaderEntry(new TimestampHeaderEntryProvider<>(TimeUnit.SECONDS)) 无(只有扇区位图)
回退 .ext 无,只一个 provider 有两层(打包区 → .ext)

因为没有 .ext 回退,255 扇区上限直接作用在数据上:4096 × 255 = 1044480,减去 4 字节长度前缀 → 单条目上限 1044476 字节,超出即抛 UnsupportedDataException(详见 区域文件格式)。

也不建子目录:directory 直接被当成存 r.X.Z.mca 的目录,不像 SaveCubeColumns.create() 会先建 region2d/、region3d/。

坐标 ↔ 区域 / id 的换算

public static final int LOC_BITS = 5;               // 32
public static final int LOC_BITMASK = (1 << 5) - 1;  // 31
public static final int ENTRIES_PER_REGION = 1024;

getRegionKey() → new RegionKey("r." + regX + "." + regZ + "." + extension)  // 各自 >> 5
getId()        → ((entryZ & 31) << 5) | (entryX & 31)

⚠️ X / Z 顺序是反的:getRegionKey() 拼的是 r.<regX>.<regZ>,而 getId() 是 Z 在高位、X 在低位。EntryLocation2D 恰好相反(区域名 X.Z、id 也是 X 在高位)。两者不能混用同一个 key 空间。

性质 说明
equals 只比较 entryX / entryZ —— extension 不参与比较
hashCode 31 * entryX + entryZ
字段 getEntryX() / getEntryZ();extension 无 getter,只有构造参数
toString EntryLocation2D{entryX=…, entryZ=…, extension=…} —— 前缀写错了 EntryLocation2D,实际是 MinecraftChunkLocation(源码原文如此)
可变性 entryX / entryZ 是 final,但 extension 字段不是 final

MinecraftChunkLocation.Provider

IKeyProvider<MinecraftChunkLocation> 的实现,扩展名在构造时注入:

方法 行为
构造 Provider(String extension)
getKeyCount(RegionKey) 恒返回 ENTRIES_PER_REGION = 1024
isValid(RegionKey) key.getName().matches("r\\.-?\\d+\\.-?\\d+\\." + extension)
fromRegionAndId(RegionKey, int) 先 isValid;不通过抛 IllegalArgumentException("Invalid name " + regionKey.getName() + ", expected pattern r\\.-?\\d+\\.-?\\d+\\." + extension)(错误信息里把预期模式也带上了)。然后 relativeX = id & 31、relativeZ = id >>> 5,还原为 new MinecraftChunkLocation(regX << 5 | relativeX, regZ << 5 | relativeZ, extension)

时间戳头表

impl/header/TimestampHeaderEntryProvider<L extends IKey<L>>:

成员 值
getEntryByteCount() Integer.BYTES = 4
apply(L o) new IntHeaderEntry((int) TimeUnit.MILLISECONDS.convert(System.currentTimeMillis(), timeUnit))
构造 TimestampHeaderEntryProvider(TimeUnit timeUnit),本类传 TimeUnit.SECONDS

它在每次 writeValue / writeValue(key, null) 后被 updateHeaders 重写(连删除操作也会刷新时间戳)。头表是列存布局:扇区位图整块在前,时间戳整块在后,详见 区域文件格式。

TimestampHeaderEntryProvider 的泛型参数 L extends IKey<L> 与 IHeaderDataEntryProvider<H, K> 要求的 K extends IKey<K> 是两个独立类型变量(H 在本实现里固定为 IntHeaderEntry),所以 new TimestampHeaderEntryProvider<>(TimeUnit.SECONDS) 在 Region.Builder<MinecraftChunkLocation> 上靠目标类型推断才成立。

数据访问

只覆写了构造器,未覆写任何数据方法,全部来自 SaveSection:

方法 签名
单条写 / 批量写 save(MinecraftChunkLocation, ByteBuffer) / save(Map<MinecraftChunkLocation, ByteBuffer>)
单条读 / 批量读 load(K, boolean) / load(Collection<K>, boolean)
遍历 allKeys(boolean) / allEntries(boolean)
其它 hasEntry(K) / flush() / close()

由于只有一个 provider,多提供者回退链里"第一个接受者胜出、其余擦除"的逻辑退化为单层直写;全部失败时同样抛 SaveSectionException("No region provider supporting key … with data size …")。

数值

数值 值
单区域条目数 1024
单条目字节上限 1044476(255 × 4096 − 4),无回退
头表字节 1024 × (4 + 4) = 8192 字节 = 2 扇区(entryMapSectors)
单区域文件最大理论大小 (1 + 255 × 1024) 扇区 × 4096 B ≈ 1.0 GiB

相关条目