fix stale inventory access in storage buses

This commit is contained in:
Pxx500 committed 2026-11-12 03:36:52 +01:00
1 parent 67077d9d4b
commit 5a086a71f6
11 files changed
+256 -91

No files matched your search

@@ -708,9 +708,10 @@ public class DualityInterface implements IGridTickable, IStorageMonitorable, IIn
final boolean couldDoWork = this.updateStorage();
final boolean hasWorkToDo = this.hasWorkToDo();
return (hasWorkToDo || (sentItems && this.hasItemsToSend()))
? (couldDoWork || sentItems ? TickRateModulation.URGENT : TickRateModulation.SLOWER)
: TickRateModulation.SLEEP;
if (hasWorkToDo || this.hasItemsToSend()) {
return couldDoWork || sentItems ? TickRateModulation.URGENT : TickRateModulation.SLOWER;
}
return TickRateModulation.SLEEP;
}
// Returns if it successfully sent some items
@@ -724,8 +725,8 @@ public class DualityInterface implements IGridTickable, IStorageMonitorable, IIn
boolean sentSomething = false;
for (final ForgeDirection s : possibleDirections) {
final TileEntity te = w
.getTileEntity(tile.xCoord + s.offsetX, tile.yCoord + s.offsetY, tile.zCoord + s.offsetZ);
final TileEntity te = Platform
.getLoadedTileEntity(w, tile.xCoord + s.offsetX, tile.yCoord + s.offsetY, tile.zCoord + s.offsetZ);
if (te == null) continue;
@@ -1254,8 +1255,8 @@ public class DualityInterface implements IGridTickable, IStorageMonitorable, IIn
final ArrayList<VerifiedAcceptors> verifiedSides = new ArrayList<>();
for (final ForgeDirection s : possibleDirections) {
final TileEntity te = w
.getTileEntity(tile.xCoord + s.offsetX, tile.yCoord + s.offsetY, tile.zCoord + s.offsetZ);
final TileEntity te = Platform
.getLoadedTileEntity(w, tile.xCoord + s.offsetX, tile.yCoord + s.offsetY, tile.zCoord + s.offsetZ);
if (te == null) continue;
@@ -1403,8 +1404,11 @@ public class DualityInterface implements IGridTickable, IStorageMonitorable, IIn
boolean allAreBusy = true;
for (final ForgeDirection s : possibleDirections) {
final TileEntity te = w
.getTileEntity(tile.xCoord + s.offsetX, tile.yCoord + s.offsetY, tile.zCoord + s.offsetZ);
final TileEntity te = Platform.getLoadedTileEntity(
w,
tile.xCoord + s.offsetX,
tile.yCoord + s.offsetY,
tile.zCoord + s.offsetZ);
if (te != null && te.getClass().getName().equals("li.cil.oc.common.tileentity.Adapter")) continue;
final InventoryAdaptor ad = InventoryAdaptor.getAdaptor(te, s.getOpposite());
if (ad != null) {
@@ -1,15 +1,24 @@
package appeng.me.storage;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nonnull;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.chunk.Chunk;
import appeng.api.config.AccessRestriction;
import appeng.api.config.Actionable;
import appeng.api.networking.security.BaseActionSource;
import appeng.api.storage.IMEInventory;
import appeng.api.storage.IMENetworkInventory;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IAEStackType;
import appeng.api.storage.data.IItemList;
import appeng.util.Platform;
import appeng.util.item.ItemFilterList;
import appeng.util.item.NetworkItemList;
import appeng.util.item.PrioritizedNetworkItemList;
@@ -17,11 +26,49 @@ import appeng.util.item.PrioritizedNetworkItemList;
public class StorageBusInventoryHandler<T extends IAEStack<T>> extends MEInventoryHandler<T> {
private final Supplier<AccessRestriction> reshuffleAccess;
private final TileEntity target;
private final Chunk targetChunk;
private final TileEntity[] otherTargets;
private final Chunk[] otherChunks;
public StorageBusInventoryHandler(IMEInventory<T> i, IAEStackType<T> type,
Supplier<AccessRestriction> reshuffleAccess) {
Supplier<AccessRestriction> reshuffleAccess, TileEntity target) {
super(i, type);
this.reshuffleAccess = reshuffleAccess;
List<TileEntity> targets = Platform.getInventoryTiles(target);
this.target = targets.isEmpty() ? null : targets.get(0);
this.targetChunk = this.target == null ? null
: this.target.getWorldObj().getChunkFromBlockCoords(this.target.xCoord, this.target.zCoord);
this.otherTargets = new TileEntity[Math.max(0, targets.size() - 1)];
this.otherChunks = new Chunk[this.otherTargets.length];
for (int index = 1; index < targets.size(); index++) {
TileEntity tile = targets.get(index);
this.otherTargets[index - 1] = tile;
this.otherChunks[index - 1] = tile.getWorldObj().getChunkFromBlockCoords(tile.xCoord, tile.zCoord);
}
}
public boolean isValid() {
if (this.target == null || this.target.isInvalid() || !this.targetChunk.isChunkLoaded) return false;
for (int index = 0; index < this.otherTargets.length; index++) {
if (this.otherTargets[index].isInvalid() || !this.otherChunks[index].isChunkLoaded) return false;
}
return true;
}
@Override
public boolean canAccept(T input) {
return this.isValid() && super.canAccept(input);
}
@Override
public T extractItems(T request, Actionable type, BaseActionSource src) {
return this.isValid() ? super.extractItems(request, type, src) : null;
}
@Override
public T getAvailableItem(@Nonnull T request, int iteration) {
return this.isValid() ? super.getAvailableItem(request, iteration) : null;
}
@Override
@@ -37,7 +84,7 @@ public class StorageBusInventoryHandler<T extends IAEStack<T>> extends MEInvento
@Override
public IItemList<T> getAvailableItems(final IItemList<T> out, int iteration, Optional<Predicate<T>> filter) {
if (!this.hasReadAccess && !isVisible()) {
if (!this.isValid() || (!this.hasReadAccess && !isVisible())) {
return out;
}
@@ -73,6 +120,7 @@ public class StorageBusInventoryHandler<T extends IAEStack<T>> extends MEInvento
@Override
public PrioritizedNetworkItemList<T> getAvailableItemsWithPriority(int iteration) {
if (!this.isValid()) return new PrioritizedNetworkItemList<>(this.getExternalNetworkInventory());
final Predicate<T> predicate = this.isExtractFilterActive() && !this.getExtractPartitionList().isEmpty()
? this.getExtractFilterCondition()
: e -> true;
@@ -10,11 +10,14 @@
package appeng.parts.automation;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest;
import net.minecraft.util.IIcon;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
@@ -51,6 +54,8 @@ public abstract class PartSharedItemBus<StackType extends IAEStack<StackType>> e
private final IAEStackInventory config = new IAEStackInventory(this, 9);
private int cachedAdaptorHash = 0;
private InventoryAdaptor cachedAdaptor;
private List<TileEntity> cachedTargets = Collections.emptyList();
private boolean targetChunkUnloaded;
private boolean lastRedstone = false;
private boolean pendingStateUpdate = false;
protected String oreFilterString = "";
@@ -117,17 +122,25 @@ public abstract class PartSharedItemBus<StackType extends IAEStack<StackType>> e
self.xCoord + this.getSide().offsetX,
self.yCoord + this.getSide().offsetY,
self.zCoord + this.getSide().offsetZ);
this.targetChunkUnloaded = !self.getWorldObj().blockExists(
self.xCoord + this.getSide().offsetX,
self.yCoord + this.getSide().offsetY,
self.zCoord + this.getSide().offsetZ)
|| (target instanceof TileEntityChest && !Platform.isInventoryTileLoaded(target));
final int newAdaptorHash = Platform.generateTileHash(target);
// Some neighbors, such as GT tanks, expose inventories only after placement initialization.
if (this.cachedAdaptorHash == newAdaptorHash && newAdaptorHash != 0 && this.cachedAdaptor != null) {
if (this.cachedAdaptorHash == newAdaptorHash && newAdaptorHash != 0
&& this.cachedAdaptor != null
&& Platform.areInventoryTilesLoaded(this.cachedTargets)) {
return this.cachedAdaptor;
}
// noinspection MagicConstant
this.cachedAdaptor = InventoryAdaptor.getAdaptor(target, this.getSide().getOpposite(), getAdaptorFlags());
this.cachedAdaptorHash = this.cachedAdaptor == null ? 0 : newAdaptorHash;
this.cachedTargets = Platform.getInventoryTiles(target);
return this.cachedAdaptor;
}
@@ -143,11 +156,9 @@ public abstract class PartSharedItemBus<StackType extends IAEStack<StackType>> e
}
/**
* Checks if the bus can actually do something.
* <p>
* Currently this tests if the chunk for the target is actually loaded.
* Checks whether the target and any other half of a vanilla chest can be accessed without loading chunks.
*
* @return true, if the the bus should do its work.
* @return true if the target's chunks are loaded.
*/
protected boolean canDoBusWork() {
final TileEntity self = this.getHost().getTile();
@@ -155,7 +166,10 @@ public abstract class PartSharedItemBus<StackType extends IAEStack<StackType>> e
final int xCoordinate = self.xCoord + this.getSide().offsetX;
final int zCoordinate = self.zCoord + this.getSide().offsetZ;
return world != null && world.getChunkProvider().chunkExists(xCoordinate >> 4, zCoordinate >> 4);
if (world == null || !world.getChunkProvider().chunkExists(xCoordinate >> 4, zCoordinate >> 4)) return false;
TileEntity target = Platform
.getLoadedTileEntity(world, xCoordinate, self.yCoord + this.getSide().offsetY, zCoordinate);
return !(target instanceof TileEntityChest) || Platform.isInventoryTileLoaded(target);
}
private void updateState() {
@@ -190,12 +204,7 @@ public abstract class PartSharedItemBus<StackType extends IAEStack<StackType>> e
protected TileEntity getTileEntity(final TileEntity self, final int x, final int y, final int z) {
final World w = self.getWorldObj();
if (w.getChunkProvider().chunkExists(x >> 4, z >> 4)) {
return w.getTileEntity(x, y, z);
}
return null;
return Platform.getLoadedTileEntity(w, x, y, z);
}
protected abstract TickRateModulation doBusWork();
@@ -213,7 +222,7 @@ public abstract class PartSharedItemBus<StackType extends IAEStack<StackType>> e
@Override
protected boolean isSleeping() {
return this.getTarget() == null || super.isSleeping();
return (this.getTarget() == null && !this.targetChunkUnloaded) || super.isSleeping();
}
@Override
@@ -28,6 +28,7 @@ import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants.NBT;
@@ -122,7 +123,8 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
private int priority = 0;
private boolean cached = false;
private IStorageBusMonitor<?> monitor = null;
private MEInventoryHandler handler = null;
private StorageBusInventoryHandler handler = null;
private boolean targetChunkUnloaded = false;
private int handlerHash = 0;
private boolean wasActive = false;
private byte resetCacheLogic = 0;
@@ -526,12 +528,15 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
return new TickingRequest(
TickRates.StorageBus.getMin(),
TickRates.StorageBus.getMax(),
this.monitor == null,
this.monitor == null && !this.targetChunkUnloaded,
true);
}
@Override
public TickRateModulation tickingRequest(final IGridNode node, final int ticksSinceLastCall) {
if (this.targetChunkUnloaded || (this.handler != null && !this.handler.isValid())) {
this.resetCacheLogic = 2;
}
if (this.resetCacheLogic != 0) {
this.resetCache();
}
@@ -540,7 +545,7 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
return this.monitor.onTick();
}
return TickRateModulation.SLEEP;
return this.targetChunkUnloaded ? TickRateModulation.SLOWER : TickRateModulation.SLEEP;
}
private void resetCache() {
@@ -605,11 +610,7 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
// Looks for interfaces for subnet
private IMEInventory getConnectedInventory() {
final TileEntity self = this.getHost().getTile();
final TileEntity target = self.getWorldObj().getTileEntity(
self.xCoord + this.getSide().offsetX,
self.yCoord + this.getSide().offsetY,
self.zCoord + this.getSide().offsetZ);
final TileEntity target = this.getTarget();
if (target != null) {
final IExternalStorageHandler esh = AEApi.instance().registries().externalStorage()
@@ -621,23 +622,38 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
return null;
}
private TileEntity getTarget() {
final TileEntity self = this.getHost().getTile();
final World world = self.getWorldObj();
final ForgeDirection side = this.getSide();
final int x = self.xCoord + side.offsetX;
final int y = self.yCoord + side.offsetY;
final int z = self.zCoord + side.offsetZ;
this.targetChunkUnloaded = !world.blockExists(x, y, z);
if (this.targetChunkUnloaded) return null;
final TileEntity target = Platform.getLoadedTileEntity(world, x, y, z);
if (target instanceof TileEntityChest && !Platform.isInventoryTileLoaded(target)) {
this.targetChunkUnloaded = true;
return null;
}
return target;
}
public MEInventoryHandler getInternalHandler() {
if (this.cached) {
if (this.cached && !this.targetChunkUnloaded && (this.handler == null || this.handler.isValid())) {
return this.handler;
}
final boolean wasSleeping = this.monitor == null;
this.cached = true;
final TileEntity self = this.getHost().getTile();
final ForgeDirection side = this.getSide();
final TileEntity target = self.getWorldObj()
.getTileEntity(self.xCoord + side.offsetX, self.yCoord + side.offsetY, self.zCoord + side.offsetZ);
final TileEntity target = this.getTarget();
final int newHandlerHash = Platform.generateTileHash(target);
if (this.handlerHash == newHandlerHash && this.handlerHash != 0 && this.handler != null) {
if (this.handlerHash == newHandlerHash && this.handlerHash != 0
&& this.handler != null
&& this.handler.isValid()) {
return this.handler;
}
@@ -665,7 +681,11 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
if (inv != null) {
this.checkInterfaceVsStorageBus(target, this.getSide().getOpposite());
this.handler = new StorageBusInventoryHandler<>(inv, this.getStackType(), this::getReshuffleAccess);
this.handler = new StorageBusInventoryHandler<>(
inv,
this.getStackType(),
this::getReshuffleAccess,
target);
AccessRestriction currentAccess = (AccessRestriction) this.getConfigManager()
.getSetting(Settings.ACCESS);
@@ -740,7 +760,7 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
if (wasSleeping != (this.monitor == null)) {
try {
final ITickManager tm = this.getProxy().getTick();
if (this.monitor == null) {
if (this.monitor == null && !this.targetChunkUnloaded) {
tm.sleepDevice(this.getProxy().getNode());
} else {
tm.wakeDevice(this.getProxy().getNode());
@@ -18,6 +18,7 @@ import net.minecraftforge.common.util.ForgeDirection;
import appeng.core.AEConfig;
import appeng.me.GridAccessException;
import appeng.util.Platform;
import cofh.api.energy.IEnergyReceiver;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@@ -127,19 +128,18 @@ public class PartP2PGT5Power extends PartP2PTunnelNormal<PartP2PGT5Power> implem
@Nullable
private TileEntity getTarget() {
TileEntity te;
if (this.isCachedTargetValid) {
te = this.cachedTarget;
if (te == null || !te.isInvalid()) {
return te;
}
}
this.isCachedTargetValid = true;
te = this.getTile();
TileEntity te = this.getTile();
ForgeDirection side = this.getSide();
return this.cachedTarget = te.getWorldObj()
.getTileEntity(te.xCoord + side.offsetX, te.yCoord + side.offsetY, te.zCoord + side.offsetZ);
TileEntity target = Platform.getLoadedTileEntity(
te.getWorldObj(),
te.xCoord + side.offsetX,
te.yCoord + side.offsetY,
te.zCoord + side.offsetZ);
if (!this.isCachedTargetValid || this.cachedTarget != target) {
this.cachedTarget = target;
this.isCachedTargetValid = true;
}
return this.cachedTarget;
}
private long injectEnergy(IEnergyConnected te, ForgeDirection oppositeSide, long aVoltage, long aAmperage) {
@@ -59,6 +59,7 @@ public class PartP2PItems extends PartP2PTunnelNormal<PartP2PItems>
private int oldSize = 0;
private boolean requested;
private IInventory cachedInv;
private List<TileEntity> cachedTargets = new LinkedList<>();
public PartP2PItems(final ItemStack is) {
super(is);
@@ -76,11 +77,12 @@ public class PartP2PItems extends PartP2PTunnelNormal<PartP2PItems>
private IInventory getDestination() {
this.requested = true;
if (this.cachedInv != null) {
if (this.cachedInv != null && Platform.areInventoryTilesLoaded(this.cachedTargets)) {
return this.cachedInv;
}
final List<IInventory> outs = new LinkedList<>();
this.cachedTargets = new LinkedList<>();
final TunnelCollection<PartP2PItems> itemTunnels;
try {
@@ -93,7 +95,7 @@ public class PartP2PItems extends PartP2PTunnelNormal<PartP2PItems>
if (t.loopsBackTo(this, new HashSet<>())) {
continue;
}
final IInventory inv = t.getOutputInv();
final IInventory inv = t.getOutputInv(this.cachedTargets);
if (inv != null) {
if (Platform.getRandomInt() % 2 == 0) {
outs.add(inv);
@@ -106,14 +108,17 @@ public class PartP2PItems extends PartP2PTunnelNormal<PartP2PItems>
return this.cachedInv = new WrapperChainedInventory(outs);
}
private IInventory getOutputInv() {
private IInventory getOutputInv(List<TileEntity> targets) {
IInventory output = null;
if (this.getProxy().isActive()) {
final TileEntity te = this.getTile().getWorldObj().getTileEntity(
final TileEntity te = Platform.getLoadedTileEntity(
this.getTile().getWorldObj(),
this.getTile().xCoord + this.getSide().offsetX,
this.getTile().yCoord + this.getSide().offsetY,
this.getTile().zCoord + this.getSide().offsetZ);
final List<TileEntity> inventoryTiles = Platform.getInventoryTiles(te);
if (inventoryTiles.isEmpty()) return null;
if (this.which.contains(this)) {
return null;
@@ -148,6 +153,10 @@ public class PartP2PItems extends PartP2PTunnelNormal<PartP2PItems>
}
this.which.pop();
if (output != null) {
targets.add(this.getTile());
targets.addAll(inventoryTiles);
}
}
return output;
@@ -320,7 +329,8 @@ public class PartP2PItems extends PartP2PTunnelNormal<PartP2PItems>
}
private boolean loopsBackTo(final PartP2PItems tunnel, final Set<PartP2PItems> visited) {
final TileEntity te = this.getTile().getWorldObj().getTileEntity(
final TileEntity te = Platform.getLoadedTileEntity(
this.getTile().getWorldObj(),
this.getTile().xCoord + this.getSide().offsetX,
this.getTile().yCoord + this.getSide().offsetY,
this.getTile().zCoord + this.getSide().offsetZ);
@@ -26,6 +26,7 @@ import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidHandler;
import appeng.me.GridAccessException;
import appeng.util.Platform;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@@ -187,19 +188,15 @@ public class PartP2PLiquids extends PartP2PTunnelNormal<PartP2PLiquids> implemen
return null;
}
if (this.cachedTank != null) {
return this.cachedTank;
}
final TileEntity te = this.getTile().getWorldObj().getTileEntity(
final TileEntity te = Platform.getLoadedTileEntity(
this.getTile().getWorldObj(),
this.getTile().xCoord + this.getSide().offsetX,
this.getTile().yCoord + this.getSide().offsetY,
this.getTile().zCoord + this.getSide().offsetZ);
if (te instanceof IFluidHandler) {
return this.cachedTank = (IFluidHandler) te;
if (this.cachedTank != te) {
this.cachedTank = te instanceof IFluidHandler handler ? handler : null;
}
return null;
return this.cachedTank;
}
@Override
@@ -69,7 +69,7 @@ public final class PartP2PRFPower extends PartP2PTunnelNormal<PartP2PRFPower> im
return 0;
}
if (this.isActive()) {
if (this.getProxy().isActive()) {
final Stack<PartP2PRFPower> stack = this.getDepth();
for (final PartP2PRFPower t : stack) {
@@ -136,12 +136,13 @@ public final class PartP2PRFPower extends PartP2PTunnelNormal<PartP2PRFPower> im
private IEnergyReceiver getOutput() {
if (this.isOutput()) {
if (!this.cachedTarget) {
final TileEntity self = this.getTile();
final TileEntity te = self.getWorldObj().getTileEntity(
self.xCoord + this.getSide().offsetX,
self.yCoord + this.getSide().offsetY,
self.zCoord + this.getSide().offsetZ);
final TileEntity self = this.getTile();
final TileEntity te = Platform.getLoadedTileEntity(
self.getWorldObj(),
self.xCoord + this.getSide().offsetX,
self.yCoord + this.getSide().offsetY,
self.zCoord + this.getSide().offsetZ);
if (!this.cachedTarget || this.outputTarget != te) {
this.outputTarget = te instanceof IEnergyReceiver ? (IEnergyReceiver) te : null;
this.cachedTarget = true;
}
@@ -157,7 +158,7 @@ public final class PartP2PRFPower extends PartP2PTunnelNormal<PartP2PRFPower> im
@Override
public int getEnergyStored(final ForgeDirection from) {
if (this.isOutput() || !this.isActive()) {
if (this.isOutput() || !this.getProxy().isActive()) {
return 0;
}
@@ -189,7 +190,7 @@ public final class PartP2PRFPower extends PartP2PTunnelNormal<PartP2PRFPower> im
@Override
public int getMaxEnergyStored(final ForgeDirection from) {
if (this.isOutput() || !this.isActive()) {
if (this.isOutput() || !this.getProxy().isActive()) {
return 0;
}
+9 -1
View File
@@ -11,6 +11,7 @@
package appeng.util;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
public class BlockUpdate implements IWorldCallable<Boolean> {
@@ -27,7 +28,14 @@ public class BlockUpdate implements IWorldCallable<Boolean> {
@Override
public Boolean call(final World world) throws Exception {
if (world.blockExists(this.x, this.y, this.z)) {
world.notifyBlocksOfNeighborChange(this.x, this.y, this.z, Platform.AIR_BLOCK);
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
final int x = this.x + side.offsetX;
final int y = this.y + side.offsetY;
final int z = this.z + side.offsetZ;
if (world.blockExists(x, y, z)) {
world.notifyBlockOfNeighborChange(x, y, z, Platform.AIR_BLOCK);
}
}
}
return true;
+27 -14
View File
@@ -13,6 +13,7 @@ package appeng.util;
import static appeng.util.Platform.isEIOLoaded;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
@@ -64,11 +65,8 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
public abstract class InventoryAdaptor implements Iterable<ItemSlot> {
// Two flat maps keyed by packObjectIdentity(te)|packSideAndFlags(d,flags).
// ADAPTOR_CACHE_REFS stores the actual te reference for identity-hash collision safety.
// ADAPTOR_CACHE stores the resolved adaptor (may be null for cached null results).
private static final Long2ObjectOpenHashMap<Object> ADAPTOR_CACHE = new Long2ObjectOpenHashMap<>();
private static final Long2ObjectOpenHashMap<Object> ADAPTOR_CACHE_REFS = new Long2ObjectOpenHashMap<>();
// Entries retain the actual reference and inventory tiles to validate identity and chunk lifetime
private static final Long2ObjectOpenHashMap<CachedAdaptor> ADAPTOR_CACHE = new Long2ObjectOpenHashMap<>();
private static int adaptorCacheTick = Integer.MIN_VALUE;
private static int counter = 0;
@@ -136,23 +134,21 @@ public abstract class InventoryAdaptor implements Iterable<ItemSlot> {
private static void cacheInvalidateIfStale(int currentTick) {
if (currentTick != adaptorCacheTick) {
ADAPTOR_CACHE.clear();
ADAPTOR_CACHE_REFS.clear();
adaptorCacheTick = currentTick;
}
}
public static void clearCache() {
ADAPTOR_CACHE.clear();
ADAPTOR_CACHE_REFS.clear();
}
/**
* Returns {@code true} iff the cache contains an entry for the given arguments and the stored reference matches the
* supplied {@code te}.
* Checks that a cache entry still refers to the supplied object and its current, loaded inventory tiles.
*/
private static boolean isCached(Object te, ForgeDirection d, int flags) {
final long key = makeCacheKey(te, d, flags);
return ADAPTOR_CACHE_REFS.get(key) == te;
final CachedAdaptor entry = ADAPTOR_CACHE.get(key);
return entry != null && entry.isValid(te);
}
/**
@@ -161,13 +157,29 @@ public abstract class InventoryAdaptor implements Iterable<ItemSlot> {
*/
private static InventoryAdaptor cacheGet(Object te, ForgeDirection d, int flags) {
final long key = makeCacheKey(te, d, flags);
return (InventoryAdaptor) ADAPTOR_CACHE.get(key);
return ADAPTOR_CACHE.get(key).adaptor;
}
private static void cachePut(Object te, ForgeDirection d, int flags, InventoryAdaptor adaptor) {
final long key = makeCacheKey(te, d, flags);
ADAPTOR_CACHE_REFS.put(key, te);
ADAPTOR_CACHE.put(key, adaptor);
ADAPTOR_CACHE.put(key, new CachedAdaptor(te, adaptor));
}
private static final class CachedAdaptor {
private final Object reference;
private final InventoryAdaptor adaptor;
private final List<TileEntity> tiles;
private CachedAdaptor(Object reference, InventoryAdaptor adaptor) {
this.reference = reference;
this.adaptor = adaptor;
this.tiles = reference instanceof TileEntity tile ? Platform.getInventoryTiles(tile) : null;
}
private boolean isValid(Object reference) {
return this.reference == reference && (this.tiles == null || Platform.areInventoryTilesLoaded(this.tiles));
}
}
// -------------------------------------------------------------------------
@@ -224,7 +236,8 @@ public abstract class InventoryAdaptor implements Iterable<ItemSlot> {
}
if (invs && te instanceof TileEntityChest) {
return new AdaptorIInventory(Platform.GetChestInv(te));
IInventory inventory = Platform.GetChestInv(te);
return inventory == null ? null : new AdaptorIInventory(inventory);
}
if (te instanceof ISidedInventory sided) {
+55
View File
@@ -726,6 +726,7 @@ public class Platform {
* Utility function to get the full inventory for a Double Chest in the World.
*/
public static IInventory GetChestInv(final Object te) {
if (!isInventoryTileLoaded((TileEntity) te)) return null;
TileEntityChest teA = (TileEntityChest) te;
TileEntity teB = null;
final Block myBlockID = teA.getWorldObj().getBlock(teA.xCoord, teA.yCoord, teA.zCoord);
@@ -779,6 +780,59 @@ public class Platform {
return new InventoryLargeChest("", teA, (IInventory) teB);
}
/** Returns the current tile without loading its chunk. */
@Nullable
public static TileEntity getLoadedTileEntity(World world, int x, int y, int z) {
if (world == null || !world.blockExists(x, y, z)) return null;
TileEntity tile = world.getTileEntity(x, y, z);
return tile == null || tile.isInvalid() ? null : tile;
}
/** Captures the current inventory tiles, or an empty list if any required chunk is unloaded. */
public static List<TileEntity> getInventoryTiles(TileEntity target) {
final List<TileEntity> tiles = new ArrayList<>(2);
if (!isInventoryTileLoaded(target)) return tiles;
if (target instanceof TileEntityChest) {
final World world = target.getWorldObj();
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
if (side.offsetY != 0) continue;
final int x = target.xCoord + side.offsetX;
final int z = target.zCoord + side.offsetZ;
final TileEntity neighbor = getLoadedTileEntity(world, x, target.yCoord, z);
if (neighbor instanceof TileEntityChest && neighbor.getBlockType() == target.getBlockType()) {
tiles.add(neighbor);
}
}
}
tiles.add(target);
return tiles;
}
/** Checks that the current target and neighboring chest chunks are already loaded. */
public static boolean isInventoryTileLoaded(TileEntity target) {
if (target == null
|| getLoadedTileEntity(target.getWorldObj(), target.xCoord, target.yCoord, target.zCoord) != target) {
return false;
}
if (target instanceof TileEntityChest) {
for (ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
if (side.offsetY == 0 && !target.getWorldObj()
.blockExists(target.xCoord + side.offsetX, target.yCoord, target.zCoord + side.offsetZ))
return false;
}
}
return true;
}
/** Checks that every captured tile still belongs to the loaded world. */
public static boolean areInventoryTilesLoaded(List<TileEntity> tiles) {
if (tiles.isEmpty()) return false;
for (TileEntity tile : tiles) {
if (getLoadedTileEntity(tile.getWorldObj(), tile.xCoord, tile.yCoord, tile.zCoord) != tile) return false;
}
return true;
}
public static boolean isModLoaded(final String modid) {
try {
// if this fails for some reason, try the other method.
@@ -1490,6 +1544,7 @@ public class Platform {
} else if (target instanceof ITileStorageMonitorable) {
return 0;
} else if (target instanceof TileEntityChest chest) {
if (!isInventoryTileLoaded(chest)) return 0;
chest.checkForAdjacentChests();
if (chest.adjacentChestZNeg != null) {
hash ^= chest.adjacentChestZNeg.hashCode();