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); }