From 37d24334bb00dcc0051293b72188eb3262255192 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 16 Sep 2025 20:01:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Infinity=20?= =?UTF-8?q?=E5=AD=98=E5=82=A8=EF=BC=9A=E7=A7=BB=E9=99=A4=E5=8F=AF=E5=8F=98?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=20EMPTY=EF=BC=8C=E5=B0=81=E8=A3=85=20NBT=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=9B=E6=8C=89=E4=B8=96=E7=95=8C=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20StorageManager=EF=BC=88=E7=A7=BB=E9=99=A4=E5=85=A8?= =?UTF-8?q?=E5=B1=80=20INSTANCE=EF=BC=89=EF=BC=9B=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=97=B6=E9=87=8D=E7=BD=AE=20totalStored=20=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E7=B4=AF=E8=AE=A1=EF=BC=9B=E7=94=A8=20Atomic?= =?UTF-8?q?Boolean=20=E4=BB=A3=E6=9B=BF=E9=98=9F=E5=88=97=20contains?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=AD=A3=20persist=20=E7=9A=84=E7=A9=BA/?= =?UTF-8?q?=E6=97=A0=E7=AE=A1=E7=90=86=E5=99=A8=E5=A4=84=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InfinityBigIntegerCellInventory.java | 49 ++++++++++++++----- .../util/storage/InfinityDataStorage.java | 32 ++++++++++-- .../util/storage/InfinityStorageManager.java | 36 +++++++++++--- 3 files changed, 93 insertions(+), 24 deletions(-) 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 19273c4..7e2216f 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 @@ -26,6 +26,7 @@ import java.math.RoundingMode; import java.text.DecimalFormat; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; /** * InfinityBigIntegerCellInventory @@ -91,9 +92,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return null; } - // 获取全局存储实例 + // 获取全局存储实例(尽量安全地获取任意已注册的世界实例) private static InfinityStorageManager getStorageInstance() { - return InfinityStorageManager.INSTANCE; + return InfinityStorageManager.getAnyInstance(); } // 服务器 tick 回调:合并并执行待持久化项 @@ -133,10 +134,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell { private InfinityDataStorage getCellStorage() { if (this.getUUID() == null) { // 如果没有UUID,返回空存储 - return InfinityDataStorage.EMPTY; + return InfinityDataStorage.empty(); } else { - // 否则获取或创建对应UUID的存储 - return getStorageInstance().getOrCreateCell(getUUID()); + // 否则获取或创建对应UUID的存储,若管理器不可用则返回空存储以避免 NPE + InfinityStorageManager mgr = getStorageInstance(); + if (mgr == null) return InfinityDataStorage.empty(); + return mgr.getOrCreateCell(getUUID()); } } @@ -187,8 +190,11 @@ 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; + // 在加载前重置缓存,避免重复累计 + totalStored = BigInteger.ZERO; + InfinityDataStorage storage = this.getCellStorage(); + ListTag keys = storage.getKeys(); + ListTag amounts = storage.getAmounts(); int len = Math.min(keys.size(), amounts.size()); for (int i = 0; i < len; i++) { AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); @@ -223,6 +229,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } // 标记数据需要保存,并通知容器或直接持久化 + private final AtomicBoolean queued = new AtomicBoolean(false); + private void saveChanges() { // 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O isPersisted = false; @@ -231,7 +239,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { container.saveChanges(); } else { // 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O - if (!PENDING_PERSIST.contains(this)) { + if (queued.compareAndSet(false, true)) { PENDING_PERSIST.offer(this); } } @@ -258,13 +266,21 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (map.isEmpty()) { // 如果存储为空,移除UUID和全局存储中的数据 if (this.hasUUID()) { - getStorageInstance().removeCell(getUUID()); + InfinityStorageManager mgr = getStorageInstance(); + if (mgr != null) mgr.removeCell(getUUID()); if (stack.getTag() != null) { stack.getTag().remove("uuid"); // 移除缓存的 total 字段 stack.getTag().remove("total"); } + // 清理 queued 标志并标记已持久化 + queued.set(false); + isPersisted = true; + return; } + // 无 UUID 且为空,不需要做额外操作,但确保已标记为已持久化 + queued.set(false); + isPersisted = true; return; } // 构建要保存的Key和数量列表(混合表示:long 或 string) @@ -284,11 +300,16 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } } // 如果没有Key,更新为空存储,否则保存数据 - if (keys.isEmpty()) { - getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage()); + InfinityStorageManager mgr = getStorageInstance(); + if (mgr == null) { + // 无可用存储管理器,跳过持久化(保留内存状态) } else { - // amounts 现在为 CompoundTag 列表 - getStorageInstance().modifyCell(this.getUUID(), keys, amountTags); + if (keys.isEmpty()) { + mgr.updateCell(this.getUUID(), new InfinityDataStorage()); + } else { + // amounts 现在为 CompoundTag 列表 + mgr.modifyCell(this.getUUID(), keys, amountTags); + } } // 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long if (stack.getOrCreateTag() != null) { @@ -299,6 +320,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } } isPersisted = true; + // 持久化完成后,清除 queued 标志 + queued.set(false); } // 插入物品到存储单元 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 4ef9036..0a44d87 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -18,17 +18,17 @@ import net.minecraft.nbt.Tag; */ public class InfinityDataStorage { - /** 空实例(表示没有数据) */ - public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); + // 不再暴露可变的共享实例,避免多个调用方修改同一 ListTag 导致交叉污染 + private static final InfinityDataStorage TRUE_EMPTY = new InfinityDataStorage(new ListTag(), new ListTag()); /** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */ - public ListTag keys; + private ListTag keys; /** * 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag): * - 若数量能放入 long,则 CompoundTag 包含键 "l"(long) * - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式 */ - public ListTag amounts; + private ListTag amounts; public InfinityDataStorage() { this(new ListTag(), new ListTag()); @@ -39,6 +39,14 @@ public class InfinityDataStorage { this.amounts = amounts; } + /** + * 返回一个空的不可共享实例(调用方若需要可变副本请自行复制) + */ + public static InfinityDataStorage empty() { + // 返回一个新的实例以避免共享可变对象被篡改 + return new InfinityDataStorage(new ListTag(), new ListTag()); + } + /** * 将当前数据封装为 CompoundTag 以写入存档 */ @@ -58,4 +66,20 @@ public class InfinityDataStorage { ListTag stackAmounts = nbt.getList("amounts", Tag.TAG_COMPOUND); return new InfinityDataStorage(stackKeys, stackAmounts); } + + public ListTag getKeys() { + return keys; + } + + public ListTag getAmounts() { + return amounts; + } + + public void setKeys(ListTag keys) { + this.keys = keys; + } + + public void setAmounts(ListTag amounts) { + this.amounts = amounts; + } } 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 e24a5ab..1b36fbd 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -2,13 +2,16 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; import net.minecraft.world.level.saveddata.SavedData; import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** * InfinityStorageManager @@ -26,9 +29,9 @@ public class InfinityStorageManager extends SavedData { */ public static final String FILE_NAME = "eap_infinity_biginteger_cells"; /** - * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充) + * Per-world instances to avoid cross-world leakage. Keyed by world ResourceKey. */ - public static InfinityStorageManager INSTANCE = null; + private static final Map, InfinityStorageManager> INSTANCES = new ConcurrentHashMap<>(); /** * UUID -> 数据 的内存映射 */ @@ -54,10 +57,29 @@ public class InfinityStorageManager extends SavedData { * 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE */ public static InfinityStorageManager getForLevel(ServerLevel level) { - if (INSTANCE == null && level != null) { - INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); + if (level == null) return null; + ResourceKey key = level.dimension(); + InfinityStorageManager mgr = INSTANCES.get(key); + if (mgr == null) { + mgr = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); + INSTANCES.put(key, mgr); } - return INSTANCE; + return mgr; + } + + /** + * 返回任何现有实例,作为无法访问 ServerLevel 的代码路径的安全回退。 + */ + public static InfinityStorageManager getAnyInstance() { + return INSTANCES.values().stream().findFirst().orElse(null); + } + + /** + * 删除世界的实例(在世界卸载时调用) + */ + public static void removeForLevel(ServerLevel level) { + if (level == null) return; + INSTANCES.remove(level.dimension()); } @Override @@ -98,8 +120,8 @@ public class InfinityStorageManager extends SavedData { public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { InfinityDataStorage cellToModify = getOrCreateCell(cellID); if (stackKeys != null && stackAmounts != null) { - cellToModify.keys = stackKeys; - cellToModify.amounts = stackAmounts; + cellToModify.setKeys(stackKeys); + cellToModify.setAmounts(stackAmounts); } updateCell(cellID, cellToModify); }