维度有序缓存 OrderedDimCache
基本信息
| 属性 | 值 |
|---|---|
| 源码类 | com.rune580.sharedprospecting.database.OrderedDimCache |
| 归属 | 每个 ServerUtilities 队伍 × 每个维度各一个实例 |
| 维度字段 | public final int dimension(构造时定型,永不变) |
| 容器 | fastutil LongArrayList × 3 |
| 排序语义 | 仅追加(append-only),插入顺序即矿脉发现顺序 |
字段
private final LongList oreVeins = new LongArrayList();
private final LongList undergroundFluids = new LongArrayList();
@Getter
private final LongList depletedVeins = new LongArrayList();
public final int dimension;
三个列表都是 Long 键,不存任何矿脉类型、Y 坐标或方块 id。矿脉的实际数据要靠键反查 VP 的 ServerCache。
| 列表 | 元素含义 | 键的计算 |
|---|---|---|
oreVeins |
已探明的矿脉所在区块(已折算到 VP 的中心区块坐标) | RevisionUtil.getOreVeinKey(x, z) = Utils.chunkCoordsToKey(Utils.mapToCenterOreChunkCoord(x), Utils.mapToCenterOreChunkCoord(z)) |
undergroundFluids |
已探明的地下流体所在区块 | RevisionUtil.getUndergroundFluidKey(x, z) = Utils.chunkCoordsToKey(Utils.mapToCornerUndergroundFluidChunkCoord(x), Utils.mapToCornerUndergroundFluidChunkCoord(z)) |
depletedVeins |
已被挖空、需要在队伍间同步"不再显示"的矿脉 | 同 oreVeins 的键算法 |
矿脉用中心区块坐标、地下流体用角落区块坐标,这是直接沿用 Visual Prospecting Utils 的两套折算,本 mod 不做二次转换。
为什么用"有序列表"而不是 Map
这是整个 mod 的设计核心。列表只追加、不删除、不排序,putOreVein / putUndergroundFluid 用 contains 判重后 add:
public boolean putOreVein(long veinPos) {
OreVeinPosition vein = ServerCache.instance.getOreVein(
dimension, CoordinatePacker.unpackX(veinPos), CoordinatePacker.unpackZ(veinPos));
if (oreVeins.contains(veinPos) || OreVeinPosition.EMPTY_VEIN.equals(vein)
|| vein.veinType == VeinType.NO_VEIN) {
return false;
}
return oreVeins.add(veinPos);
}
putOreVein 有三重拒绝:已在列表里、VP 服务端缓存里查不到(EMPTY_VEIN)、或矿脉类型是 VeinType.NO_VEIN(空气)。putUndergroundFluid 只做一项判重(undergroundFluids.contains),不校验实体。
因为顺序稳定,列表长度就可以直接当作"同步进度"——客户端报告"我已知 N 条",服务端只需把 [N, size) 这段区间发过去。团队里每个成员看到的矿脉顺序因此完全一致,不会因为 HashMap 遍历顺序漂移。
代价是判重是 O(n) 的 contains,depletedVeins 的增删用 add / rem(见 队伍数据模型 的 updateDepletedVeins)。队伍数据量极大时这是个线性扫描,源码没有为此做额外索引。
revision 的真实含义
public long getRevision() {
return RevisionUtil.packRevision(oreVeins.size(), undergroundFluids.size());
}
packRevision = CoordinatePacker.pack(oreSize, 0, fluidSize),Y 槽恒为 0。
revision 不是自增计数器,而是"矿脉数量 + 流体数量"这一对数值打包进一个 long。 它不是"第几次变更",而是"当前一共有多少条"。这让增量同步退化成一次区间截取,不需要服务端为每个客户端维护差异记录。
增量取出
getOresForRevision(long revision):
oreSize = Math.min(RevisionUtil.getOreSize(revision), oreVeins.size())。- 若
getRevision() == revision(无变化)或oreSize == oreVeins.size()(客户端已全部知道)→ 返回Collections.emptyList()。 - 否则遍历
oreVeins.subList(oreSize, oreVeins.size()),逐键用ServerCache.instance.getOreVein(dimension, unpackX, unpackZ)换回真正的OreVeinPosition对象。
getFluidsForRevision(long revision):结构相同,但取回方式不同——它不存也不取回流体对象:
World world = DimensionManager.getWorld(dimension);
for (long key : undergroundFluids.subList(fluidSize, undergroundFluids.size())) {
int x = Utils.coordChunkToBlock(CoordinatePacker.unpackX(key));
int z = Utils.coordChunkToBlock(CoordinatePacker.unpackZ(key));
// do one fluid field at a time since the saved coords aren't guaranteed to be adjacent
List<UndergroundFluidPosition> prospectedFluids = ServerCache.instance
.prospectUndergroundFluidBlockRadius(world, x, z, 0);
fluids.addAll(prospectedFluids);
}
源码注释解释了原因:存下来的坐标不保证相邻,所以不能按矩形区域批量探,只能一个流体区块一次、半径 0 地单独探。world 由 DimensionManager.getWorld(dimension) 现取。
因此两种数据的不对称性很明显:
oreVeins |
undergroundFluids |
|
|---|---|---|
| 存什么 | 区块键 | 区块键 |
| 何时还原成对象 | 取出时按键查 ServerCache.getOreVein |
取出时重新扫描世界(prospectUndergroundFluidBlockRadius,radius 0) |
| 存盘体积 | 8 字节/条 | 8 字节/条(同样小) |
| 还原代价 | O(1) 查表 | O(区块扫描) |
落盘
dimCompound.setTag("oreList", saveList(oreVeins));
dimCompound.setTag("fluidList", saveList(undergroundFluids));
dimCompound.setTag("depletedVeins", saveList(depletedVeins));
三个字段都是 NBTTagList,每项一个 NBTTagLong。load() 对应 readOres / readFluids / readLongList(depletedVeins, "depletedVeins", compound)。注意 depletedVeins 没有旧格式兼容分支——只有 ores / fluids 有。详见 持久化与 NBT 结构。
读列表时用了 Access Transformer 打开的 NBTTagList.tagList 字段(func_74747_a),逐项 (NBTTagLong) obj).func_150291_c() 取值。
相关条目
- 队伍数据模型 - 谁持有这些缓存、何时通知成员
- 版本号同步机制 -
getRevision()打包的 long 在客户端如何被消费 - 持久化与 NBT 结构 - 三个 TAG_Long 列表与旧格式兼容
- 网络封包协议 - 增量数据走的是 VP 自己的封包