fix: 移除无线存储元件

This commit is contained in:
C-H716 2025-09-20 19:53:52 +08:00
parent 8e74d13d73
commit 1ee535391f
8 changed files with 5 additions and 803 deletions

View File

@ -1,18 +1,13 @@
package com.extendedae_plus;
import appeng.api.storage.StorageCells;
import appeng.block.AEBaseEntityBlock;
import appeng.blockentity.crafting.CraftingBlockEntity;
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.init.*;
import com.extendedae_plus.network.ModNetwork;
import com.extendedae_plus.util.storage.InfinityStorageManager;
import com.mojang.logging.LogUtils;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Blocks;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
@ -21,7 +16,6 @@ import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.level.LevelEvent;
import net.neoforged.neoforge.event.server.ServerStartingEvent;
import org.slf4j.Logger;
@ -56,10 +50,6 @@ public class ExtendedAEPlus {
// Note that this is necessary if and only if we want *this* class (ExtendedAEPlus) to respond directly to events.
// Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below.
NeoForge.EVENT_BUS.register(this);
NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad);
NeoForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick);
NeoForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping);
// 注册配置接入自定义的 ModConfigs
modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
@ -75,7 +65,7 @@ public class ExtendedAEPlus {
LOGGER.info("HELLO FROM COMMON SETUP");
// 示例日志避免引用不存在的模板 Config 字段
LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE);
// 绑定 AE2 CraftingBlockEntity 到本模组的自定义加速器方块避免 AEBaseEntityBlock.blockEntityType 为空
event.enqueueWork(() -> {
try {
@ -120,13 +110,5 @@ public class ExtendedAEPlus {
// Do something when the server starts
LOGGER.info("HELLO from server starting");
}
// 在世界加载时注册/加载 SavedData
private static void onLevelLoad(LevelEvent.Load event) {
if (event.getLevel() instanceof ServerLevel serverLevel) {
InfinityStorageManager.getForLevel(serverLevel);
}
}
}

View File

@ -1,36 +0,0 @@
package com.extendedae_plus.ae.api.storage;
import appeng.api.storage.cells.ICellHandler;
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 需要访问或创建存储单元时返回对应的 InfinityBigIntegerCellInventoryStorageCell 实现
* 参数 container AE2 提供的保存回调ISaveProvider cell 需要持久化时会调用它
*/
@Override
public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) {
return InfinityBigIntegerCellInventory.createInventory(is, container);
}
}

View File

@ -1,435 +0,0 @@
package com.extendedae_plus.ae.api.storage;
import appeng.api.config.Actionable;
import appeng.api.networking.security.IActionSource;
import appeng.api.stacks.AEItemKey;
import appeng.api.stacks.AEKey;
import appeng.api.stacks.KeyCounter;
import appeng.api.storage.cells.CellState;
import appeng.api.storage.cells.ISaveProvider;
import appeng.api.storage.cells.StorageCell;
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
import com.extendedae_plus.util.storage.InfinityDataStorage;
import com.extendedae_plus.util.storage.InfinityStorageManager;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import net.neoforged.neoforge.event.tick.ServerTickEvent;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* InfinityBigIntegerCellInventory
* <p>
* 本类实现 AE2 StorageCell表示单个 Infinity 存储单元的运行时数据与行为
* 主要职责
* - 在内存中维护条目映射 (AEKey -> BigInteger 数量)
* - 提供插入/提取/列举/持久化等操作的实现
* - 通过 UUID ItemStack 与世界级的 SavedData 关联以实现持久化
* <p>
* 重要字段
* - stack: 关联的 ItemStackNB T 中保存 UUID 与缓存信息
* - container: AE2 提供的保存回调 (ISaveProvider)用于合并与触发持久化
* - storedMap: 延迟初始化的内存映射减少未使用时内存占用
* - totalStored: 缓存的总数量 (BigInteger)避免频繁全表扫描
* - isPersisted: 标记内存状态是否已同步到持久层
*/
public class InfinityBigIntegerCellInventory implements StorageCell {
// 待持久化队列用于 debounce在服务器 tick 中合并持久化
private static final ConcurrentLinkedQueue<InfinityBigIntegerCellInventory> PENDING_PERSIST = new ConcurrentLinkedQueue<>();
// 数字格式化对象保留两位小数复用以减少对象分配
private static final DecimalFormat DF = new DecimalFormat("#.##");
// 关联的 ItemStack含可能的 uuid NBT
private final ItemStack stack;
// AE2 提供的保存提供者用于在容器中批量保存时触发回调
private final ISaveProvider container;
// 内存中的键-数量映射使用 BigInteger 支持超长数量延迟初始化
private Object2ObjectMap<AEKey, BigInteger> storedMap = null;
// 标记是否已持久化到 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;
}
// 服务器 tick 回调合并并执行待持久化项
public static void onServerTick(ServerTickEvent.Post event) {
// NeoForge 提供 Pre/Post 子类以区分阶段Post 对应原来的 Phase.END
InfinityBigIntegerCellInventory inv;
// 处理本次 tick 中的全部待持久化项
while ((inv = PENDING_PERSIST.poll()) != null) {
try {
if (!inv.isPersisted) {
inv.persist();
}
} catch (Throwable ignored) {
// 忽略单项错误继续处理其余队列
}
}
}
// 在服务器停止时被调用立即强制持久化队列中的所有实例
public static void onServerStopping(ServerStoppingEvent event) {
InfinityBigIntegerCellInventory inv;
while ((inv = PENDING_PERSIST.poll()) != null) {
try {
if (!inv.isPersisted) {
inv.persist();
}
} catch (Throwable ignored) {
// 忽略单项错误继续尝试持久化其它实例
}
}
// 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回在单人模式下可能直接由系统触发
try {
var stor = getStorageInstance();
if (stor != null) stor.setDirty();
} catch (Throwable ignored) {
}
}
// BigInteger 格式化为带单位的字符串保留两位小数
public static String formatBigInteger(BigInteger number) {
// 使用局部 DF非线程安全 Minecraft 通常在主线程运行
BigDecimal bd = new BigDecimal(number);
BigDecimal thousand = new BigDecimal(1000);
String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"};
int idx = 0;
while (bd.compareTo(thousand) >= 0 && idx < units.length - 1) {
bd = bd.divide(thousand, 2, RoundingMode.HALF_UP);
idx++;
}
if (idx == 0) {
return bd.setScale(0, RoundingMode.DOWN).toPlainString();
}
return DF.format(bd.doubleValue()) + units[idx];
}
// 获取当前存储单元的数据存储对象
private InfinityDataStorage getCellStorage() {
if (this.getUUID() == null) {
// 如果没有UUID返回空存储
return InfinityDataStorage.EMPTY;
} else {
// 否则获取或创建对应UUID的存储
return getStorageInstance().getOrCreateCell(getUUID());
}
}
// 获取存储单元状态/非空
@Override
public CellState getStatus() {
if (this.getCellStoredMap().isEmpty()) {
return CellState.EMPTY;
}
return CellState.NOT_EMPTY;
}
// 获取存储单元的待机能耗
@Override
public double getIdleDrain() {
return 512;
}
// 获取存储单元的描述此处返回null可自定义
@Override
public Component getDescription() {
return null;
}
// 判断物品堆栈是否有UUID
public boolean hasUUID() {
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
if (customData == null) return false;
CompoundTag tag = customData.copyTag();
return tag != null && tag.contains("uuid");
}
// 获取物品堆栈的UUID
public UUID getUUID() {
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
if (customData == null) return null;
CompoundTag tag = customData.copyTag();
if (tag != null && tag.contains("uuid")) {
return tag.getUUID("uuid");
}
return null;
}
// 获取或初始化存储映射
private Object2ObjectMap<AEKey, BigInteger> getCellStoredMap() {
if (storedMap == null) {
storedMap = new Object2ObjectOpenHashMap<>();
this.loadCellStoredMap();
}
return storedMap;
}
// 从存储中加载物品映射
private void loadCellStoredMap() {
boolean corruptedTag = false; // 标记数据是否损坏
if (!this.hasUUID()) return;
ListTag keys = this.getCellStorage().keys;
ListTag amounts = this.getCellStorage().amounts;
int len = Math.min(keys.size(), amounts.size());
HolderLookup.Provider registries = getStorageInstance().getRegistries();
for (int i = 0; i < len; i++) {
AEKey key = AEKey.fromTagGeneric(registries,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);
}
}
}
// 获取所有可用的物品堆栈及其数量
@Override
public void getAvailableStacks(KeyCounter out) {
BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
Object2ObjectMap<AEKey, BigInteger> map = getCellStoredMap();
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
AEKey key = entry.getKey();
BigInteger value = entry.getValue();
// 当前 KeyCounter 中已有的值long
long existing = out.get(key);
// existing 与当前 value BigInteger 累加并饱和到 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)
if (existing == Long.MAX_VALUE) {
// 已经饱和无需再添加
continue;
}
long delta;
if (toSet == Long.MAX_VALUE) {
delta = Long.MAX_VALUE - existing;
} else {
delta = toSet - existing;
}
if (delta != 0) {
out.add(key, delta);
}
}
}
// 持久化存储单元数据到全局存储
@Override
public void persist() {
if (this.isPersisted)
return;
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
if (map.isEmpty()) {
// 如果存储为空移除UUID和全局存储中的数据
if (this.hasUUID()) {
getStorageInstance().removeCell(getUUID());
// CustomData 中移除缓存字段
try {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
tag.remove("uuid");
tag.remove("total");
});
} catch (Throwable ignored) {
}
}
return;
}
// 构建要保存的Key和数量列表混合表示long string
ListTag amountTags = new ListTag();
ListTag keys = new ListTag();
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
BigInteger amount = entry.getValue();
if (amount.compareTo(BigInteger.ZERO) > 0) {
keys.add(entry.getKey().toTagGeneric(getStorageInstance().getRegistries()));
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()) {
getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage());
} else {
// amounts 现在为 CompoundTag 列表
getStorageInstance().modifyCell(this.getUUID(), keys, amountTags);
}
// 将缓存的 totalStored 同步到 ItemStack CustomData优先使用 long
try {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
tag.putLong("total", totalStored.longValue());
} else {
tag.putString("total", totalStored.toString());
}
// 将当前已存储的不同物品种类数缓存到 CustomData键名: "types"用于客户端 tooltip 显示
int typesCount = this.getCellStoredMap().size();
tag.putInt("types", typesCount);
});
} catch (Throwable ignored) {
}
isPersisted = true;
}
// 插入物品到存储单元
@Override
public long insert(AEKey what, long amount, Actionable mode, IActionSource source) {
// 数量为0或类型不匹配直接返回
if (amount == 0)
return 0;
// 不允许存储无限单元自身
if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem)
return 0;
// 如果没有UUID生成UUID并初始化存储
if (!this.hasUUID()) {
UUID id = UUID.randomUUID();
try {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putUUID("uuid", id));
} catch (Throwable ignored) {
}
getStorageInstance().getOrCreateCell(getUUID());
// 确保 storedMap 初始化并从持久层加载数据
this.getCellStoredMap();
}
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
BigInteger currentAmount = map.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));
this.saveChanges();
}
return amount;
}
// 从存储单元提取物品
@Override
public long extract(AEKey what, long amount, Actionable mode, IActionSource source) {
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
BigInteger currentAmount = map.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 (mode == Actionable.MODULATE) {
map.remove(what);
// 更新 cached total
// 如果 currentAmount 大于 Long.MAX_VALUEtotalStored 减去 currentAmount 会保留大整数
totalStored = totalStored.subtract(currentAmount);
this.saveChanges();
}
return ret;
} else {
// 提取部分
if (mode == Actionable.MODULATE) {
map.put(what, currentAmount.subtract(requested));
// 更新 cached total
totalStored = totalStored.subtract(requested);
this.saveChanges();
}
return amount;
}
}
return 0;
}
// 获取存储单元内所有物品的总数量格式化字符串
public String getTotalStorage() {
// 使用缓存的 totalStored避免每次全表扫描
return formatBigInteger(totalStored);
}
}

View File

@ -1,81 +0,0 @@
package com.extendedae_plus.ae.items;
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
import com.google.common.base.Preconditions;
import net.minecraft.ChatFormatting;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.LongTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.CustomData;
import org.jetbrains.annotations.NotNull;
import java.math.BigInteger;
import java.util.List;
public class InfinityBigIntegerCellItem extends Item {
public InfinityBigIntegerCellItem() {
super(new Properties().stacksTo(1).fireResistant());
}
@Override
public void appendHoverText(ItemStack stack,
@NotNull TooltipContext context,
List<Component> tooltip,
@NotNull TooltipFlag tooltipFlag) {
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon1"));
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon2"));
Preconditions.checkArgument(stack.getItem() == this);
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
if (customData != null) {
CompoundTag tag = customData.copyTag();
if (tag != null && tag.contains("uuid")) {
String uuidStr = tag.getUUID("uuid").toString();
tooltip.add(
Component.literal("UUID: ")
.withStyle(ChatFormatting.GRAY)
.append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW))
);
if (tag.contains("types")) {
try {
int types = tag.getInt("types");
tooltip.add(
Component.literal("Types: ")
.withStyle(ChatFormatting.GRAY)
.append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN))
);
} catch (Exception ignored) {
}
}
if (tag.contains("total")) {
BigInteger total = BigInteger.ZERO;
Tag t = tag.get("total");
try {
if (t instanceof LongTag) {
total = BigInteger.valueOf(tag.getLong("total"));
} else {
String s = tag.getString("total");
total = new BigInteger(s);
}
} catch (Exception ignored) {
}
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
tooltip.add(
Component.literal("Byte: ")
.withStyle(ChatFormatting.GRAY)
.append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
);
}
}
}
}
}

View File

@ -27,7 +27,7 @@ public final class ModCreativeTabs {
output.accept(ModItems.ACCELERATOR_256x.get());
output.accept(ModItems.ACCELERATOR_1024x.get());
output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
// output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
})
.build());
}

View File

@ -1,7 +1,6 @@
package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.neoforged.neoforge.registries.DeferredItem;
@ -48,8 +47,8 @@ public final class ModItems {
() -> new BlockItem(ModBlocks.ACCELERATOR_1024x.get(), new Item.Properties())
);
public static final DeferredItem<Item> INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
"infinity_biginteger_cell", InfinityBigIntegerCellItem::new
);
// public static final DeferredItem<Item> INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
// "infinity_biginteger_cell", InfinityBigIntegerCellItem::new
// );
}

View File

@ -1,61 +0,0 @@
package com.extendedae_plus.util.storage;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
/**
* InfinityDataStorage
*
* 表示单个 UUID 对应的持久化数据容器直接映射到世界存档中的一项记录
* 数据结构说明
* - keys: 存放序列化后的 AEKey每项为 CompoundTag用于标识不同的存储条目
* - amounts: keys 一一对应的数量列表每项为 CompoundTag采用混合表示
* - 当数量能放入 long CompoundTag 包含键 "l" 存放 long
* - 当数量超出 long CompoundTag 包含键 "s" 存放 BigInteger 的字符串形式
*
* 该类提供将内存数据与 NBT 之间互转的辅助方法 `SavedData` 在世界保存/加载时调用
*/
public class InfinityDataStorage {
/** 空实例(表示没有数据) */
public static final InfinityDataStorage EMPTY = new InfinityDataStorage();
/** 序列化的键列表NBT ListTag元素为 CompoundTag */
public ListTag keys;
/**
* keys 对应的数量列表NBT ListTag元素为 CompoundTag
* - 若数量能放入 long CompoundTag 包含键 "l"(long)
* - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式
*/
public ListTag amounts;
public InfinityDataStorage() {
this(new ListTag(), new ListTag());
}
private InfinityDataStorage(ListTag keys, ListTag amounts) {
this.keys = keys;
this.amounts = amounts;
}
/**
* 将当前数据封装为 CompoundTag 以写入存档
*/
public CompoundTag serializeNBT() {
CompoundTag nbt = new CompoundTag();
nbt.put("keys", keys);
nbt.put("amounts", amounts);
return nbt;
}
/**
* 从存档读取数据并构造实例
*/
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);
}
}

View File

@ -1,166 +0,0 @@
package com.extendedae_plus.util.storage;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.saveddata.SavedData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* InfinityStorageManager
* <p>
* 世界级别的持久化容器集中管理所有 InfinityBigInteger 存储单元的序列化数据
* 功能要点
* - 在世界加载时从存档恢复所有 cell 的数据
* - 提供按 UUID 获取/创建单个 cell 的数据容器
* - 在世界保存时将内存数据打包为 NBT 写回存档
*/
public class InfinityStorageManager extends SavedData {
private static final Factory<InfinityStorageManager> FACTORY = new Factory<>(InfinityStorageManager::new, InfinityStorageManager::readNbt);
/**
* SavedData 文件名常量
*/
public static final String FILE_NAME = "eap_infinity_biginteger_cells";
/**
* 全局单例实例在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充
*/
public static InfinityStorageManager INSTANCE = null;
/**
* UUID -> 数据 的内存映射
*/
private final Map<UUID, InfinityDataStorage> cells;
@Nullable
private WeakReference<HolderLookup.Provider> registries;
public InfinityStorageManager() {
this.cells = new HashMap<>();
setDirty();
}
public InfinityStorageManager(Map<UUID, InfinityDataStorage> cells) {
this.cells = cells;
setDirty();
}
/**
* NBT 构造用于在世界加载时从存档恢复数据
*/
public InfinityStorageManager(CompoundTag nbt) {
this.cells = new HashMap<>();
ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
for (int i = 0; i < cellList.size(); i++) {
CompoundTag cell = cellList.getCompound(i);
cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data")));
}
setDirty();
}
/**
* 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE
*/
public static InfinityStorageManager getForLevel(ServerLevel level) {
if (INSTANCE == null && level != null) {
INSTANCE = level.getDataStorage().computeIfAbsent(FACTORY, FILE_NAME);
INSTANCE.registries = new WeakReference<>(level.registryAccess());
}
return INSTANCE;
}
public HolderLookup.Provider getRegistries() {
var r = this.registries;
if (r == null) {
throw new IllegalStateException("StorageManager was not initialized properly.");
}
var registries = r.get();
if (registries == null) {
throw new IllegalStateException("Using a StorageManager whose server was already closed");
}
return registries;
}
@Override
public @NotNull CompoundTag save(@NotNull CompoundTag nbt, HolderLookup.@NotNull Provider registries) {
// 将内存中的所有 cell 序列化为一个 ListTag
ListTag cellList = new ListTag();
for (Map.Entry<UUID, InfinityDataStorage> entry : cells.entrySet()) {
CompoundTag cell = new CompoundTag();
cell.putUUID("uuid", entry.getKey());
cell.put("data", entry.getValue().serializeNBT());
cellList.add(cell);
}
nbt.put("list", cellList);
return nbt;
}
/**
* 更新或添加某个 UUID 对应的数据并标记为脏需要保存
*/
public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) {
cells.put(uuid, infinityDataStorage);
setDirty();
}
/**
* 获取或创建某个 UUID 对应的数据容器
*/
public InfinityDataStorage getOrCreateCell(UUID uuid) {
if (!cells.containsKey(uuid)) {
updateCell(uuid, new InfinityDataStorage());
}
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;
}
updateCell(cellID, cellToModify);
}
/**
* 删除某个 UUID 的持久化记录并标记为脏
*/
public void removeCell(UUID uuid) {
cells.remove(uuid);
setDirty();
}
public static InfinityStorageManager readNbt(CompoundTag nbt, HolderLookup.Provider registries) {
Map<UUID, InfinityDataStorage> cell = new HashMap<>();
ListTag diskList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
for (int i = 0; i < diskList.size(); i++) {
CompoundTag disk = diskList.getCompound(i);
cell.put(disk.getUUID("uuid"), InfinityDataStorage.loadFromNBT(disk.getCompound("data")));
}
return new InfinityStorageManager(cell);
}
public static InfinityStorageManager getInstance(MinecraftServer server) {
ServerLevel world = server.getLevel(ServerLevel.OVERWORLD);
InfinityStorageManager manager = null;
if (world != null) {
manager = world.getDataStorage().computeIfAbsent(FACTORY, FILE_NAME);
}
if (manager != null) {
manager.registries = new WeakReference<>(server.registryAccess());
}
return manager;
}
}