From 7a8b8ccd6588b17d342e2fcba11efff648417331 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 23 Sep 2025 18:13:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9C=80=E7=BB=88=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=89=88=E6=97=A0=E9=99=90=E5=AD=98=E5=82=A8=E7=A3=81=E7=9B=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- gradle.properties | 2 +- .../com/extendedae_plus/ExtendedAEPlus.java | 24 +- .../InfinityBigIntegerCellHandler.java | 15 - .../InfinityBigIntegerCellInventory.java | 556 +++++++----------- .../ae/items/InfinityBigIntegerCellItem.java | 74 +-- .../command/InfinityDiskGiveCommand.java | 5 +- .../util/storage/InfinityConstants.java | 30 + .../util/storage/InfinityDataStorage.java | 57 +- .../util/storage/InfinityStorageManager.java | 269 +++------ 10 files changed, 419 insertions(+), 615 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/util/storage/InfinityConstants.java diff --git a/README.md b/README.md index ca05466..af20501 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,6 @@ ExtendedAE Plus 是一个面向 Applied Energistics 2 与 ExtendedAE 的功能 - Applied Energistics 2(AE2):MIT License - SpongePowered Mixin:MIT License - Configuration(by Toma):MIT License -- AE2Things(by ProjectET):MIT License +- AE2Things-Forge(by Technici4n):MIT License 请查阅各上游项目以获取完整与最新的许可证信息。第三方组件的许可证与版权归其各自作者所有。 diff --git a/gradle.properties b/gradle.properties index b86c90f..b06db34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G loom.platform = forge # Mod properties -mod_version = 1.4.2-fix2 +mod_version = 1.4.2-fix3 maven_group = com.extendedae_plus archives_name = extendedae_plus diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 3e02a54..00cd4c1 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -3,22 +3,20 @@ package com.extendedae_plus; import appeng.api.storage.StorageCells; import appeng.menu.locator.MenuLocators; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler; -import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; import com.extendedae_plus.client.ClientRegistrar; +import com.extendedae_plus.command.InfinityDiskGiveCommand; import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.init.*; import com.extendedae_plus.menu.locator.CuriosItemLocator; import com.extendedae_plus.util.storage.InfinityStorageManager; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ModelEvent; import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.level.LevelEvent; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.event.RegisterCommandsEvent; -import com.extendedae_plus.command.InfinityDiskGiveCommand; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -54,15 +52,11 @@ public class ExtendedAEPlus { // 注册到Forge事件总线 MinecraftForge.EVENT_BUS.register(this); - MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); // 注册命令注册监听 MinecraftForge.EVENT_BUS.addListener(this::onRegisterCommands); // 注册通用配置 ModConfig.init(); - // 注册 InfinityBigIntegerCellInventory 的事件监听(tick flush 与停止时 flush) - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping); -// ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC); + MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::worldTick); } /** @@ -121,10 +115,12 @@ public class ExtendedAEPlus { } } - // 在世界加载时注册/加载 SavedData - private static void onLevelLoad(LevelEvent.Load event) { - if (event.getLevel() instanceof ServerLevel serverLevel) { - InfinityStorageManager.getForLevel(serverLevel); + + public static InfinityStorageManager STORAGE_INSTANCE = new InfinityStorageManager(); + + public static void worldTick(TickEvent.LevelTickEvent event) { + if (event.phase == TickEvent.Phase.START && event.side.isServer()) { + STORAGE_INSTANCE = InfinityStorageManager.getInstance(event.level.getServer()); } } diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java index bc7c29c..0609b6c 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java @@ -5,30 +5,15 @@ import appeng.api.storage.cells.ISaveProvider; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; import net.minecraft.world.item.ItemStack; -/** - * InfinityBigIntegerCellHandler - * - * 该类实现 AE2 的 ICellHandler,用于: - * - 判定某个 ItemStack 是否为本 mod 的 Infinity 存储单元 - * - 在 AE2 请求访问或创建存储单元时,创建并返回对应的 StorageCell 实例 - */ public class InfinityBigIntegerCellHandler implements ICellHandler { - /** Handler 单例,供注册与调用使用 */ public static final InfinityBigIntegerCellHandler INSTANCE = new InfinityBigIntegerCellHandler(); - /** - * 判断给定的 ItemStack 是否为 InfinityBigIntegerCell - */ @Override public boolean isCell(ItemStack is) { return is.getItem() instanceof InfinityBigIntegerCellItem; } - /** - * 在 AE2 需要访问或创建存储单元时返回对应的 InfinityBigIntegerCellInventory(StorageCell 实现)。 - * 参数 container 为 AE2 提供的保存回调(ISaveProvider),当 cell 需要持久化时会调用它。 - */ @Override public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) { return InfinityBigIntegerCellInventory.createInventory(is, container); 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 efbf16b..efcce92 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 @@ -8,7 +8,10 @@ import appeng.api.stacks.KeyCounter; import appeng.api.storage.cells.CellState; import appeng.api.storage.cells.ISaveProvider; import appeng.api.storage.cells.StorageCell; +import appeng.core.AELog; +import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; +import com.extendedae_plus.util.storage.InfinityConstants; import com.extendedae_plus.util.storage.InfinityDataStorage; import com.extendedae_plus.util.storage.InfinityStorageManager; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -16,77 +19,45 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; 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.minecraftforge.event.TickEvent; -import net.minecraftforge.event.server.ServerStoppingEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; +import java.util.Objects; import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedQueue; -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; /** - * InfinityBigIntegerCellInventory - *

- * 本类实现 AE2 的 StorageCell,表示单个 Infinity 存储单元的运行时数据与行为。 - * 主要职责: - * - 在内存中维护条目映射 (AEKey -> BigInteger 数量) - * - 提供插入/提取/列举/持久化等操作的实现 - * - 通过 UUID 将 ItemStack 与世界级的 SavedData 关联以实现持久化 - *

- * 重要字段: - * - stack: 关联的 ItemStack,NB T 中保存 UUID 与缓存信息 - * - container: AE2 提供的保存回调 (ISaveProvider),用于合并与触发持久化 - * - storedMap: 延迟初始化的内存映射,减少未使用时内存占用 - * - totalStored: 缓存的总数量 (BigInteger),避免频繁全表扫描 - * - isPersisted: 标记内存状态是否已同步到持久层 + * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

+ * Original copyright (c) Technici4n

*/ public class InfinityBigIntegerCellInventory implements StorageCell { - - // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化) - private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>(); - // 数字格式化对象:改为方法局部以避免静态非线程安全问题 - - // 关联的 ItemStack(含可能的 uuid NBT) - private final ItemStack stack; + private final InfinityBigIntegerCellItem cell; + // 磁盘本身 + private final ItemStack self; // AE2 提供的保存提供者,用于在容器中批量保存时触发回调 private final ISaveProvider container; - // 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化) - private Object2ObjectMap storedMap = null; + // 存储物品键和数量的映射 + private Object2ObjectMap AEKey2AmountsMap; + // 存储的物品种类数量 + private int totalAEKeyType; + // 存储的物品总数 + private BigInteger totalAEKey2Amounts = BigInteger.ZERO; // 标记是否已持久化到 SavedData private boolean isPersisted = true; - // 缓存的总存储量,避免每次调用进行全表扫描 - private BigInteger totalStored = BigInteger.ZERO; - /** - * 私有构造器:通过 createInventory 工厂方法调用 - * - * @param stack 关联的物品堆 - * @param saveProvider AE2 的保存回调(可为 null) - */ - private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) { - this.stack = stack; - container = saveProvider; - // 不在构造时创建 storedMap,推迟到实际访问或首次写入时初始化 - this.storedMap = null; - } - // 创建存储单元库存实例的静态方法 - 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; + public InfinityBigIntegerCellInventory(InfinityBigIntegerCellItem cell, ItemStack stack, ISaveProvider saveProvider) { + // 保存存储单元类型(InfinityBigIntegerCellItem 实例),用于访问磁盘属性 + this.cell = cell; + // 保存物品堆栈,表示磁盘本身,包含运行时的 NBT 数据 + this.self = stack; + // 保存提供者,用于触发数据保存 + this.container = saveProvider; + // 初始化 storedAmounts 为 null,延迟加载物品数据 + this.AEKey2AmountsMap = null; + // 初始化磁盘数据 + initData(); } // 将 BigInteger 格式化为带单位的字符串,保留两位小数 @@ -107,29 +78,43 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return df.format(bd.doubleValue()) + units[idx]; } - // 获取当前存储单元的数据存储对象 + // 获取磁盘的 InfinityDataStorage 数据 private InfinityDataStorage getCellStorage() { - if (this.getUUID() == null) { - // 如果没有UUID,返回空存储(返回新实例以避免共享可变状态) - return InfinityDataStorage.empty(); + // 如果磁盘有 UUID,返回对应的 InfinityDataStorage + if (getUUID() != null) { + return getStorageManagerInstance().getOrCreateCell(getUUID()); } else { - // 在访问全局 SavedData 之前做防御性检查,避免在客户端或尚未初始化的情况下 NPE - InfinityStorageManager mgr = getStorageInstance(); - if (mgr == null) { - // SavedData 尚未就绪,视为空存储(返回新实例以避免共享可变状态) - return InfinityDataStorage.empty(); - } - // 否则获取或创建对应UUID的存储 - return mgr.getOrCreateCell(getUUID()); + // 否则返回空的 InfinityDataStorage + return InfinityDataStorage.EMPTY; } } - // 获取存储单元状态(空/非空) + // 初始化磁盘数据 + private void initData() { + // 如果磁盘有 UUID,加载存储的物品数据 + if (hasUUID()) { + this.totalAEKeyType = getCellStorage().amounts.size(); + this.totalAEKey2Amounts = getCellStorage().itemCount.equals(BigInteger.ZERO) ? + BigInteger.ZERO : + getCellStorage().itemCount; + + } else { + // 否则初始化为空 + this.totalAEKeyType = 0; + this.totalAEKey2Amounts = BigInteger.ZERO; + // 加载物品数据 + getCellStoredMap(); + } + } + + // 获取存储单元的状态(空、部分填充) @Override public CellState getStatus() { - if (this.getCellStoredMap().isEmpty()) { + // 如果没有存储任何物品,返回空状态 + if (this.getTotalAEKey2Amounts().equals(BigInteger.ZERO)) { return CellState.EMPTY; } + // 否则返回满状态 return CellState.NOT_EMPTY; } @@ -139,225 +124,226 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return 512; } + // 持久化存储单元数据到全局存储 + @Override + public void persist() { + if (this.isPersisted) + return; + + if (totalAEKey2Amounts.equals(BigInteger.ZERO)) { + if (hasUUID()) { + getStorageManagerInstance().removeCell(getUUID()); + if (self.hasTag()) { + var tag = self.getTag(); + // remove persisted identifiers and cached summary fields from the ItemStack + tag.remove(InfinityConstants.INFINITY_CELL_UUID); + tag.remove(InfinityConstants.INFINITY_ITEM_TOTAL); + tag.remove(InfinityConstants.INFINITY_ITEM_TYPES); + // backward compat: also remove internal cell item count key if present + tag.remove(InfinityConstants.INFINITY_CELL_ITEM_COUNT); + } + initData(); + } + return; + } + + // 创建物品键列表 + ListTag keys = new ListTag(); + // 创建物品数量列表 + ListTag amounts = new ListTag(); + // 初始化物品总数 + BigInteger itemCount = BigInteger.ZERO; + + for (var entry : this.AEKey2AmountsMap.object2ObjectEntrySet()) { + BigInteger amount = entry.getValue(); + // 如果数量大于 0,添加到键和数量列表 + if (amount.compareTo(BigInteger.ZERO) > 0) { + keys.add(entry.getKey().toTagGeneric()); + CompoundTag amountTag = new CompoundTag(); + amountTag.putByteArray("value", amount.toByteArray()); + amounts.add(amountTag); + + itemCount = itemCount.add(amount); + } + } + + if (keys.isEmpty()) { + getStorageManagerInstance().updateCell(getUUID(), new InfinityDataStorage()); + } else { + getStorageManagerInstance().modifyDisk(getUUID(), keys, amounts, itemCount); + } + + // 更新存储的物品种类数量 + this.totalAEKeyType = this.AEKey2AmountsMap.size(); + // 更新存储的物品总数 + this.totalAEKey2Amounts = itemCount; + // 将物品总数与种类数量存入物品堆栈的 NBT(用于快捷查看/tooltip),同时保留旧字段以兼容历史版本 + var tag = self.getOrCreateTag(); + tag.putByteArray(InfinityConstants.INFINITY_ITEM_TOTAL, itemCount.toByteArray()); + tag.putInt(InfinityConstants.INFINITY_ITEM_TYPES, this.totalAEKeyType); + // backward compat storage field (kept for legacy readers) + tag.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray()); + + // 标记数据已持久化 + this.isPersisted = true; + } + // 获取存储单元的描述(此处返回null,可自定义) @Override public Component getDescription() { return null; } + // 静态方法,创建存储单元库存 + public static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) { + // 检查物品堆栈是否为空 + Objects.requireNonNull(stack, "Cannot create cell inventory for null itemstack"); + // 检查物品是否为 IDISKCellItem 类型 + if (!(stack.getItem() instanceof InfinityBigIntegerCellItem cell)) { + return null; + } + // 创建并返回新的 DISKCellInventory 实例 + return new InfinityBigIntegerCellInventory(cell, stack, saveProvider); + } + + // 获取存储的物品总数 + public BigInteger getTotalAEKey2Amounts() { + return this.totalAEKey2Amounts; + } + + // 获取存储的物品种类数量 + public int getTotalAEKeyType() { + return this.totalAEKeyType; + } + // 判断物品堆栈是否有UUID public boolean hasUUID() { - return stack.hasTag() && stack.getOrCreateTag().contains("uuid"); + return self.hasTag() && self.getOrCreateTag().contains(InfinityConstants.INFINITY_CELL_UUID); } // 获取物品堆栈的UUID public UUID getUUID() { - if (this.hasUUID()) - return stack.getOrCreateTag().getUUID("uuid"); - else + if (this.hasUUID()) { + return self.getOrCreateTag().getUUID(InfinityConstants.INFINITY_CELL_UUID); + } else { return null; + } } // 获取或初始化存储映射 private Object2ObjectMap getCellStoredMap() { - if (storedMap == null) { - storedMap = new Object2ObjectOpenHashMap<>(); + if (AEKey2AmountsMap == null) { + AEKey2AmountsMap = new Object2ObjectOpenHashMap<>(); this.loadCellStoredMap(); } - return storedMap; - } - - // 从存储中加载物品映射 - private void loadCellStoredMap() { - boolean corruptedTag = false; // 标记数据是否损坏 - if (!stack.hasTag()) return; - InfinityDataStorage storage = this.getCellStorage(); - ListTag keys = storage.keys; - ListTag amounts = storage.amounts; - int len = Math.min(keys.size(), amounts.size()); - for (int i = 0; i < len; i++) { - AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); - CompoundTag amtTag = amounts.getCompound(i); - try { - BigInteger amount; - if (amtTag.contains("l")) { - long v = amtTag.getLong("l"); - amount = BigInteger.valueOf(v); - } else if (amtTag.contains("s")) { - amount = new BigInteger(amtTag.getString("s")); - } else { - corruptedTag = true; - continue; - } - if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) { - corruptedTag = true; - } else { - // storedMap 已在 getCellStoredMap() 中初始化,直接使用字段以避免额外方法开销 - storedMap.put(key, amount); - // 更新缓存的总数 - totalStored = totalStored.add(amount); - } - } catch (NumberFormatException ex) { - corruptedTag = true; - } - } - // 如果有损坏,保存修正后的数据 - if (corruptedTag) { - this.saveChanges(); - } - } - - // 标记数据需要保存,并通知容器或直接持久化 - private void saveChanges() { - // 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O - isPersisted = false; - if (container != null) { - // 当存在容器时,优先让容器统一处理持久化 - container.saveChanges(); - } else { - // 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O - if (!PENDING_PERSIST.contains(this)) { - PENDING_PERSIST.offer(this); - } - } + return AEKey2AmountsMap; } // 获取所有可用的物品堆栈及其数量 @Override public void getAvailableStacks(KeyCounter out) { BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); - Object2ObjectMap map = getCellStoredMap(); - for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) { + if(this.getCellStoredMap() == null) return; + for (var entry : this.getCellStoredMap().object2ObjectEntrySet()) { AEKey key = entry.getKey(); BigInteger value = entry.getValue(); - // 当前 KeyCounter 中已有的值(long) + // 获取 KeyCounter 中已有的值 long existing = out.get(key); - // 将 existing 与当前 value 做 BigInteger 累加并饱和到 Long.MAX_VALUE + // 计算总和并限制到 Long.MAX_VALUE BigInteger sum = BigInteger.valueOf(existing).add(value); long toSet = sum.compareTo(maxLong) > 0 ? Long.MAX_VALUE : sum.longValue(); - - // KeyCounter 没有 set(key,long) 的统一接口暴露(只有 add/remove),所以先移除已存在的值再设置。 - // 为避免读取-写入竞争,我们计算出要新增的 delta 并调用 add(key, delta) + // 更新 KeyCounter if (existing == Long.MAX_VALUE) { - // 已经饱和,无需再添加 continue; } - long delta; - if (toSet == Long.MAX_VALUE) { - delta = Long.MAX_VALUE - existing; - } else { - delta = toSet - existing; - } + long delta = toSet - existing; if (delta != 0) { out.add(key, delta); } } } - // 持久化存储单元数据到全局存储 - @Override - public void persist() { - if (this.isPersisted) - 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 map = this.getCellStoredMap(); - if (map.isEmpty()) { - // 如果存储为空,移除UUID和全局存储中的数据 - if (this.hasUUID()) { - mgr.removeCell(getUUID()); - if (stack.getTag() != null) { - stack.getTag().remove("uuid"); - // 移除缓存的 total 字段 - stack.getTag().remove("total"); - } - // 标记为已持久化,避免重复尝试 - isPersisted = true; - } - return; + // 从存储中加载物品映射 + private void loadCellStoredMap() { + boolean dataCorruption = false; + if (!self.hasTag()) return; + + var keys = getCellStorage().keys; + var amounts = getCellStorage().amounts; + // 数据损坏 + if (keys.size() != amounts.size()) { + AELog.warn("Loading storage cell with mismatched amounts/tags: %d != %d", amounts.size(), keys.size()); } - // 构建要保存的Key和数量列表(混合表示:long 或 string) - ListTag amountTags = new ListTag(); - ListTag keys = new ListTag(); - for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) { - BigInteger amount = entry.getValue(); - if (amount.compareTo(BigInteger.ZERO) > 0) { - keys.add(entry.getKey().toTagGeneric()); - CompoundTag amt = new CompoundTag(); - if (amount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { - amt.putLong("l", amount.longValue()); - } else { - amt.putString("s", amount.toString()); - } - amountTags.add(amt); - } - } - // 如果没有Key,更新为空存储,否则保存数据 - 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) { - if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { - stack.getOrCreateTag().putLong("total", totalStored.longValue()); + // 遍历数量和键,加载到 AEKey2AmountsMap + for (int i = 0; i < amounts.size(); i++) { + AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); + BigInteger amount = new BigInteger(amounts.getCompound(i).getByteArray("value")); + // 检查数据是否损坏 + if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) { + dataCorruption = true; } else { - stack.getOrCreateTag().putString("total", totalStored.toString()); + AEKey2AmountsMap.put(key, amount); } - // 将当前已存储的不同物品种类数缓存到 NBT(键名: "types"),用于客户端 tooltip 显示 - int typesCount = this.getCellStoredMap().size(); - stack.getOrCreateTag().putInt("types", typesCount); } - isPersisted = true; + if (dataCorruption) { + this.saveChanges(); + } + } + + // 获取全局存储实例 + private static InfinityStorageManager getStorageManagerInstance() { + return ExtendedAEPlus.STORAGE_INSTANCE; + } + + // 标记数据需要保存,并通知容器或直接持久化 + private void saveChanges() { + // 更新存储的物品种类数量 + this.totalAEKeyType = this.AEKey2AmountsMap.size(); + // 重置物品总数 + this.totalAEKey2Amounts = BigInteger.ZERO; + // 计算物品总数 + for (BigInteger AEKey2Amounts : this.AEKey2AmountsMap.values()) { + this.totalAEKey2Amounts = this.totalAEKey2Amounts.add(AEKey2Amounts); + } + // 标记数据未持久化 + this.isPersisted = false; + // 如果有保存提供者,通知保存 + if (this.container != null) { + this.container.saveChanges(); + } else { + // 否则立即持久化 + this.persist(); + } } // 插入物品到存储单元 @Override public long insert(AEKey what, long amount, Actionable mode, IActionSource source) { // 数量为0或类型不匹配直接返回 - if (amount == 0) + if (amount == 0){ return 0; + } // 不允许存储无限单元自身 - if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) + if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) { return 0; + } + // 如果没有UUID,尝试在服务器端且存储管理器已就绪时生成UUID并初始化存储 if (!this.hasUUID()) { - InfinityStorageManager mgr = getStorageInstance(); - // 仅在 SavedData 已就绪(通常在服务器端)时创建 UUID 并注册持久化存储 - if (mgr != null) { - stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); - mgr.getOrCreateCell(getUUID()); - // 确保 storedMap 初始化并从持久层加载数据 - this.getCellStoredMap(); - } else { - // SavedData 未就绪:把插入作为本地内存操作(不生成 UUID,不持久化),确保 storedMap 初始化以容纳临时数据 - this.getCellStoredMap(); - } + self.getOrCreateTag().putUUID(InfinityConstants.INFINITY_CELL_UUID, UUID.randomUUID()); + getStorageManagerInstance().getOrCreateCell(getUUID()); + loadCellStoredMap(); } - Object2ObjectMap map = this.getCellStoredMap(); - BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); + // 获取当前物品数量 + BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO); + if (mode == Actionable.MODULATE) { // 实际插入,更新数量并保存 BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount)); - map.put(what, newAmount); - // 更新 cached total - totalStored = totalStored.add(BigInteger.valueOf(amount)); + getCellStoredMap().put(what, newAmount); this.saveChanges(); } return amount; @@ -366,35 +352,26 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 从存储单元提取物品 @Override public long extract(AEKey what, long amount, Actionable mode, IActionSource source) { - Object2ObjectMap map = this.getCellStoredMap(); - BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); + BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO); + // 如果有物品可提取 if (currentAmount.compareTo(BigInteger.ZERO) > 0) { + BigInteger requested = BigInteger.valueOf(amount); - if (currentAmount.compareTo(requested) <= 0) { - // 提取全部 - long ret; - if (currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { - ret = Long.MAX_VALUE; - } else { - ret = currentAmount.longValue(); - } + + // 如果提取数量大于等于当前数量 + if (requested.compareTo(currentAmount) >= 0) { if (mode == Actionable.MODULATE) { - map.remove(what); - // 更新 cached total - // 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数 - totalStored = totalStored.subtract(currentAmount); + getCellStoredMap().remove(what); this.saveChanges(); } - return ret; + return currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0 ? Long.MAX_VALUE : currentAmount.longValue(); } else { - // 提取部分 + // 提取部分数量 if (mode == Actionable.MODULATE) { - map.put(what, currentAmount.subtract(requested)); - // 更新 cached total - totalStored = totalStored.subtract(requested); + getCellStoredMap().put(what, currentAmount.subtract(requested)); this.saveChanges(); } - return amount; + return requested.longValue(); } } return 0; @@ -403,89 +380,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 获取存储单元内所有物品的总数量(格式化字符串) public String getTotalStorage() { // 使用缓存的 totalStored,避免每次全表扫描 - return formatBigInteger(totalStored); - } - - /** 定时 tick 保存计数器 */ - private static int SAVE_TICK_COUNTER = 0; - - /** tick 间隔,单位为服务器 tick(20 tick ≈ 1 秒) */ - private static final int SAVE_TICK_INTERVAL = 20; // 约30秒 - - /** 服务器 tick 事件:处理队列并定时保存 */ - @SubscribeEvent - public static void onServerTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.END) return; - - // 处理待持久化队列 - InfinityBigIntegerCellInventory inv; - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - if (!inv.isPersisted) { - inv.persist(); - } - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ex.getMessage()); - } - } - - // 定时标记全局存储为脏 - try { - if (++SAVE_TICK_COUNTER >= SAVE_TICK_INTERVAL) { - SAVE_TICK_COUNTER = 0; - if (InfinityStorageManager.INSTANCE != null) { - InfinityStorageManager.INSTANCE.setDirty(); - } - } - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory tick dirty set error: {}", ex.getMessage()); - } - } - - // 在服务器停止时被调用,立即强制持久化队列中的所有实例 - @SubscribeEvent - 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) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping init error: {}", ignored.getMessage()); - } - - InfinityBigIntegerCellInventory inv; - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - if (!inv.isPersisted) { - inv.persist(); - } - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping persist error: {}", ex.getMessage()); } - } - - // 强制写回磁盘,确保直接关闭游戏也能保存 - try { - InfinityStorageManager mgr = InfinityStorageManager.INSTANCE; - if (mgr != null) { - MinecraftServer server = event.getServer(); - if (server != null) { - for (ServerLevel level : server.getAllLevels()) { - try { - mgr.forceSaveAll(level); - LOGGER.info("Stop forceSaveAll for level {}", level.dimension().location()); - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory forceSaveAll error for level {}: {}", - level.dimension().location(), ex.getMessage()); - } - } - } - } - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping forceSaveAll error: {}", ex.getMessage()); - } + return formatBigInteger(totalAEKey2Amounts); } } diff --git a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java index 75baebf..2b4af58 100644 --- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -1,11 +1,12 @@ package com.extendedae_plus.ae.items; +import appeng.api.config.FuzzyMode; +import appeng.api.storage.cells.ICellWorkbenchItem; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; +import com.extendedae_plus.util.storage.InfinityConstants; import com.google.common.base.Preconditions; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.LongTag; -import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; @@ -16,24 +17,15 @@ import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; -import java.math.BigInteger; import java.util.List; import java.util.Objects; -public class InfinityBigIntegerCellItem extends Item { +public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchItem { public InfinityBigIntegerCellItem() { super(new Properties().stacksTo(1).fireResistant()); } - /** - * 在物品悬停提示中展示额外信息。 - * 功能: - * - 若 ItemStack 的 NBT 含有 UUID,则显示该 UUID(不会触发服务器加载或持久化行为) - * - 若 NBT 同步了 total 字段,则读取并格式化显示总存储量(使用 Inventory 的 formatBigInteger) - * - * 设计说明:客户端 tooltip 不主动访问服务端 SavedData,以避免不必要的 I/O 与状态变更。 - */ @Override public void appendHoverText(ItemStack stack, @Nullable Level world, @@ -45,15 +37,16 @@ public class InfinityBigIntegerCellItem extends Item { Preconditions.checkArgument(stack.getItem() == this); // 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑 CompoundTag tag = stack.getTag(); - if (tag != null && tag.contains("uuid")) { - String uuidStr = tag.getUUID("uuid").toString(); + if (tag != null && tag.contains(InfinityConstants.INFINITY_CELL_UUID)) { + String uuidStr = tag.getUUID(InfinityConstants.INFINITY_CELL_UUID).toString(); tooltip.add( Component.literal("UUID: ").withStyle(ChatFormatting.GRAY).append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW)) ); - // 读取并显示已缓存的种类数量(types),表示当前存储了多少种不同的 AEKey - if (tag.contains("types")) { + + // 显示已缓存的种类数量(types)——优先使用 ItemStack 缓存字段 + if (tag.contains(InfinityConstants.INFINITY_ITEM_TYPES)) { try { - int types = tag.getInt("types"); + int types = tag.getInt(InfinityConstants.INFINITY_ITEM_TYPES); tooltip.add( Component.literal("Types: ").withStyle(ChatFormatting.GRAY).append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN)) ); @@ -61,24 +54,30 @@ public class InfinityBigIntegerCellItem extends Item { // ignore malformed value } } - // 读取并显示已缓存的 total(支持 long 或 string),使用格式化函数展示友好单位 - if (tag.contains("total")) { - BigInteger total = BigInteger.ZERO; - Tag t = tag.get("total"); + + // 显示物品总数(formatted)。优先使用缓存的 INFINITY_ITEM_TOTAL 字段(byte[]),否则回退为 legacy 字段或不显示 + if (tag.contains(InfinityConstants.INFINITY_ITEM_TOTAL)) { try { - if (t instanceof LongTag) { - total = BigInteger.valueOf(tag.getLong("total")); - } else { - String s = tag.getString("total"); - total = new BigInteger(s); - } + byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_ITEM_TOTAL); + java.math.BigInteger total = new java.math.BigInteger(bytes); + String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); + tooltip.add( + Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) + ); } catch (Exception ignored) { - // 解析失败保持为 0 + // ignore malformed value + } + } else if (tag.contains(InfinityConstants.INFINITY_CELL_ITEM_COUNT)) { + try { + byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT); + java.math.BigInteger total = new java.math.BigInteger(bytes); + String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); + tooltip.add( + Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) + ); + } catch (Exception ignored) { + // ignore malformed value } - String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); - tooltip.add( - Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) - ); } } } @@ -90,7 +89,16 @@ public class InfinityBigIntegerCellItem extends Item { ItemStack stack = new ItemStack(Objects.requireNonNull( ForgeRegistries.ITEMS.getValue(new ResourceLocation("extendedae_plus", "infinity_biginteger_cell") ))); - stack.getOrCreateTag().putUUID("uuid", uuid); + stack.getOrCreateTag().putUUID(InfinityConstants.INFINITY_CELL_UUID, uuid); return stack; } + + @Override + public FuzzyMode getFuzzyMode(ItemStack itemStack) { + return null; + } + + @Override + public void setFuzzyMode(ItemStack itemStack, FuzzyMode fuzzyMode) { + } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java b/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java index 306e483..67305f3 100644 --- a/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java +++ b/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java @@ -1,5 +1,6 @@ package com.extendedae_plus.command; +import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; import com.extendedae_plus.util.storage.InfinityStorageManager; import com.mojang.brigadier.CommandDispatcher; @@ -33,7 +34,7 @@ public class InfinityDiskGiveCommand { source.sendFailure(Component.literal("This command must be run on server side.")); return 0; } - InfinityStorageManager mgr = InfinityStorageManager.getForLevel((ServerLevel) player.level()); + InfinityStorageManager mgr = ExtendedAEPlus.STORAGE_INSTANCE; if (mgr == null) { source.sendFailure(Component.literal("InfinityStorageManager is not initialized.")); return 0; @@ -54,7 +55,7 @@ public class InfinityDiskGiveCommand { } catch (Exception ex) { source.sendFailure(Component.literal("Error: " + ex.getMessage())); return 0; - } + } } } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityConstants.java b/src/main/java/com/extendedae_plus/util/storage/InfinityConstants.java new file mode 100644 index 0000000..1fcb598 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityConstants.java @@ -0,0 +1,30 @@ +package com.extendedae_plus.util.storage; + +public interface InfinityConstants { + // 当前磁盘格式版本号,增加字段用于向后/向前兼容 + int FORMAT_VERSION = 2; + // 存储磁盘数据的格式版本号 + String FORMAT_VERSION_FIELD = "infinity_format_version"; + + // savedData 文件名常量 + String SAVE_FILE_NAME = "infinity_biginteger_cells"; + + // 磁盘的唯一标识符键名,存储在 ItemStack 和 InfinityStorageManager 中 + String INFINITY_CELL_UUID = "infinity_cell_uuid"; + // 单个磁盘的 InfinityDataStorage 数据键名 + String INFINITY_CELL_DATA = "infinity_cell_data"; + // 所有磁盘数据的列表键名,存储在 InfinityStorageManager 的 NBT 中 + String INFINITY_CELL_LIST = "infinity_cell_list"; + + // 磁盘中所有物品键的键名(ListTag of CompoundTag) + String INFINITY_CELL_KEYS = "infinity_cell_keys"; + // 磁盘中每种物品数量的键名(ListTag of CompoundTag,包含 "value") + String INFINITY_CELL_AMOUNTS = "infinity_cell_amounts"; + // 磁盘中所有物品的总数键名(ListTag,包含一个 CompoundTag 的 "value") + String INFINITY_CELL_ITEM_COUNT = "infinity_cell_item_count"; + + // ItemStack 的 NBT 中存储总物品数量的键名 + String INFINITY_ITEM_TOTAL = "infinity_item_total"; + // ItemStack 的 NBT 中存储物品种类数量的键名 + String INFINITY_ITEM_TYPES = "infinity_item_types"; +} 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 c56e421..6462128 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -2,62 +2,47 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; + +import java.math.BigInteger; /** - * InfinityDataStorage - * - * 表示单个 UUID 对应的持久化数据容器,直接映射到世界存档中的一项记录。 - * 数据结构说明: - * - keys: 存放序列化后的 AEKey(每项为 CompoundTag),用于标识不同的存储条目 - * - amounts: 与 keys 一一对应的数量列表(每项为 CompoundTag),采用混合表示: - * - 当数量能放入 long 时,CompoundTag 包含键 "l" 存放 long 值 - * - 当数量超出 long 时,CompoundTag 包含键 "s" 存放 BigInteger 的字符串形式 - * - * 该类提供将内存数据与 NBT 之间互转的辅助方法,供 `SavedData` 在世界保存/加载时调用。 + * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

+ * Original copyright (c) Technici4n

*/ public class InfinityDataStorage { + // 定义一个静态常量 EMPTY,表示一个空的 DataStorage 实例,用于默认或占位场景 + public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); - /** 空实例的访问器(返回新实例以避免共享可变状态) */ - public static InfinityDataStorage empty() { - return new InfinityDataStorage(); - } - - /** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */ public ListTag keys; - /** - * 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag): - * - 若数量能放入 long,则 CompoundTag 包含键 "l"(long) - * - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式 - */ public ListTag amounts; + // 存储磁盘中物品的总数,使用 BigInteger 支持大容量 + public BigInteger itemCount; public InfinityDataStorage() { - this(new ListTag(), new ListTag()); + this(new ListTag(), new ListTag(), BigInteger.ZERO); } - private InfinityDataStorage(ListTag keys, ListTag amounts) { + private InfinityDataStorage(ListTag keys, ListTag amounts, BigInteger itemCount) { this.keys = keys; this.amounts = amounts; + this.itemCount = itemCount; } - /** - * 将当前数据封装为 CompoundTag 以写入存档 - */ + // 将 DataStorage 数据序列化为 NBT 格式 public CompoundTag serializeNBT() { CompoundTag nbt = new CompoundTag(); - nbt.put("keys", keys); - nbt.put("amounts", amounts); + nbt.put(InfinityConstants.INFINITY_CELL_KEYS, keys); + nbt.put(InfinityConstants.INFINITY_CELL_AMOUNTS, amounts); + nbt.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray()); return nbt; } - /** - * 从存档读取数据并构造实例 - */ + // 从 NBT 数据反序列化创建 DataStorage 实例 public static InfinityDataStorage loadFromNBT(CompoundTag nbt) { - ListTag stackKeys = nbt.getList("keys", Tag.TAG_COMPOUND); - // amounts 以 CompoundTag 列表存储,每个 CompoundTag 内含 long 或 String - ListTag stackAmounts = nbt.getList("amounts", Tag.TAG_COMPOUND); - return new InfinityDataStorage(stackKeys, stackAmounts); + ListTag keys = nbt.getList(InfinityConstants.INFINITY_CELL_KEYS, ListTag.TAG_COMPOUND); + ListTag amounts = nbt.getList(InfinityConstants.INFINITY_CELL_AMOUNTS, ListTag.TAG_COMPOUND); + BigInteger itemCount = new BigInteger(nbt.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT)); + // 使用加载的数据创建新的 DataStorage 实例 + return new InfinityDataStorage(keys, amounts, itemCount); } } 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 6ba1080..f27f2a6 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -2,145 +2,37 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtIo; +import net.minecraft.server.MinecraftServer; 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 java.io.File; -import java.io.IOException; -import java.nio.file.AtomicMoveNotSupportedException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; +import java.math.BigInteger; +import java.util.*; /** - * InfinityStorageManager - *

- * 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。 - * 功能要点: - * - 在世界加载时从存档恢复所有 cell 的数据 - * - 提供按 UUID 获取/创建单个 cell 的数据容器 - * - 在世界保存时将内存数据打包为 NBT 写回存档 + * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

+ * Original copyright (c) Technici4n

*/ public class InfinityStorageManager extends SavedData { - // 当前磁盘格式版本号,增加字段用于向后/向前兼容 - private static final int FORMAT_VERSION = 1; + // 存储所有磁盘的Map,键为UUID,值为DataStorage对象 + private final Map cells; - /** - * SavedData 文件名常量 - */ - public static final String FILE_NAME = "eap_infinity_biginteger_cells"; - /** - * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充) - */ - public static InfinityStorageManager INSTANCE = null; - /** - * UUID -> 数据 的内存映射 - */ - private final Map cells = new HashMap<>(); - - /** - * 返回当前已加载的所有 UUID 的不可变视图,用于命令或调试用途 - */ - public java.util.Set getAllLoadedUUIDs() { - return java.util.Collections.unmodifiableSet(cells.keySet()); - } - + // 构造方法,初始化磁盘Map public InfinityStorageManager() { - setDirty(); + cells = new HashMap<>(); + // 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘 + this.setDirty(); } - /** - * 从 NBT 构造:用于在世界加载时从存档恢复数据 - */ - public InfinityStorageManager(CompoundTag nbt) { - // 读取格式版本,缺省视为 1(兼容旧档) - int version = nbt.contains("format_version") ? nbt.getInt("format_version") : 1; - if (!nbt.contains("format_version")) { - // 旧档未包含 format_version,标记为需要写回以便下次保存写入版本号 - LOGGER.info("Found legacy InfinityStorageManager dat without format_version; treating as version {} and marking dirty for upgrade", version); - // 立即标记为脏以触发尽快写盘(升级写入 format_version) - this.setDirty(); - } else { - LOGGER.info("Loading InfinityStorageManager format_version={}", version); - } - - ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND); - for (int i = 0; i < cellList.size(); i++) { - CompoundTag cell = cellList.getCompound(i); - 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(); - } - - /** - * 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE - */ - public static InfinityStorageManager getForLevel(ServerLevel level) { - if (INSTANCE == null && level != null) { - // 迁移逻辑:若旧的压缩 dat 文件存在且缺少 format_version,则读取并补上 version,备份原文件 - try { - Path dataDir = level.getServer().getWorldPath(new LevelResource("data")); - Path filePath = dataDir.resolve(FILE_NAME + ".dat"); - File oldFile = filePath.toFile(); - if (oldFile.exists()) { - // 检测是否为 gzip(压缩 NBT) - boolean isGzip = false; - try (var fis = Files.newInputStream(filePath)) { - int b1 = fis.read(); - int b2 = fis.read(); - isGzip = (b1 == 0x1F && b2 == 0x8B); - } catch (IOException ignored) { - } - if (isGzip) { - try { - var root = NbtIo.readCompressed(oldFile); - if (!root.contains("format_version")) { - // 备份旧文件 - Path bak = filePath.resolveSibling(FILE_NAME + ".dat.bak"); - try { - Files.copy(filePath, bak); - } catch (IOException ignored) { - } - // 写回并加入 format_version - root.putInt("format_version", FORMAT_VERSION); - Path tmp = filePath.resolveSibling(FILE_NAME + ".tmp"); - File tmpFile = tmp.toFile(); - if (tmpFile.getParentFile() != null && !tmpFile.getParentFile().exists()) { - tmpFile.getParentFile().mkdirs(); - } - NbtIo.writeCompressed(root, tmpFile); - try { - Files.move(tmp, filePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - } catch (AtomicMoveNotSupportedException ex) { - Files.move(tmp, filePath, StandardCopyOption.REPLACE_EXISTING); - } - LOGGER.info("Migrated old compressed {} to include format_version", filePath); - } - } catch (IOException ex) { - LOGGER.info("Failed to migrate old compressed file {}: {}", filePath, ex.getMessage()); - } - } - } - } catch (Throwable ignored) { - } - - INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); - } - return INSTANCE; + // 私有构造方法,用于从已有Map创建StorageManager + private InfinityStorageManager(Map cells) { + // 确保使用已加载的数据 + this.cells = cells; + // 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘 + this.setDirty(); } @Override @@ -149,83 +41,96 @@ public class InfinityStorageManager extends SavedData { ListTag cellList = new ListTag(); for (Map.Entry entry : cells.entrySet()) { CompoundTag cell = new CompoundTag(); - cell.putUUID("uuid", entry.getKey()); - cell.put("data", entry.getValue().serializeNBT()); + cell.putUUID(InfinityConstants.INFINITY_CELL_UUID, entry.getKey()); + cell.put(InfinityConstants.INFINITY_CELL_DATA, entry.getValue().serializeNBT()); cellList.add(cell); } - nbt.put("list", cellList); + nbt.put(InfinityConstants.INFINITY_CELL_LIST, cellList); // 写入当前格式版本号,便于未来迁移与兼容判断 - nbt.putInt("format_version", FORMAT_VERSION); + nbt.putInt(InfinityConstants.FORMAT_VERSION_FIELD, InfinityConstants.FORMAT_VERSION); return nbt; } - /** - * 更新或添加某个 UUID 对应的数据并标记为脏(需要保存) - */ + // 静态方法,从 NBT 数据反序列化创建 StorageManager 实例 + public static InfinityStorageManager readNbt(CompoundTag nbt) { + // 读取格式版本,缺省视为 1(兼容旧档) + int version = nbt.contains(InfinityConstants.FORMAT_VERSION_FIELD) ? + nbt.getInt(InfinityConstants.FORMAT_VERSION_FIELD) : + 1; + + Map cells = new HashMap<>(); + // 从 NBT 中获取磁盘数据列表,指定类型为 CompoundTag(TAG_COMPOUND) + ListTag cellList = nbt.getList(InfinityConstants.INFINITY_CELL_LIST, CompoundTag.TAG_COMPOUND); + // 遍历 cellList 中的每个 CompoundTag + for (int i = 0; i < cellList.size(); i++) { + // 获取当前索引的 CompoundTag,表示单个磁盘的数据 + CompoundTag cell = cellList.getCompound(i); + // 从 CompoundTag 中读取 UUID 和 DataStorage 数据,并存入 cells 映射 + cells.put(cell.getUUID(InfinityConstants.INFINITY_CELL_UUID), InfinityDataStorage.loadFromNBT(cell.getCompound(InfinityConstants.INFINITY_CELL_DATA))); + } + // 使用加载的 cells 数据创建新的 StorageManager 实例 + return new InfinityStorageManager(cells); + } + + // 返回当前已加载的所有 UUID 的不可变视图,用于命令或调试用途 + public Set getAllLoadedUUIDs() { + return Collections.unmodifiableSet(cells.keySet()); + } + + + // 更新或添加某个 UUID 对应的数据并标记为脏(需要保存) public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) { cells.put(uuid, infinityDataStorage); + // 标记数据为“脏”,确保修改后的数据会在下次保存时写入磁盘 setDirty(); } - /** - * 获取或创建某个 UUID 对应的数据容器 - */ + // 删除某个 UUID 的持久化记录并标记为脏 + public void removeCell(UUID uuid) { + cells.remove(uuid); + // 标记数据为“脏”,确保移除操作会在下次保存时反映到磁盘 + setDirty(); + } + + // 检查指定 UUID 是否存在于 disks 映射中 + public boolean hasUUID(UUID uuid) { + // 返回 cells 映射是否包含指定 UUID + return cells.containsKey(uuid); + } + + // 获取或创建某个 UUID 对应的数据容器 public InfinityDataStorage getOrCreateCell(UUID uuid) { + // 检查 cells 映射中是否不存在指定 UUID if (!cells.containsKey(uuid)) { updateCell(uuid, new InfinityDataStorage()); } + // 返回指定 UUID 对应的 DataStorage 对象 return cells.get(uuid); } - /** - * 修改某个 UUID 对应的键与数量列表并保存(新的签名,stackAmounts 为 ListTag 字符串列表) - */ - public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { - InfinityDataStorage cellToModify = getOrCreateCell(cellID); - if (stackKeys != null && stackAmounts != null) { - cellToModify.keys = stackKeys; - cellToModify.amounts = stackAmounts; + // 修改指定 UUID 的磁盘数据,包括堆栈键、数量和总项目数 + public void modifyDisk(UUID uuid, ListTag keys, ListTag amounts, BigInteger itemCount) { + // 获取或创建指定 UUID 的 DataStorage 对象 + InfinityDataStorage cellToModify = getOrCreateCell(uuid); + if (keys != null && amounts != null) { + cellToModify.keys = keys; + cellToModify.amounts = amounts; } - updateCell(cellID, cellToModify); + // 更新 DataStorage 的 itemCount 字段 + cellToModify.itemCount = itemCount; + // 将修改后的 DataStorage 对象更新到 cells 映射 + updateCell(uuid, cellToModify); } - /** - * 删除某个 UUID 的持久化记录并标记为脏 - */ - public void removeCell(UUID uuid) { - cells.remove(uuid); - setDirty(); - } - - /** - * 强制将内存中的所有 InfinityDataStorage 写入磁盘 - * 可在服务器停止或 tick 中调用,确保数据不会丢失 - */ - public void forceSaveAll(ServerLevel level) { - try { - if (level != null) { - this.setDirty(); - CompoundTag nbt = new CompoundTag(); - this.save(nbt); - File file = level.getServer().getWorldPath(new LevelResource("data")) - .resolve(FILE_NAME + ".dat").toFile(); - NbtIo.writeCompressed(nbt, file); - - // 打印所有已保存的无限磁盘 UUID 及相关信息 - StringBuilder sb = new StringBuilder(); - sb.append("Saving Infinity Disks (UUIDs and info):\n"); - for (Map.Entry entry : cells.entrySet()) { - UUID uuid = entry.getKey(); - InfinityDataStorage data = entry.getValue(); - int types = (data.keys != null) ? data.keys.size() : 0; - sb.append(" - UUID: ").append(uuid) - .append(", Types: ").append(types) - .append("\n"); - } - LOGGER.info(sb.toString()); - } - } catch (Throwable ex) { - LOGGER.info("InfinityStorageManager forceSaveAll error: {}", ex.getMessage()); - } + // 静态方法,获取 StorageManager 的单例实例 + public static InfinityStorageManager getInstance(MinecraftServer server) { + ServerLevel world = server.getLevel(ServerLevel.OVERWORLD); + // 使用 DataStorage 的 computeIfAbsent 方法加载或创建 StorageManager 实例 + // 如果数据存在,则调用 readNbt 加载;否则调用默认构造器创建新实例 + return world.getDataStorage().computeIfAbsent( + InfinityStorageManager::readNbt, + InfinityStorageManager::new, + InfinityConstants.SAVE_FILE_NAME + ); } }