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