fix: 回退无限磁盘修改至上一版本,再次修复,添加版本号

This commit is contained in:
C-H716 2025-09-20 02:32:11 +08:00
parent 49b0ef0726
commit ba79d0e31a
3 changed files with 94 additions and 22 deletions

View File

@ -16,6 +16,8 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag; import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component; 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.minecraft.world.item.ItemStack;
import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.event.server.ServerStoppingEvent;
@ -23,7 +25,6 @@ import net.minecraftforge.event.server.ServerStoppingEvent;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
@ -48,8 +49,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 待持久化队列用于 debounce在服务器 tick 中合并持久化 // 待持久化队列用于 debounce在服务器 tick 中合并持久化
private static final ConcurrentLinkedQueue<InfinityBigIntegerCellInventory> PENDING_PERSIST = new ConcurrentLinkedQueue<>(); private static final ConcurrentLinkedQueue<InfinityBigIntegerCellInventory> PENDING_PERSIST = new ConcurrentLinkedQueue<>();
// 数字格式化对象保留两位小数复用以减少对象分配 // 数字格式化对象改为方法局部以避免静态非线程安全问题
private static final DecimalFormat DF = new DecimalFormat("#.##");
// 关联的 ItemStack含可能的 uuid NBT // 关联的 ItemStack含可能的 uuid NBT
private final ItemStack stack; private final ItemStack stack;
@ -106,6 +106,17 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 在服务器停止时被调用立即强制持久化队列中的所有实例 // 在服务器停止时被调用立即强制持久化队列中的所有实例
public static void onServerStopping(ServerStoppingEvent event) { 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; InfinityBigIntegerCellInventory inv;
while ((inv = PENDING_PERSIST.poll()) != null) { while ((inv = PENDING_PERSIST.poll()) != null) {
try { try {
@ -116,7 +127,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage()); LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage());
} }
} }
// 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回在单人模式下可能直接由系统触发 // 将全局存储管理器标记为脏以确保 SavedData 被写回
try { try {
var stor = getStorageInstance(); var stor = getStorageInstance();
if (stor != null) stor.setDirty(); if (stor != null) stor.setDirty();
@ -127,7 +138,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// BigInteger 格式化为带单位的字符串保留两位小数 // BigInteger 格式化为带单位的字符串保留两位小数
public static String formatBigInteger(BigInteger number) { public static String formatBigInteger(BigInteger number) {
// 使用局部 DF非线程安全 Minecraft 通常在主线程运行 // 使用方法局部的 DecimalFormat避免静态共享的非线程安全问题
java.text.DecimalFormat df = new java.text.DecimalFormat("#.##");
BigDecimal bd = new BigDecimal(number); BigDecimal bd = new BigDecimal(number);
BigDecimal thousand = new BigDecimal(1000); BigDecimal thousand = new BigDecimal(1000);
String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"}; String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"};
@ -139,17 +151,23 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
if (idx == 0) { if (idx == 0) {
return bd.setScale(0, RoundingMode.DOWN).toPlainString(); return bd.setScale(0, RoundingMode.DOWN).toPlainString();
} }
return DF.format(bd.doubleValue()) + units[idx]; return df.format(bd.doubleValue()) + units[idx];
} }
// 获取当前存储单元的数据存储对象 // 获取当前存储单元的数据存储对象
private InfinityDataStorage getCellStorage() { private InfinityDataStorage getCellStorage() {
if (this.getUUID() == null) { if (this.getUUID() == null) {
// 如果没有UUID返回空存储 // 如果没有UUID返回空存储返回新实例以避免共享可变状态
return InfinityDataStorage.EMPTY; return InfinityDataStorage.empty();
} else { } else {
// 在访问全局 SavedData 之前做防御性检查避免在客户端或尚未初始化的情况下 NPE
InfinityStorageManager mgr = getStorageInstance();
if (mgr == null) {
// SavedData 尚未就绪视为空存储返回新实例以避免共享可变状态
return InfinityDataStorage.empty();
}
// 否则获取或创建对应UUID的存储 // 否则获取或创建对应UUID的存储
return getStorageInstance().getOrCreateCell(getUUID()); return mgr.getOrCreateCell(getUUID());
} }
} }
@ -200,8 +218,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
private void loadCellStoredMap() { private void loadCellStoredMap() {
boolean corruptedTag = false; // 标记数据是否损坏 boolean corruptedTag = false; // 标记数据是否损坏
if (!stack.hasTag()) return; if (!stack.hasTag()) return;
ListTag keys = this.getCellStorage().keys; InfinityDataStorage storage = this.getCellStorage();
ListTag amounts = this.getCellStorage().amounts; ListTag keys = storage.keys;
ListTag amounts = storage.amounts;
int len = Math.min(keys.size(), amounts.size()); int len = Math.min(keys.size(), amounts.size());
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); AEKey key = AEKey.fromTagGeneric(keys.getCompound(i));
@ -233,6 +252,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
if (corruptedTag) { if (corruptedTag) {
this.saveChanges(); this.saveChanges();
} }
// 打印加载后的摘要日志
try {
var uuid = this.getUUID();
LOGGER.info("Loaded cell {}: types={}, totalCached={}", uuid, this.getCellStoredMap().size(), this.getTotalStorage());
} catch (Throwable ignored) {
}
} }
// 标记数据需要保存并通知容器或直接持久化 // 标记数据需要保存并通知容器或直接持久化
@ -289,16 +314,32 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
public void persist() { public void persist() {
if (this.isPersisted) if (this.isPersisted)
return; 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<AEKey, BigInteger> map = this.getCellStoredMap(); Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
if (map.isEmpty()) { if (map.isEmpty()) {
// 如果存储为空移除UUID和全局存储中的数据 // 如果存储为空移除UUID和全局存储中的数据
if (this.hasUUID()) { if (this.hasUUID()) {
getStorageInstance().removeCell(getUUID()); mgr.removeCell(getUUID());
if (stack.getTag() != null) { if (stack.getTag() != null) {
stack.getTag().remove("uuid"); stack.getTag().remove("uuid");
// 移除缓存的 total 字段 // 移除缓存的 total 字段
stack.getTag().remove("total"); stack.getTag().remove("total");
} }
// 标记为已持久化避免重复尝试
isPersisted = true;
} }
return; return;
} }
@ -320,10 +361,10 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
} }
// 如果没有Key更新为空存储否则保存数据 // 如果没有Key更新为空存储否则保存数据
if (keys.isEmpty()) { if (keys.isEmpty()) {
getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage()); mgr.updateCell(this.getUUID(), new InfinityDataStorage());
} else { } else {
// amounts 现在为 CompoundTag 列表 // amounts 现在为 CompoundTag 列表
getStorageInstance().modifyCell(this.getUUID(), keys, amountTags); mgr.modifyCell(this.getUUID(), keys, amountTags);
} }
// 将缓存的 totalStored 同步到 ItemStack NBT优先使用 long // 将缓存的 totalStored 同步到 ItemStack NBT优先使用 long
if (stack.getOrCreateTag() != null) { if (stack.getOrCreateTag() != null) {
@ -337,6 +378,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
stack.getOrCreateTag().putInt("types", typesCount); stack.getOrCreateTag().putInt("types", typesCount);
} }
isPersisted = true; isPersisted = true;
// 打印持久化摘要日志
try {
var uuid = this.getUUID();
LOGGER.info("Persisted cell {}: types={}, totalCached={}", uuid, this.getCellStoredMap().size(), this.getTotalStorage());
} catch (Throwable ignored) {
}
} }
// 插入物品到存储单元 // 插入物品到存储单元
@ -348,12 +395,19 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 不允许存储无限单元自身 // 不允许存储无限单元自身
if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem)
return 0; return 0;
// 如果没有UUID生成UUID并初始化存储 // 如果没有UUID尝试在服务器端且存储管理器已就绪时生成UUID并初始化存储
if (!this.hasUUID()) { if (!this.hasUUID()) {
stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); InfinityStorageManager mgr = getStorageInstance();
getStorageInstance().getOrCreateCell(getUUID()); // 仅在 SavedData 已就绪通常在服务器端时创建 UUID 并注册持久化存储
// 确保 storedMap 初始化并从持久层加载数据 if (mgr != null) {
this.getCellStoredMap(); stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID());
mgr.getOrCreateCell(getUUID());
// 确保 storedMap 初始化并从持久层加载数据
this.getCellStoredMap();
} else {
// SavedData 未就绪把插入作为本地内存操作不生成 UUID不持久化确保 storedMap 初始化以容纳临时数据
this.getCellStoredMap();
}
} }
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap(); Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);

View File

@ -18,8 +18,10 @@ import net.minecraft.nbt.Tag;
*/ */
public class InfinityDataStorage { public class InfinityDataStorage {
/** 空实例(表示没有数据) */ /** 空实例的访问器(返回新实例以避免共享可变状态) */
public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); public static InfinityDataStorage empty() {
return new InfinityDataStorage();
}
/** 序列化的键列表NBT ListTag元素为 CompoundTag */ /** 序列化的键列表NBT ListTag元素为 CompoundTag */
public ListTag keys; public ListTag keys;

View File

@ -10,6 +10,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
/** /**
* InfinityStorageManager * InfinityStorageManager
* <p> * <p>
@ -21,6 +23,10 @@ import java.util.UUID;
*/ */
public class InfinityStorageManager extends SavedData { public class InfinityStorageManager extends SavedData {
// 当前磁盘格式版本号增加字段用于向后/向前兼容
private static final int FORMAT_VERSION = 1;
/** /**
* SavedData 文件名常量 * SavedData 文件名常量
*/ */
@ -42,10 +48,18 @@ public class InfinityStorageManager extends SavedData {
* NBT 构造用于在世界加载时从存档恢复数据 * NBT 构造用于在世界加载时从存档恢复数据
*/ */
public InfinityStorageManager(CompoundTag nbt) { public InfinityStorageManager(CompoundTag nbt) {
// 读取格式版本缺省视为 1兼容旧档
int version = nbt.contains("format_version") ? nbt.getInt("format_version") : 1;
LOGGER.info("Loading InfinityStorageManager format_version={}", version);
ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND); ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
for (int i = 0; i < cellList.size(); i++) { for (int i = 0; i < cellList.size(); i++) {
CompoundTag cell = cellList.getCompound(i); CompoundTag cell = cellList.getCompound(i);
cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); 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(); setDirty();
} }
@ -71,6 +85,8 @@ public class InfinityStorageManager extends SavedData {
cellList.add(cell); cellList.add(cell);
} }
nbt.put("list", cellList); nbt.put("list", cellList);
// 写入当前格式版本号便于未来迁移与兼容判断
nbt.putInt("format_version", FORMAT_VERSION);
return nbt; return nbt;
} }