From 1279031535f3726e7a2af4eea90ef3fdcf810e23 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Fri, 19 Sep 2025 20:49:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=97=A0=E9=99=90?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=85=83=E4=BB=B6=E7=9A=84=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=86=99=E5=85=A5=20+=20=E5=8E=9F=E5=AD=90?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/extendedae_plus/ExtendedAEPlus.java | 4 - .../InfinityBigIntegerCellInventory.java | 105 +++++++----------- .../util/storage/InfinityStorageManager.java | 65 +++++++---- 3 files changed, 83 insertions(+), 91 deletions(-) diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index ca77fbf..44a408a 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -3,7 +3,6 @@ package com.extendedae_plus; import appeng.api.storage.StorageCells; import appeng.menu.locator.MenuLocators; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler; -import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; import com.extendedae_plus.client.ClientRegistrar; import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.init.*; @@ -53,9 +52,6 @@ public class ExtendedAEPlus { // 注册到Forge事件总线 MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); - // 注册每秒合并持久化队列的事件监听(Server tick end + stopping) - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping); // 注册通用配置 ModConfig.init(); } 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 d23c236..ce89043 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 @@ -17,14 +17,11 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; 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.Map; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; @@ -48,8 +45,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化) private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>(); - // 用于按 tick 计数,每 20 tick(约 1 秒)触发一次合并写入 - private static int TICK_COUNTER = 0; // 数字格式化对象,保留两位小数(复用以减少对象分配) private static final DecimalFormat DF = new DecimalFormat("#.##"); @@ -137,13 +132,13 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 判断物品堆栈是否有UUID public boolean hasUUID() { - return this.stack.hasTag() && this.stack.getOrCreateTag().contains("uuid"); + return stack.hasTag() && stack.getOrCreateTag().contains("uuid"); } // 获取物品堆栈的UUID public UUID getUUID() { if (this.hasUUID()) { - return this.stack.getOrCreateTag().getUUID("uuid"); + return stack.getOrCreateTag().getUUID("uuid"); } else { return null; } @@ -151,17 +146,17 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 获取或初始化存储映射 private Object2ObjectMap getCellStoredMap() { - if (this.storedMap == null) { - this.storedMap = new Object2ObjectOpenHashMap<>(); + if (storedMap == null) { + storedMap = new Object2ObjectOpenHashMap<>(); this.loadCellStoredMap(); } - return this.storedMap; + return storedMap; } // 从存储中加载物品映射 private void loadCellStoredMap() { boolean corruptedTag = false; // 标记数据是否损坏 - if (!this.stack.hasTag()) + if (!stack.hasTag()) return; ListTag keys = this.getCellStorage().keys; ListTag amounts = this.getCellStorage().amounts; @@ -200,38 +195,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 标记数据需要保存,并通知容器或直接持久化 private void saveChanges() { // 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O - this.isPersisted = false; - // 将本实例加入待处理队列(去重) - if (!PENDING_PERSIST.contains(this)) { - PENDING_PERSIST.add(this); - } - if (this.container != null) { - // 当存在容器时,仍然通知容器以便 AE2 在合并时回调 persist() - this.container.saveChanges(); - } - } - - /** - * 每个服务器 tick 调用一次,用于按秒合并并执行待持久化项的 persist() - */ - public static void onServerTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.END) return; - TICK_COUNTER++; - if (TICK_COUNTER % 20 != 0) return; // 每 20 tick(约 1 秒)执行一次 - onServerStopping(null); - } - - /** - * 在服务器停止时强制刷新所有待持久化项 - */ - public static void onServerStopping(ServerStoppingEvent event) { - InfinityBigIntegerCellInventory inv; - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - inv.persist(); - } catch (Exception e) { - e.printStackTrace(); - } + isPersisted = false; + if (container != null) { + // 当存在容器时,优先让容器统一处理持久化 + container.saveChanges(); + } else { + persist(); } } @@ -282,10 +251,10 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 如果存储为空,移除UUID和全局存储中的数据,并清理缓存的 types/total if (hasUUID()) { InfinityStorageManager.INSTANCE.removeCell(getUUID()); - if (this.stack.getTag() != null) { - this.stack.getTag().remove("uuid"); - this.stack.getTag().remove("types"); - this.stack.getTag().remove("total"); + if (stack.getTag() != null) { + stack.getTag().remove("uuid"); + stack.getTag().remove("types"); + stack.getTag().remove("total"); } initData(); } @@ -311,36 +280,34 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (keys.isEmpty()) { InfinityStorageManager.INSTANCE.updateCell(this.getUUID(), new InfinityDataStorage()); // 清理缓存 - if (this.stack.getTag() != null) { - this.stack.getTag().remove("types"); - this.stack.getTag().remove("total"); + if (stack.getTag() != null) { + stack.getTag().remove("types"); + stack.getTag().remove("total"); } } else { // amounts 现在为 CompoundTag 列表 InfinityStorageManager.INSTANCE.modifyCell(this.getUUID(), keys, amountTags); // 缓存类型数量与总量到 ItemStack 的 NBT,避免每次 tooltip 或展示时重新统计 try { - if (this.stack.getTag() == null) { - this.stack.setTag(new CompoundTag()); - } + if (stack.getTag() == null) stack.setTag(new CompoundTag()); int typesCount = keys.size(); - this.stack.getOrCreateTag().putInt("types", typesCount); - BigInteger total = BigInteger.ZERO; - for (Map.Entry e : map.object2ObjectEntrySet()) { - BigInteger v = e.getValue(); - if (v.compareTo(BigInteger.ZERO) > 0) { + 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(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { - this.stack.getOrCreateTag().putLong("total", total.longValue()); + if (total.compareTo(java.math.BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { + stack.getOrCreateTag().putLong("total", total.longValue()); } else { - this.stack.getOrCreateTag().putString("total", total.toString()); + stack.getOrCreateTag().putString("total", total.toString()); } } catch (Exception ignored) { } } - this.isPersisted = true; + isPersisted = true; } // 插入物品到存储单元 @@ -356,7 +323,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } // 如果没有UUID,生成UUID并初始化存储(延迟创建全局存储以避免在 manager 未就绪时 NPE) if (!this.hasUUID()) { - this.stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); + stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); InfinityStorageManager.INSTANCE.getOrCreateCell(getUUID()); // 确保 storedMap 初始化并从持久层加载数据 loadCellStoredMap(); @@ -403,4 +370,16 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } return 0; } + + // 获取存储单元内所有物品的总数量(格式化字符串) + 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); + } } 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 9aa2f64..26d880a 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -1,6 +1,5 @@ package com.extendedae_plus.util.storage; -import com.extendedae_plus.ExtendedAEPlus; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; @@ -13,13 +12,15 @@ import java.io.File; import java.io.IOException; 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; /** * InfinityStorageManager - *

+ * * 替代之前基于 SavedData 的实现,本类使用手动文件 I/O 在 world 目录下保存 NBT 数据, * 以避免依赖 Minecraft 的 SavedData 机制。 * 数据保持与之前兼容的 NBT 结构:根 Compound 包含 "list" => ListTag of Compound { uuid, data } @@ -28,16 +29,15 @@ public class InfinityStorageManager { public static final String FILE_NAME = "eap_infinity_biginteger_cells.dat"; - /** - * 全局单例,由 mod 在 world load 时初始化 - */ + /** 全局单例,由 mod 在 world load 时初始化 */ public static volatile InfinityStorageManager INSTANCE = new InfinityStorageManager(); private final Map cells = new HashMap<>(); private Path saveFilePath = null; - private InfinityStorageManager() {} + public InfinityStorageManager() { + } /** * 初始化并从 world 保存目录加载数据;若文件不存在则保持空状态 @@ -47,17 +47,15 @@ public class InfinityStorageManager { try { File worldFolder = serverLevel.getServer().getWorldPath(LevelResource.ROOT).toFile(); // 保存到 world// 文件夹下,避免与其它 mod 冲突 - File modDir = new File(worldFolder, ExtendedAEPlus.MODID); - if (!modDir.exists()) { - modDir.mkdirs(); - } - this.saveFilePath = new File(modDir, FILE_NAME).toPath(); - if (Files.exists(this.saveFilePath)) { - CompoundTag root = NbtIo.readCompressed(this.saveFilePath.toFile()); + 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); - this.cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); + cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); } } } catch (IOException e) { @@ -70,40 +68,58 @@ public class InfinityStorageManager { * 保存当前内存数据到文件(会覆盖已有文件) */ public synchronized void saveToFile() { - if (this.saveFilePath == null) - return; + if (saveFilePath == null) return; try { CompoundTag root = new CompoundTag(); ListTag cellList = new ListTag(); - for (Map.Entry entry : this.cells.entrySet()) { + 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); - // 使用压缩写入以节省空间 - NbtIo.writeCompressed(root, this.saveFilePath.toFile()); + // 使用压缩写入到临时文件,然后原子替换目标文件以避免半成品/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); + try { + Files.move(tmp, saveFilePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException ex) { + // 若底层文件系统不支持原子移动,退回到非原子替换 + Files.move(tmp, saveFilePath, StandardCopyOption.REPLACE_EXISTING); + } } catch (IOException e) { e.printStackTrace(); } } public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) { - this.cells.put(uuid, infinityDataStorage); + if (uuid == null) return; // 忽略无效 UUID + cells.put(uuid, infinityDataStorage); saveToFile(); } public InfinityDataStorage getOrCreateCell(UUID uuid) { - if (!this.cells.containsKey(uuid)) { + if (uuid == null) { + return InfinityDataStorage.EMPTY; + } + if (!cells.containsKey(uuid)) { InfinityDataStorage newCell = new InfinityDataStorage(); - this.cells.put(uuid, newCell); + cells.put(uuid, newCell); saveToFile(); } - return this.cells.get(uuid); + return cells.get(uuid); } 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; @@ -113,7 +129,8 @@ public class InfinityStorageManager { } public void removeCell(UUID uuid) { - this.cells.remove(uuid); + if (uuid == null) return; + cells.remove(uuid); saveToFile(); } }