fix: 修复 Infinity 存储:移除可变共享 EMPTY,封装 NBT 字段;按世界管理 StorageManager(移除全局 INSTANCE);加载时重置 totalStored 防止重复累计;用 AtomicBoolean 代替队列 contains,修正 persist 的空/无管理器处理。

This commit is contained in:
C-H716 2025-09-16 20:01:03 +08:00
parent 9bdda0526f
commit 37d24334bb
3 changed files with 93 additions and 24 deletions

View File

@ -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);
}
// 插入物品到存储单元

View File

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

View File

@ -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<Level>.
*/
public static InfinityStorageManager INSTANCE = null;
private static final Map<ResourceKey<Level>, 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<Level> 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);
}