fix: 使用自定义I/O存储管理器替代原有SavedData

This commit is contained in:
C-H716 2025-09-19 18:16:18 +08:00
parent 49b0ef0726
commit 572b6e6f17
5 changed files with 206 additions and 213 deletions

View File

@ -53,12 +53,11 @@ public class ExtendedAEPlus {
// 注册到Forge事件总线 // 注册到Forge事件总线
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(this);
MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad);
// 注册通用配置 // 注册每秒合并持久化队列的事件监听Server tick end + stopping
ModConfig.init();
// 注册 InfinityBigIntegerCellInventory 的事件监听tick flush 与停止时 flush
MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick);
MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping); MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping);
// ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC); // 注册通用配置
ModConfig.init();
} }
/** /**
@ -120,7 +119,8 @@ public class ExtendedAEPlus {
// 在世界加载时注册/加载 SavedData // 在世界加载时注册/加载 SavedData
private static void onLevelLoad(LevelEvent.Load event) { private static void onLevelLoad(LevelEvent.Load event) {
if (event.getLevel() instanceof ServerLevel serverLevel) { if (event.getLevel() instanceof ServerLevel serverLevel) {
InfinityStorageManager.getForLevel(serverLevel); // 初始化自定义的文件 I/O 存储管理器
InfinityStorageManager.INSTANCE.initFromWorld(serverLevel);
} }
} }
} }

View File

@ -24,10 +24,10 @@ 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.text.DecimalFormat;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
/** /**
* InfinityBigIntegerCellInventory * InfinityBigIntegerCellInventory
* <p> * <p>
@ -48,6 +48,8 @@ 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<>();
// 用于按 tick 计数 20 tick 1 触发一次合并写入
private static int TICK_COUNTER = 0;
// 数字格式化对象保留两位小数复用以减少对象分配 // 数字格式化对象保留两位小数复用以减少对象分配
private static final DecimalFormat DF = new DecimalFormat("#.##"); private static final DecimalFormat DF = new DecimalFormat("#.##");
@ -59,8 +61,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
private Object2ObjectMap<AEKey, BigInteger> storedMap = null; private Object2ObjectMap<AEKey, BigInteger> storedMap = null;
// 标记是否已持久化到 SavedData // 标记是否已持久化到 SavedData
private boolean isPersisted = true; private boolean isPersisted = true;
// 缓存的总存储量避免每次调用进行全表扫描
private BigInteger totalStored = BigInteger.ZERO;
/** /**
* 私有构造器通过 createInventory 工厂方法调用 * 私有构造器通过 createInventory 工厂方法调用
@ -70,59 +71,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
*/ */
private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) { private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) {
this.stack = stack; this.stack = stack;
container = saveProvider; this.container = saveProvider;
// 不在构造时创建 storedMap推迟到实际访问或首次写入时初始化
this.storedMap = null; this.storedMap = null;
} initData();
// 创建存储单元库存实例的静态方法
static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) {
if (stack.getItem() instanceof InfinityBigIntegerCellItem) {
return new InfinityBigIntegerCellInventory(stack, saveProvider);
}
return null;
}
// 获取全局存储实例
private static InfinityStorageManager getStorageInstance() {
return InfinityStorageManager.INSTANCE;
}
// 服务器 tick 回调合并并执行待持久化项
public static void onServerTick(TickEvent.ServerTickEvent event) {
if (event.phase != TickEvent.Phase.END) return;
InfinityBigIntegerCellInventory inv;
// 处理本次 tick 中的全部待持久化项
while ((inv = PENDING_PERSIST.poll()) != null) {
try {
if (!inv.isPersisted) {
inv.persist();
}
} catch (Throwable ignored) {
LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ignored.getMessage());
}
}
}
// 在服务器停止时被调用立即强制持久化队列中的所有实例
public static void onServerStopping(ServerStoppingEvent event) {
InfinityBigIntegerCellInventory inv;
while ((inv = PENDING_PERSIST.poll()) != null) {
try {
if (!inv.isPersisted) {
inv.persist();
}
} catch (Throwable ignored) {
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage());
}
}
// 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回在单人模式下可能直接由系统触发
try {
var stor = getStorageInstance();
if (stor != null) stor.setDirty();
} catch (Throwable ignored) {
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error2: {}", ignored.getMessage());
}
} }
// BigInteger 格式化为带单位的字符串保留两位小数 // BigInteger 格式化为带单位的字符串保留两位小数
@ -142,14 +93,24 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
return DF.format(bd.doubleValue()) + units[idx]; return DF.format(bd.doubleValue()) + units[idx];
} }
// 创建存储单元库存实例的静态方法
static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) {
return new InfinityBigIntegerCellInventory(stack, saveProvider);
}
private void initData() {
if (!hasUUID()) {
getCellStoredMap();
}
}
// 获取当前存储单元的数据存储对象 // 获取当前存储单元的数据存储对象
private InfinityDataStorage getCellStorage() { private InfinityDataStorage getCellStorage() {
if (this.getUUID() == null) { if (this.getUUID() == null) {
// 如果没有UUID返回空存储 // 如果没有UUID返回空存储
return InfinityDataStorage.EMPTY; return InfinityDataStorage.EMPTY;
} else { } else {
// 否则获取或创建对应UUID的存储 return InfinityStorageManager.INSTANCE.getOrCreateCell(this.getUUID());
return getStorageInstance().getOrCreateCell(getUUID());
} }
} }
@ -176,34 +137,37 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 判断物品堆栈是否有UUID // 判断物品堆栈是否有UUID
public boolean hasUUID() { public boolean hasUUID() {
return stack.hasTag() && stack.getOrCreateTag().contains("uuid"); return this.stack.hasTag() && this.stack.getOrCreateTag().contains("uuid");
} }
// 获取物品堆栈的UUID // 获取物品堆栈的UUID
public UUID getUUID() { public UUID getUUID() {
if (this.hasUUID()) if (this.hasUUID()) {
return stack.getOrCreateTag().getUUID("uuid"); return this.stack.getOrCreateTag().getUUID("uuid");
else } else {
return null; return null;
}
} }
// 获取或初始化存储映射 // 获取或初始化存储映射
private Object2ObjectMap<AEKey, BigInteger> getCellStoredMap() { private Object2ObjectMap<AEKey, BigInteger> getCellStoredMap() {
if (storedMap == null) { if (this.storedMap == null) {
storedMap = new Object2ObjectOpenHashMap<>(); this.storedMap = new Object2ObjectOpenHashMap<>();
this.loadCellStoredMap(); this.loadCellStoredMap();
} }
return storedMap; return this.storedMap;
} }
// 从存储中加载物品映射 // 从存储中加载物品映射
private void loadCellStoredMap() { private void loadCellStoredMap() {
boolean corruptedTag = false; // 标记数据是否损坏 boolean corruptedTag = false; // 标记数据是否损坏
if (!stack.hasTag()) return; if (!this.stack.hasTag())
return;
ListTag keys = this.getCellStorage().keys; ListTag keys = this.getCellStorage().keys;
ListTag amounts = this.getCellStorage().amounts; ListTag amounts = this.getCellStorage().amounts;
int len = Math.min(keys.size(), amounts.size());
for (int i = 0; i < len; i++) {
for (int i = 0; i < amounts.size(); i++) {
AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); AEKey key = AEKey.fromTagGeneric(keys.getCompound(i));
CompoundTag amtTag = amounts.getCompound(i); CompoundTag amtTag = amounts.getCompound(i);
try { try {
@ -221,15 +185,13 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
corruptedTag = true; corruptedTag = true;
} else { } else {
// storedMap 已在 getCellStoredMap() 中初始化直接使用字段以避免额外方法开销 // storedMap 已在 getCellStoredMap() 中初始化直接使用字段以避免额外方法开销
storedMap.put(key, amount); getCellStoredMap().put(key, amount);
// 更新缓存的总数
totalStored = totalStored.add(amount);
} }
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
corruptedTag = true; corruptedTag = true;
} }
} }
// 如果有损坏保存修正后的数据 // 如果有损坏尝试保存修正后的数据若全局管理器尚未就绪则保守处理
if (corruptedTag) { if (corruptedTag) {
this.saveChanges(); this.saveChanges();
} }
@ -238,14 +200,37 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 标记数据需要保存并通知容器或直接持久化 // 标记数据需要保存并通知容器或直接持久化
private void saveChanges() { private void saveChanges() {
// 标记为未持久化交由容器或延迟任务合并写入以减少 I/O // 标记为未持久化交由容器或延迟任务合并写入以减少 I/O
isPersisted = false; this.isPersisted = false;
if (container != null) { // 将本实例加入待处理队列去重
// 当存在容器时优先让容器统一处理持久化 if (!PENDING_PERSIST.contains(this)) {
container.saveChanges(); PENDING_PERSIST.add(this);
} else { }
// 如果没有容器入队等待服务器 tick 在主线程统一持久化避免频繁 I/O if (this.container != null) {
if (!PENDING_PERSIST.contains(this)) { // 当存在容器时仍然通知容器以便 AE2 在合并时回调 persist()
PENDING_PERSIST.offer(this); 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();
} }
} }
} }
@ -287,20 +272,25 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 持久化存储单元数据到全局存储 // 持久化存储单元数据到全局存储
@Override @Override
public void persist() { public void persist() {
if (this.isPersisted) if (this.isPersisted) {
return; return;
}
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap(); Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
if (map.isEmpty()) { if (map.isEmpty()) {
// 如果存储为空移除UUID和全局存储中的数据 // 如果存储为空保守处理写回空的 persisted 数据但不要从 ItemStack 上移除 uuid
if (this.hasUUID()) { if (this.hasUUID()) {
getStorageInstance().removeCell(getUUID()); // 如果存储为空移除UUID和全局存储中的数据并清理缓存的 types/total
if (stack.getTag() != null) { if (hasUUID()) {
stack.getTag().remove("uuid"); InfinityStorageManager.INSTANCE.removeCell(getUUID());
// 移除缓存的 total 字段 if (this.stack.getTag() != null) {
stack.getTag().remove("total"); this.stack.getTag().remove("uuid");
this.stack.getTag().remove("types");
this.stack.getTag().remove("total");
}
initData();
} }
return;
} }
return;
} }
// 构建要保存的Key和数量列表混合表示long string // 构建要保存的Key和数量列表混合表示long string
ListTag amountTags = new ListTag(); ListTag amountTags = new ListTag();
@ -318,42 +308,58 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
amountTags.add(amt); amountTags.add(amt);
} }
} }
// 如果没有Key更新为空存储否则保存数据
if (keys.isEmpty()) { if (keys.isEmpty()) {
getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage()); InfinityStorageManager.INSTANCE.updateCell(this.getUUID(), new InfinityDataStorage());
// 清理缓存
if (this.stack.getTag() != null) {
this.stack.getTag().remove("types");
this.stack.getTag().remove("total");
}
} else { } else {
// amounts 现在为 CompoundTag 列表 // amounts 现在为 CompoundTag 列表
getStorageInstance().modifyCell(this.getUUID(), keys, amountTags); InfinityStorageManager.INSTANCE.modifyCell(this.getUUID(), keys, amountTags);
} // 缓存类型数量与总量到 ItemStack NBT避免每次 tooltip 或展示时重新统计
// 将缓存的 totalStored 同步到 ItemStack NBT优先使用 long try {
if (stack.getOrCreateTag() != null) { if (this.stack.getTag() == null) {
if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { this.stack.setTag(new CompoundTag());
stack.getOrCreateTag().putLong("total", totalStored.longValue()); }
} else { int typesCount = keys.size();
stack.getOrCreateTag().putString("total", totalStored.toString()); this.stack.getOrCreateTag().putInt("types", typesCount);
BigInteger total = BigInteger.ZERO;
for (Map.Entry<AEKey, BigInteger> e : map.object2ObjectEntrySet()) {
BigInteger v = e.getValue();
if (v.compareTo(BigInteger.ZERO) > 0) {
total = total.add(v);
}
}
if (total.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
this.stack.getOrCreateTag().putLong("total", total.longValue());
} else {
this.stack.getOrCreateTag().putString("total", total.toString());
}
} catch (Exception ignored) {
} }
// 将当前已存储的不同物品种类数缓存到 NBT键名: "types"用于客户端 tooltip 显示
int typesCount = this.getCellStoredMap().size();
stack.getOrCreateTag().putInt("types", typesCount);
} }
isPersisted = true; this.isPersisted = true;
} }
// 插入物品到存储单元 // 插入物品到存储单元
@Override @Override
public long insert(AEKey what, long amount, Actionable mode, IActionSource source) { public long insert(AEKey what, long amount, Actionable mode, IActionSource source) {
// 数量为0或类型不匹配直接返回 // 数量为0或类型不匹配直接返回
if (amount == 0) if (amount == 0) {
return 0; return 0;
}
// 不允许存储无限单元自身 // 不允许存储无限单元自身
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并初始化存储延迟创建全局存储以避免在 manager 未就绪时 NPE
if (!this.hasUUID()) { if (!this.hasUUID()) {
stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); this.stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID());
getStorageInstance().getOrCreateCell(getUUID()); InfinityStorageManager.INSTANCE.getOrCreateCell(getUUID());
// 确保 storedMap 初始化并从持久层加载数据 // 确保 storedMap 初始化并从持久层加载数据
this.getCellStoredMap(); loadCellStoredMap();
} }
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);
@ -361,8 +367,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 实际插入更新数量并保存 // 实际插入更新数量并保存
BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount)); BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount));
map.put(what, newAmount); map.put(what, newAmount);
// 更新 cached total
totalStored = totalStored.add(BigInteger.valueOf(amount));
this.saveChanges(); this.saveChanges();
} }
return amount; return amount;
@ -383,11 +387,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
} else { } else {
ret = currentAmount.longValue(); ret = currentAmount.longValue();
} }
if (mode == Actionable.MODULATE) { if (mode == appeng.api.config.Actionable.MODULATE) {
map.remove(what); map.remove(what);
// 更新 cached total
// 如果 currentAmount 大于 Long.MAX_VALUEtotalStored 减去 currentAmount 会保留大整数
totalStored = totalStored.subtract(currentAmount);
this.saveChanges(); this.saveChanges();
} }
return ret; return ret;
@ -395,8 +396,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 提取部分 // 提取部分
if (mode == Actionable.MODULATE) { if (mode == Actionable.MODULATE) {
map.put(what, currentAmount.subtract(requested)); map.put(what, currentAmount.subtract(requested));
// 更新 cached total
totalStored = totalStored.subtract(requested);
this.saveChanges(); this.saveChanges();
} }
return amount; return amount;
@ -404,10 +403,4 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
} }
return 0; return 0;
} }
// 获取存储单元内所有物品的总数量格式化字符串
public String getTotalStorage() {
// 使用缓存的 totalStored避免每次全表扫描
return formatBigInteger(totalStored);
}
} }

View File

@ -1,7 +1,6 @@
package com.extendedae_plus.ae.items; package com.extendedae_plus.ae.items;
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
import com.google.common.base.Preconditions;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.LongTag; import net.minecraft.nbt.LongTag;
@ -14,7 +13,6 @@ import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.List; import java.util.List;
public class InfinityBigIntegerCellItem extends Item { public class InfinityBigIntegerCellItem extends Item {
@ -39,15 +37,14 @@ public class InfinityBigIntegerCellItem extends Item {
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon1")); tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon1"));
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon2")); tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon2"));
Preconditions.checkArgument(stack.getItem() == this); // 优先使用 ItemStack NBT 缓存信息显示 tooltip客户端不应触发世界 I/O
// 仅在 ItemStack 自身存在 UUID 时显示 UUID避免触发持久化或加载逻辑
CompoundTag tag = stack.getTag(); CompoundTag tag = stack.getTag();
if (tag != null && tag.contains("uuid")) { if (tag != null && tag.contains("uuid")) {
String uuidStr = tag.getUUID("uuid").toString(); String uuidStr = tag.getUUID("uuid").toString();
tooltip.add( tooltip.add(
Component.literal("UUID: ").withStyle(ChatFormatting.GRAY).append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW)) Component.literal("UUID: ").withStyle(ChatFormatting.GRAY).append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW))
); );
// 读取并显示已缓存的种类数量types表示当前存储了多少种不同的 AEKey // types 表示缓存的种类数
if (tag.contains("types")) { if (tag.contains("types")) {
try { try {
int types = tag.getInt("types"); int types = tag.getInt("types");
@ -55,27 +52,25 @@ public class InfinityBigIntegerCellItem extends Item {
Component.literal("Types: ").withStyle(ChatFormatting.GRAY).append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN)) Component.literal("Types: ").withStyle(ChatFormatting.GRAY).append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN))
); );
} catch (Exception ignored) { } catch (Exception ignored) {
// ignore malformed value
} }
} }
// 读取并显示已缓存的 total支持 long string使用格式化函数展示友好单位 // total 支持 long string 两种表现形式
if (tag.contains("total")) { if (tag.contains("total")) {
BigInteger total = BigInteger.ZERO;
Tag t = tag.get("total");
try { try {
java.math.BigInteger total;
Tag t = tag.get("total");
if (t instanceof LongTag) { if (t instanceof LongTag) {
total = BigInteger.valueOf(tag.getLong("total")); total = java.math.BigInteger.valueOf(tag.getLong("total"));
} else { } else {
String s = tag.getString("total"); String s = tag.getString("total");
total = new BigInteger(s); total = new java.math.BigInteger(s);
} }
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
tooltip.add(
Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
);
} catch (Exception ignored) { } catch (Exception ignored) {
// 解析失败保持为 0
} }
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
tooltip.add(
Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
);
} }
} }
} }

View File

@ -44,8 +44,8 @@ public class InfinityDataStorage {
*/ */
public CompoundTag serializeNBT() { public CompoundTag serializeNBT() {
CompoundTag nbt = new CompoundTag(); CompoundTag nbt = new CompoundTag();
nbt.put("keys", keys); nbt.put("keys", this.keys);
nbt.put("amounts", amounts); nbt.put("amounts", this.amounts);
return nbt; return nbt;
} }

View File

@ -1,11 +1,18 @@
package com.extendedae_plus.util.storage; package com.extendedae_plus.util.storage;
import com.extendedae_plus.ExtendedAEPlus;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag; import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.world.level.storage.LevelResource;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -13,88 +20,89 @@ import java.util.UUID;
/** /**
* InfinityStorageManager * InfinityStorageManager
* <p> * <p>
* 世界级别的持久化容器集中管理所有 InfinityBigInteger 存储单元的序列化数据 * 替代之前基于 SavedData 的实现本类使用手动文件 I/O world 目录下保存 NBT 数据
* 功能要点 * 以避免依赖 Minecraft SavedData 机制
* - 在世界加载时从存档恢复所有 cell 的数据 * 数据保持与之前兼容的 NBT 结构 Compound 包含 "list" => ListTag of Compound { uuid, data }
* - 提供按 UUID 获取/创建单个 cell 的数据容器
* - 在世界保存时将内存数据打包为 NBT 写回存档
*/ */
public class InfinityStorageManager extends SavedData { public class InfinityStorageManager {
public static final String FILE_NAME = "eap_infinity_biginteger_cells.dat";
/** /**
* SavedData 文件名常量 * 全局单例 mod world load 时初始化
*/
public static final String FILE_NAME = "eap_infinity_biginteger_cells";
/**
* 全局单例实例在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充
*/
public static InfinityStorageManager INSTANCE = null;
/**
* UUID -> 数据 的内存映射
*/ */
public static volatile InfinityStorageManager INSTANCE = new InfinityStorageManager();
private final Map<UUID, InfinityDataStorage> cells = new HashMap<>(); private final Map<UUID, InfinityDataStorage> cells = new HashMap<>();
public InfinityStorageManager() { private Path saveFilePath = null;
setDirty();
private InfinityStorageManager() {}
/**
* 初始化并从 world 保存目录加载数据若文件不存在则保持空状态
*/
public void initFromWorld(@Nullable ServerLevel serverLevel) {
if (serverLevel == null) return;
try {
File worldFolder = serverLevel.getServer().getWorldPath(LevelResource.ROOT).toFile();
// 保存到 world/<modid>/ 文件夹下避免与其它 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());
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")));
}
}
} catch (IOException e) {
// 读取失败保持空并打印栈追踪以便调试
e.printStackTrace();
}
} }
/** /**
* NBT 构造用于在世界加载时从存档恢复数据 * 保存当前内存数据到文件会覆盖已有文件
*/ */
public InfinityStorageManager(CompoundTag nbt) { public synchronized void saveToFile() {
ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND); if (this.saveFilePath == null)
for (int i = 0; i < cellList.size(); i++) { return;
CompoundTag cell = cellList.getCompound(i); try {
cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); CompoundTag root = new CompoundTag();
ListTag cellList = new ListTag();
for (Map.Entry<UUID, InfinityDataStorage> entry : this.cells.entrySet()) {
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());
} catch (IOException e) {
e.printStackTrace();
} }
setDirty();
} }
/**
* 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE
*/
public static InfinityStorageManager getForLevel(ServerLevel level) {
if (INSTANCE == null && level != null) {
INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME);
}
return INSTANCE;
}
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag nbt) {
// 将内存中的所有 cell 序列化为一个 ListTag
ListTag cellList = new ListTag();
for (Map.Entry<UUID, InfinityDataStorage> entry : cells.entrySet()) {
CompoundTag cell = new CompoundTag();
cell.putUUID("uuid", entry.getKey());
cell.put("data", entry.getValue().serializeNBT());
cellList.add(cell);
}
nbt.put("list", cellList);
return nbt;
}
/**
* 更新或添加某个 UUID 对应的数据并标记为脏需要保存
*/
public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) { public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) {
cells.put(uuid, infinityDataStorage); this.cells.put(uuid, infinityDataStorage);
setDirty(); saveToFile();
} }
/**
* 获取或创建某个 UUID 对应的数据容器
*/
public InfinityDataStorage getOrCreateCell(UUID uuid) { public InfinityDataStorage getOrCreateCell(UUID uuid) {
if (!cells.containsKey(uuid)) { if (!this.cells.containsKey(uuid)) {
updateCell(uuid, new InfinityDataStorage()); InfinityDataStorage newCell = new InfinityDataStorage();
this.cells.put(uuid, newCell);
saveToFile();
} }
return cells.get(uuid); return this.cells.get(uuid);
} }
/**
* 修改某个 UUID 对应的键与数量列表并保存新的签名stackAmounts ListTag 字符串列表
*/
public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) {
InfinityDataStorage cellToModify = getOrCreateCell(cellID); InfinityDataStorage cellToModify = getOrCreateCell(cellID);
if (stackKeys != null && stackAmounts != null) { if (stackKeys != null && stackAmounts != null) {
@ -104,11 +112,8 @@ public class InfinityStorageManager extends SavedData {
updateCell(cellID, cellToModify); updateCell(cellID, cellToModify);
} }
/**
* 删除某个 UUID 的持久化记录并标记为脏
*/
public void removeCell(UUID uuid) { public void removeCell(UUID uuid) {
cells.remove(uuid); this.cells.remove(uuid);
setDirty(); saveToFile();
} }
} }