diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java index ce89043..e678dfc 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -16,15 +16,19 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; -import java.text.DecimalFormat; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; /** * InfinityBigIntegerCellInventory *

@@ -45,8 +49,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化) private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>(); - // 数字格式化对象,保留两位小数(复用以减少对象分配) - private static final DecimalFormat DF = new DecimalFormat("#.##"); + // 数字格式化对象:改为方法局部以避免静态非线程安全问题 // 关联的 ItemStack(含可能的 uuid NBT) private final ItemStack stack; @@ -56,7 +59,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { private Object2ObjectMap storedMap = null; // 标记是否已持久化到 SavedData private boolean isPersisted = true; - + // 缓存的总存储量,避免每次调用进行全表扫描 + private BigInteger totalStored = BigInteger.ZERO; /** * 私有构造器:通过 createInventory 工厂方法调用 @@ -66,14 +70,76 @@ public class InfinityBigIntegerCellInventory implements StorageCell { */ private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) { this.stack = stack; - this.container = saveProvider; + container = saveProvider; + // 不在构造时创建 storedMap,推迟到实际访问或首次写入时初始化 this.storedMap = null; - initData(); + } + + // 创建存储单元库存实例的静态方法 + static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) { + if (stack.getItem() instanceof InfinityBigIntegerCellItem) { + return new InfinityBigIntegerCellInventory(stack, saveProvider); + } + return null; + } + + // 获取全局存储实例 + private static InfinityStorageManager getStorageInstance() { + return InfinityStorageManager.INSTANCE; + } + + // 服务器 tick 回调:合并并执行待持久化项 + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + InfinityBigIntegerCellInventory inv; + // 处理本次 tick 中的全部待持久化项 + while ((inv = PENDING_PERSIST.poll()) != null) { + try { + if (!inv.isPersisted) { + inv.persist(); + } + } catch (Throwable ignored) { + LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ignored.getMessage()); + } + } + } + + // 在服务器停止时被调用,立即强制持久化队列中的所有实例 + public static void onServerStopping(ServerStoppingEvent event) { + // 尝试在服务端停止流程开始时初始化 SavedData(确保 INSTANCE 可用以便后续持久化) + try { + MinecraftServer server = event.getServer(); + if (server != null) { + for (ServerLevel level : server.getAllLevels()) { + InfinityStorageManager.getForLevel(level); + } + } + } catch (Throwable ignored) { + } + + InfinityBigIntegerCellInventory inv; + while ((inv = PENDING_PERSIST.poll()) != null) { + try { + if (!inv.isPersisted) { + inv.persist(); + } + } catch (Throwable ignored) { + LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage()); + } + } + // 将全局存储管理器标记为脏以确保 SavedData 被写回 + try { + var stor = getStorageInstance(); + if (stor != null) stor.setDirty(); + } catch (Throwable ignored) { + LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error2: {}", ignored.getMessage()); + } } // 将 BigInteger 格式化为带单位的字符串,保留两位小数 public static String formatBigInteger(BigInteger number) { - // 使用局部 DF(非线程安全),但 Minecraft 通常在主线程运行 + // 使用方法局部的 DecimalFormat,避免静态共享的非线程安全问题 + java.text.DecimalFormat df = new java.text.DecimalFormat("#.##"); BigDecimal bd = new BigDecimal(number); BigDecimal thousand = new BigDecimal(1000); String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"}; @@ -85,27 +151,23 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (idx == 0) { return bd.setScale(0, RoundingMode.DOWN).toPlainString(); } - return DF.format(bd.doubleValue()) + units[idx]; - } - - // 创建存储单元库存实例的静态方法 - static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) { - return new InfinityBigIntegerCellInventory(stack, saveProvider); - } - - private void initData() { - if (!hasUUID()) { - getCellStoredMap(); - } + return df.format(bd.doubleValue()) + units[idx]; } // 获取当前存储单元的数据存储对象 private InfinityDataStorage getCellStorage() { if (this.getUUID() == null) { - // 如果没有UUID,返回空存储 - return InfinityDataStorage.EMPTY; + // 如果没有UUID,返回空存储(返回新实例以避免共享可变状态) + return InfinityDataStorage.empty(); } else { - return InfinityStorageManager.INSTANCE.getOrCreateCell(this.getUUID()); + // 在访问全局 SavedData 之前做防御性检查,避免在客户端或尚未初始化的情况下 NPE + InfinityStorageManager mgr = getStorageInstance(); + if (mgr == null) { + // SavedData 尚未就绪,视为空存储(返回新实例以避免共享可变状态) + return InfinityDataStorage.empty(); + } + // 否则获取或创建对应UUID的存储 + return mgr.getOrCreateCell(getUUID()); } } @@ -137,11 +199,10 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 获取物品堆栈的UUID public UUID getUUID() { - if (this.hasUUID()) { + if (this.hasUUID()) return stack.getOrCreateTag().getUUID("uuid"); - } else { + else return null; - } } // 获取或初始化存储映射 @@ -156,13 +217,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 从存储中加载物品映射 private void loadCellStoredMap() { boolean corruptedTag = false; // 标记数据是否损坏 - if (!stack.hasTag()) - return; - ListTag keys = this.getCellStorage().keys; - ListTag amounts = this.getCellStorage().amounts; - - - for (int i = 0; i < amounts.size(); i++) { + if (!stack.hasTag()) return; + InfinityDataStorage storage = this.getCellStorage(); + ListTag keys = storage.keys; + ListTag amounts = storage.amounts; + int len = Math.min(keys.size(), amounts.size()); + for (int i = 0; i < len; i++) { AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); CompoundTag amtTag = amounts.getCompound(i); try { @@ -180,16 +240,24 @@ public class InfinityBigIntegerCellInventory implements StorageCell { corruptedTag = true; } else { // storedMap 已在 getCellStoredMap() 中初始化,直接使用字段以避免额外方法开销 - getCellStoredMap().put(key, amount); + storedMap.put(key, amount); + // 更新缓存的总数 + totalStored = totalStored.add(amount); } } catch (NumberFormatException ex) { corruptedTag = true; } } - // 如果有损坏,尝试保存修正后的数据;若全局管理器尚未就绪则保守处理 + // 如果有损坏,保存修正后的数据 if (corruptedTag) { this.saveChanges(); } + // 打印加载后的摘要日志 + try { + var uuid = this.getUUID(); + LOGGER.info("Loaded cell {}: types={}, totalCached={}", uuid, this.getCellStoredMap().size(), this.getTotalStorage()); + } catch (Throwable ignored) { + } } // 标记数据需要保存,并通知容器或直接持久化 @@ -200,7 +268,10 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 当存在容器时,优先让容器统一处理持久化 container.saveChanges(); } else { - persist(); + // 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O + if (!PENDING_PERSIST.contains(this)) { + PENDING_PERSIST.offer(this); + } } } @@ -241,25 +312,36 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 持久化存储单元数据到全局存储 @Override public void persist() { - if (this.isPersisted) { + if (this.isPersisted) + return; + // 若全局存储管理器尚未就绪,重新入队并在未来 tick 重试 + InfinityStorageManager mgr = getStorageInstance(); + if (mgr == null) { + try { + var id = this.getUUID(); + LOGGER.info("Persist deferred for cell {}: storage manager not ready", id); + } catch (Throwable ignored) { + } + if (!PENDING_PERSIST.contains(this)) { + PENDING_PERSIST.offer(this); + } return; } + Object2ObjectMap map = this.getCellStoredMap(); if (map.isEmpty()) { - // 如果存储为空,保守处理:写回空的 persisted 数据但不要从 ItemStack 上移除 uuid + // 如果存储为空,移除UUID和全局存储中的数据 if (this.hasUUID()) { - // 如果存储为空,移除UUID和全局存储中的数据,并清理缓存的 types/total - if (hasUUID()) { - InfinityStorageManager.INSTANCE.removeCell(getUUID()); - if (stack.getTag() != null) { - stack.getTag().remove("uuid"); - stack.getTag().remove("types"); - stack.getTag().remove("total"); - } - initData(); + mgr.removeCell(getUUID()); + if (stack.getTag() != null) { + stack.getTag().remove("uuid"); + // 移除缓存的 total 字段 + stack.getTag().remove("total"); } - return; + // 标记为已持久化,避免重复尝试 + isPersisted = true; } + return; } // 构建要保存的Key和数量列表(混合表示:long 或 string) ListTag amountTags = new ListTag(); @@ -277,56 +359,55 @@ public class InfinityBigIntegerCellInventory implements StorageCell { amountTags.add(amt); } } + // 如果没有Key,更新为空存储,否则保存数据 if (keys.isEmpty()) { - InfinityStorageManager.INSTANCE.updateCell(this.getUUID(), new InfinityDataStorage()); - // 清理缓存 - if (stack.getTag() != null) { - stack.getTag().remove("types"); - stack.getTag().remove("total"); - } + mgr.updateCell(this.getUUID(), new InfinityDataStorage()); } else { // amounts 现在为 CompoundTag 列表 - InfinityStorageManager.INSTANCE.modifyCell(this.getUUID(), keys, amountTags); - // 缓存类型数量与总量到 ItemStack 的 NBT,避免每次 tooltip 或展示时重新统计 - try { - if (stack.getTag() == null) stack.setTag(new CompoundTag()); - int typesCount = keys.size(); - stack.getOrCreateTag().putInt("types", typesCount); - java.math.BigInteger total = java.math.BigInteger.ZERO; - for (java.util.Map.Entry e : map.object2ObjectEntrySet()) { - java.math.BigInteger v = e.getValue(); - if (v.compareTo(java.math.BigInteger.ZERO) > 0) { - total = total.add(v); - } - } - if (total.compareTo(java.math.BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { - stack.getOrCreateTag().putLong("total", total.longValue()); - } else { - stack.getOrCreateTag().putString("total", total.toString()); - } - } catch (Exception ignored) { + mgr.modifyCell(this.getUUID(), keys, amountTags); + } + // 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long + if (stack.getOrCreateTag() != null) { + if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { + stack.getOrCreateTag().putLong("total", totalStored.longValue()); + } else { + stack.getOrCreateTag().putString("total", totalStored.toString()); } + // 将当前已存储的不同物品种类数缓存到 NBT(键名: "types"),用于客户端 tooltip 显示 + int typesCount = this.getCellStoredMap().size(); + stack.getOrCreateTag().putInt("types", typesCount); } isPersisted = true; + // 打印持久化摘要日志 + try { + var uuid = this.getUUID(); + LOGGER.info("Persisted cell {}: types={}, totalCached={}", uuid, this.getCellStoredMap().size(), this.getTotalStorage()); + } catch (Throwable ignored) { + } } // 插入物品到存储单元 @Override public long insert(AEKey what, long amount, Actionable mode, IActionSource source) { // 数量为0或类型不匹配直接返回 - if (amount == 0) { + if (amount == 0) return 0; - } // 不允许存储无限单元自身 - if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) { + if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) return 0; - } - // 如果没有UUID,生成UUID并初始化存储(延迟创建全局存储以避免在 manager 未就绪时 NPE) + // 如果没有UUID,尝试在服务器端且存储管理器已就绪时生成UUID并初始化存储 if (!this.hasUUID()) { - stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); - InfinityStorageManager.INSTANCE.getOrCreateCell(getUUID()); - // 确保 storedMap 初始化并从持久层加载数据 - loadCellStoredMap(); + InfinityStorageManager mgr = getStorageInstance(); + // 仅在 SavedData 已就绪(通常在服务器端)时创建 UUID 并注册持久化存储 + if (mgr != null) { + stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); + mgr.getOrCreateCell(getUUID()); + // 确保 storedMap 初始化并从持久层加载数据 + this.getCellStoredMap(); + } else { + // SavedData 未就绪:把插入作为本地内存操作(不生成 UUID,不持久化),确保 storedMap 初始化以容纳临时数据 + this.getCellStoredMap(); + } } Object2ObjectMap map = this.getCellStoredMap(); BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); @@ -334,6 +415,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 实际插入,更新数量并保存 BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount)); map.put(what, newAmount); + // 更新 cached total + totalStored = totalStored.add(BigInteger.valueOf(amount)); this.saveChanges(); } return amount; @@ -354,8 +437,11 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } else { ret = currentAmount.longValue(); } - if (mode == appeng.api.config.Actionable.MODULATE) { + if (mode == Actionable.MODULATE) { map.remove(what); + // 更新 cached total + // 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数 + totalStored = totalStored.subtract(currentAmount); this.saveChanges(); } return ret; @@ -363,6 +449,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 提取部分 if (mode == Actionable.MODULATE) { map.put(what, currentAmount.subtract(requested)); + // 更新 cached total + totalStored = totalStored.subtract(requested); this.saveChanges(); } return amount; @@ -374,12 +462,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 获取存储单元内所有物品的总数量(格式化字符串) public String getTotalStorage() { // 使用缓存的 totalStored,避免每次全表扫描 - BigInteger total = BigInteger.ZERO; - for (BigInteger value : getCellStoredMap().values()) { - if (value.compareTo(BigInteger.ZERO) > 0) { - total = total.add(value); - } - } - return formatBigInteger(total); + return formatBigInteger(totalStored); } } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java index 71ec86a..7cfc93d 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -18,8 +18,10 @@ import net.minecraft.nbt.Tag; */ public class InfinityDataStorage { - /** 空实例(表示没有数据) */ - public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); + /** 空实例的访问器(返回新实例以避免共享可变状态) */ + public static InfinityDataStorage empty() { + return new InfinityDataStorage(); + } /** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */ public ListTag keys; diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index 26d880a..24bbe28 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -3,123 +3,177 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.world.level.storage.LevelResource; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; +import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.nio.file.AtomicMoveNotSupportedException; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; + /** * InfinityStorageManager - * - * 替代之前基于 SavedData 的实现,本类使用手动文件 I/O 在 world 目录下保存 NBT 数据, - * 以避免依赖 Minecraft 的 SavedData 机制。 - * 数据保持与之前兼容的 NBT 结构:根 Compound 包含 "list" => ListTag of Compound { uuid, data } + *

+ * 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。 + * 功能要点: + * - 在世界加载时从存档恢复所有 cell 的数据 + * - 提供按 UUID 获取/创建单个 cell 的数据容器 + * - 在世界保存时将内存数据打包为 NBT 写回存档 */ -public class InfinityStorageManager { +public class InfinityStorageManager extends SavedData { - public static final String FILE_NAME = "eap_infinity_biginteger_cells.dat"; + // 当前磁盘格式版本号,增加字段用于向后/向前兼容 + private static final int FORMAT_VERSION = 1; - /** 全局单例,由 mod 在 world load 时初始化 */ - public static volatile InfinityStorageManager INSTANCE = new InfinityStorageManager(); + /** + * SavedData 文件名常量 + */ + public static final String FILE_NAME = "eap_infinity_biginteger_cells"; + /** + * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充) + */ + public static InfinityStorageManager INSTANCE = null; + /** + * UUID -> 数据 的内存映射 + */ private final Map cells = new HashMap<>(); - private Path saveFilePath = null; - public InfinityStorageManager() { + setDirty(); } /** - * 初始化并从 world 保存目录加载数据;若文件不存在则保持空状态 + * 从 NBT 构造:用于在世界加载时从存档恢复数据 */ - public void initFromWorld(@Nullable ServerLevel serverLevel) { - if (serverLevel == null) return; - try { - File worldFolder = serverLevel.getServer().getWorldPath(LevelResource.ROOT).toFile(); - // 保存到 world// 文件夹下,避免与其它 mod 冲突 - File modDir = new File(worldFolder, "data"); - if (!modDir.exists()) modDir.mkdirs(); - saveFilePath = new File(modDir, FILE_NAME).toPath(); - if (Files.exists(saveFilePath)) { - CompoundTag root = NbtIo.readCompressed(saveFilePath.toFile()); - ListTag cellList = root.getList("list", Tag.TAG_COMPOUND); - for (int i = 0; i < cellList.size(); i++) { - CompoundTag cell = cellList.getCompound(i); - cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); - } - } - } catch (IOException e) { - // 读取失败保持空,并打印栈追踪以便调试 - e.printStackTrace(); + public InfinityStorageManager(CompoundTag nbt) { + // 读取格式版本,缺省视为 1(兼容旧档) + int version = nbt.contains("format_version") ? nbt.getInt("format_version") : 1; + if (!nbt.contains("format_version")) { + // 旧档未包含 format_version,标记为需要写回以便下次保存写入版本号 + LOGGER.info("Found legacy InfinityStorageManager dat without format_version; treating as version {} and marking dirty for upgrade", version); + // 立即标记为脏以触发尽快写盘(升级写入 format_version) + this.setDirty(); + } else { + LOGGER.info("Loading InfinityStorageManager format_version={}", version); } + + ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND); + for (int i = 0; i < cellList.size(); i++) { + CompoundTag cell = cellList.getCompound(i); + java.util.UUID uuid = cell.getUUID("uuid"); + CompoundTag dataTag = cell.getCompound("data"); + InfinityDataStorage data = InfinityDataStorage.loadFromNBT(dataTag); + cells.put(uuid, data); + LOGGER.info("Loaded InfinityDataStorage for uuid {}: keys={}, amounts={}", uuid, data.keys.size(), data.amounts.size()); + } + setDirty(); } /** - * 保存当前内存数据到文件(会覆盖已有文件) + * 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE */ - public synchronized void saveToFile() { - if (saveFilePath == null) return; - try { - CompoundTag root = new CompoundTag(); - ListTag cellList = new ListTag(); - for (Map.Entry entry : cells.entrySet()) { - // 跳过可能的 null key,防止写入时 NPE - if (entry.getKey() == null || entry.getValue() == null) continue; - CompoundTag cell = new CompoundTag(); - cell.putUUID("uuid", entry.getKey()); - cell.put("data", entry.getValue().serializeNBT()); - cellList.add(cell); - } - root.put("list", cellList); - // 使用压缩写入到临时文件,然后原子替换目标文件以避免半成品/0字节文件 - Path tmp = saveFilePath.resolveSibling(FILE_NAME + ".tmp"); - File tmpFile = tmp.toFile(); - // 确保临时文件的目录存在 - if (tmpFile.getParentFile() != null && !tmpFile.getParentFile().exists()) { - tmpFile.getParentFile().mkdirs(); - } - NbtIo.writeCompressed(root, tmpFile); + public static InfinityStorageManager getForLevel(ServerLevel level) { + if (INSTANCE == null && level != null) { + // 迁移逻辑:若旧的压缩 dat 文件存在且缺少 format_version,则读取并补上 version,备份原文件 try { - Files.move(tmp, saveFilePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - } catch (AtomicMoveNotSupportedException ex) { - // 若底层文件系统不支持原子移动,退回到非原子替换 - Files.move(tmp, saveFilePath, StandardCopyOption.REPLACE_EXISTING); + Path dataDir = level.getServer().getWorldPath(new LevelResource("data")); + Path filePath = dataDir.resolve(FILE_NAME + ".dat"); + File oldFile = filePath.toFile(); + if (oldFile.exists()) { + // 检测是否为 gzip(压缩 NBT) + boolean isGzip = false; + try (var fis = Files.newInputStream(filePath)) { + int b1 = fis.read(); + int b2 = fis.read(); + isGzip = (b1 == 0x1F && b2 == 0x8B); + } catch (IOException ignored) { + } + if (isGzip) { + try { + var root = NbtIo.readCompressed(oldFile); + if (!root.contains("format_version")) { + // 备份旧文件 + Path bak = filePath.resolveSibling(FILE_NAME + ".dat.bak"); + try { + Files.copy(filePath, bak); + } catch (IOException ignored) { + } + // 写回并加入 format_version + root.putInt("format_version", FORMAT_VERSION); + Path tmp = filePath.resolveSibling(FILE_NAME + ".tmp"); + File tmpFile = tmp.toFile(); + if (tmpFile.getParentFile() != null && !tmpFile.getParentFile().exists()) { + tmpFile.getParentFile().mkdirs(); + } + NbtIo.writeCompressed(root, tmpFile); + try { + Files.move(tmp, filePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException ex) { + Files.move(tmp, filePath, StandardCopyOption.REPLACE_EXISTING); + } + LOGGER.info("Migrated old compressed {} to include format_version", filePath); + } + } catch (IOException ex) { + LOGGER.info("Failed to migrate old compressed file {}: {}", filePath, ex.getMessage()); + } + } + } + } catch (Throwable ignored) { } - } catch (IOException e) { - e.printStackTrace(); + + INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); } + return INSTANCE; } + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { + // 将内存中的所有 cell 序列化为一个 ListTag + ListTag cellList = new ListTag(); + for (Map.Entry entry : cells.entrySet()) { + CompoundTag cell = new CompoundTag(); + cell.putUUID("uuid", entry.getKey()); + cell.put("data", entry.getValue().serializeNBT()); + cellList.add(cell); + } + nbt.put("list", cellList); + // 写入当前格式版本号,便于未来迁移与兼容判断 + nbt.putInt("format_version", FORMAT_VERSION); + return nbt; + } + + /** + * 更新或添加某个 UUID 对应的数据并标记为脏(需要保存) + */ public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) { - if (uuid == null) return; // 忽略无效 UUID cells.put(uuid, infinityDataStorage); - saveToFile(); + setDirty(); } + /** + * 获取或创建某个 UUID 对应的数据容器 + */ public InfinityDataStorage getOrCreateCell(UUID uuid) { - if (uuid == null) { - return InfinityDataStorage.EMPTY; - } if (!cells.containsKey(uuid)) { - InfinityDataStorage newCell = new InfinityDataStorage(); - cells.put(uuid, newCell); - saveToFile(); + updateCell(uuid, new InfinityDataStorage()); } return cells.get(uuid); } + /** + * 修改某个 UUID 对应的键与数量列表并保存(新的签名,stackAmounts 为 ListTag 字符串列表) + */ public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { - if (cellID == null) return; InfinityDataStorage cellToModify = getOrCreateCell(cellID); if (stackKeys != null && stackAmounts != null) { cellToModify.keys = stackKeys; @@ -128,9 +182,11 @@ public class InfinityStorageManager { updateCell(cellID, cellToModify); } + /** + * 删除某个 UUID 的持久化记录并标记为脏 + */ public void removeCell(UUID uuid) { - if (uuid == null) return; cells.remove(uuid); - saveToFile(); + setDirty(); } }