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 |
相关条目
- 存档目录与文件命名 -
r.X.Z.mcr/r.X.Z.mca的拼法与isValid正则 - 区域文件格式 - 扇区位图 + 自定义头表列存布局
- 区域缓存上限 - 本类同样经
SharedCachedRegionProvider走共享缓存 - SaveSection2D - 同为 1024 条目 / 32×32 网格但 512 扇区的对照版本