Fix/inconsistent network read order (#867)

Co-authored-by: Martin Robertz <dream-master@gmx.net>
This commit is contained in:
hiroschoandMartin Robertz authored and GitHub committed 2025-10-05 15:17:31 +02:00
1 parent 1d0303d030
commit 6a4ee8c6b0
9 files changed
+425 -82

No files matched your search

@@ -99,4 +99,14 @@ public interface IMEInventoryHandler<StackType extends IAEStack> extends IMEInve
return null;
}
/**
* Find the network inventory (if any) that is being accessed. This should only return a value for inventories that
* connect two ME networks (= storage bus on interface).
*
* @return the accessed network inventory
*/
default IMENetworkInventory<StackType> getExternalNetworkInventory() {
return null;
}
}
@@ -0,0 +1,19 @@
/*
* The MIT License (MIT) Copyright (c) 2013 AlgorithmX2 Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions: The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package appeng.api.storage;
import appeng.api.storage.data.IAEStack;
public interface IMENetworkInventory<StackType extends IAEStack> extends IMEInventoryHandler<StackType> {
}
+11
View File
@@ -32,6 +32,7 @@ import appeng.api.networking.security.BaseActionSource;
import appeng.api.storage.IMEInventoryHandler;
import appeng.api.storage.IMEMonitor;
import appeng.api.storage.IMEMonitorHandlerReceiver;
import appeng.api.storage.IMENetworkInventory;
import appeng.api.storage.StorageChannel;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IItemList;
@@ -165,6 +166,16 @@ public class NetworkMonitor<T extends IAEStack<T>> implements IMEMonitor<T> {
return this.getHandler().validForPass(i);
}
@Override
@SuppressWarnings("unchecked")
public IMENetworkInventory<T> getExternalNetworkInventory() {
IMEInventoryHandler<T> handler = this.getHandler();
if (handler instanceof IMENetworkInventory<?>networkInventory) {
return (IMENetworkInventory<T>) networkInventory;
}
return handler.getExternalNetworkInventory();
}
@Nullable
@SuppressWarnings("unchecked")
public IMEInventoryHandler<T> getHandler() {
@@ -21,6 +21,7 @@ import appeng.api.config.StorageFilter;
import appeng.api.networking.security.BaseActionSource;
import appeng.api.storage.IMEInventory;
import appeng.api.storage.IMEInventoryHandler;
import appeng.api.storage.IMENetworkInventory;
import appeng.api.storage.StorageChannel;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IItemList;
@@ -249,6 +250,15 @@ public class MEInventoryHandler<T extends IAEStack<T>> implements IMEInventoryHa
return isSticky || this.internal.getSticky();
}
@Override
@SuppressWarnings("unchecked")
public IMENetworkInventory<T> getExternalNetworkInventory() {
if (internal instanceof IMENetworkInventory<?>networkInventory) {
return (IMENetworkInventory<T>) networkInventory;
}
return this.internal.getExternalNetworkInventory();
}
@Override
public IMEInventory<T> getInternal() {
return this.internal;
@@ -77,8 +77,12 @@ public class MEMonitorPassThrough<T extends IAEStack<T>> extends MEPassThrough<T
if (out instanceof ItemFilterList) {
return super.getAvailableItems(out, iterator);
}
super.getAvailableItems(new ItemListIgnoreCrafting(out), iterator);
return out;
IItemList<T> ret = super.getAvailableItems(new ItemListIgnoreCrafting(out), iterator);
if (ret instanceof ItemListIgnoreCrafting) {
return out;
} else {
return ret;
}
}
@Override
@@ -18,6 +18,7 @@ import appeng.api.networking.IGrid;
import appeng.api.networking.security.BaseActionSource;
import appeng.api.storage.IMEInventory;
import appeng.api.storage.IMEInventoryHandler;
import appeng.api.storage.IMENetworkInventory;
import appeng.api.storage.StorageChannel;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IItemList;
@@ -109,6 +110,18 @@ public class MEPassThrough<T extends IAEStack<T>> implements IMEInventoryHandler
return true;
}
@Override
@SuppressWarnings("unchecked")
public IMENetworkInventory<T> getExternalNetworkInventory() {
if (internal instanceof IMENetworkInventory<?>networkInventory) {
return (IMENetworkInventory<T>) networkInventory;
}
if (internal instanceof IMEInventoryHandler<?>inventoryHandler) {
return (IMENetworkInventory<T>) inventoryHandler.getExternalNetworkInventory();
}
return null;
}
StorageChannel getWrappedChannel() {
return this.wrappedChannel;
}
@@ -28,13 +28,16 @@ import appeng.api.networking.security.ISecurityGrid;
import appeng.api.networking.security.MachineSource;
import appeng.api.networking.security.PlayerSource;
import appeng.api.storage.IMEInventoryHandler;
import appeng.api.storage.IMENetworkInventory;
import appeng.api.storage.StorageChannel;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IItemList;
import appeng.me.cache.SecurityCache;
import appeng.util.SortedArrayList;
import appeng.util.inv.ItemListIgnoreCrafting;
import appeng.util.item.NetworkItemList;
public class NetworkInventoryHandler<T extends IAEStack<T>> implements IMEInventoryHandler<T> {
public class NetworkInventoryHandler<T extends IAEStack<T>> implements IMENetworkInventory<T> {
private static final ThreadLocal<LinkedList> DEPTH_MOD = new ThreadLocal<>();
private static final ThreadLocal<LinkedList> DEPTH_SIM = new ThreadLocal<>();
@@ -77,6 +80,7 @@ public class NetworkInventoryHandler<T extends IAEStack<T>> implements IMEInvent
private final SecurityCache security;
private final List<IMEInventoryHandler<T>> priorityInventory;
private int myPass = 0;
private NetworkItemList<T> iterationItems = null;
public NetworkInventoryHandler(final StorageChannel chan, final SecurityCache security) {
this.myChannel = chan;
@@ -289,51 +293,97 @@ public class NetworkInventoryHandler<T extends IAEStack<T>> implements IMEInvent
}
@Override
@SuppressWarnings("unchecked")
public IItemList<T> getAvailableItems(IItemList out, int iteration) {
if (this.diveIteration(this, Actionable.SIMULATE, iteration)) {
return out;
return this.iterationItems == null ? out : this.iterationItems;
}
final boolean isIgnoreCrafting = out instanceof ItemListIgnoreCrafting;
final boolean isSource = this.getDepth(Actionable.SIMULATE).size() == 1;
final NetworkItemList<T> networkItemList = new NetworkItemList<>(
this,
() -> (IItemList<T>) getChannel().createList());
this.iterationItems = networkItemList;
final IItemList<T> currentNetworkItemList = isIgnoreCrafting
? new ItemListIgnoreCrafting<>((IItemList<T>) getChannel().createList())
: (IItemList<T>) getChannel().createList();
final List<IMEInventoryHandler<T>> priorityInventory = this.priorityInventory;
final int size = priorityInventory.size();
for (int i = 0; i < size; i++) {
out = priorityInventory.get(i).getAvailableItems(out, iteration);
final IMEInventoryHandler<T> inv = priorityInventory.get(i);
final IMENetworkInventory<T> externalNetworkInventory = inv.getExternalNetworkInventory();
if (externalNetworkInventory == this) {
continue; // ignore any attempts to read self
}
final IItemList<T> passedInList = getChannel().createList();
final IItemList<T> passedOutList = inv.getAvailableItems(passedInList, iteration);
if (externalNetworkInventory != null && passedOutList instanceof NetworkItemList) {
networkItemList.addNetworkItems(externalNetworkInventory, passedOutList);
} else {
for (T item : passedOutList) {
currentNetworkItemList.add(item);
}
}
}
networkItemList.addNetworkItems(this, currentNetworkItemList);
this.surface(this, Actionable.SIMULATE);
return out;
// we're partially violating the api by making the returned list a different one from the provided one, however
// when we're done with the network inventory scan we fulfill our api contract again
return isSource ? networkItemList.buildFinalItemList(out) : networkItemList;
}
@Override
public T getAvailableItem(@Nonnull T request, int iteration) {
long count = 0;
if (this.diveIteration(this, Actionable.SIMULATE, iteration)) {
return null;
}
final List<IMEInventoryHandler<T>> priorityInventory = this.priorityInventory;
final int size = priorityInventory.size();
boolean readsFromOtherNetwork = false;
for (int i = 0; i < size; i++) {
IMEInventoryHandler<T> j = priorityInventory.get(i);
final T stack = j.getAvailableItem(request, iteration);
if (stack != null && stack.getStackSize() > 0) {
count += stack.getStackSize();
if (count < 0) {
// overflow
count = Long.MAX_VALUE;
if (priorityInventory.get(i).getExternalNetworkInventory() != null) {
readsFromOtherNetwork = true;
break;
}
}
if (readsFromOtherNetwork) {
final T stack = this.getAvailableItems(getChannel().createList(), iteration).findPrecise(request);
count = addStackCount(stack, count);
} else {
if (this.diveIteration(this, Actionable.SIMULATE, iteration)) {
return null;
}
for (int i = 0; i < size; i++) {
IMEInventoryHandler<T> j = priorityInventory.get(i);
final T stack = j.getAvailableItem(request, iteration);
count = addStackCount(stack, count);
if (count == Long.MAX_VALUE) {
break;
}
}
this.surface(this, Actionable.SIMULATE);
}
this.surface(this, Actionable.SIMULATE);
return count == 0 ? null : request.copy().setStackSize(count);
}
private long addStackCount(T stack, long count) {
if (stack != null && stack.getStackSize() > 0) {
count += stack.getStackSize();
if (count < 0) {
// overflow
count = Long.MAX_VALUE;
}
}
return count;
}
private boolean diveIteration(final NetworkInventoryHandler<T> networkInventoryHandler, final Actionable type,
int iteration) {
if (iteration == this.myPass) {
@@ -407,4 +457,5 @@ public class NetworkInventoryHandler<T extends IAEStack<T>> implements IMEInvent
public boolean validForPass(final int i) {
return true;
}
}
@@ -1,22 +1,16 @@
package appeng.me.storage;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Predicate;
import appeng.api.storage.IMEInventory;
import appeng.api.storage.StorageChannel;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IItemList;
import appeng.me.cache.NetworkMonitor;
import appeng.util.item.ItemFilterList;
import appeng.util.item.NetworkItemList;
public class StorageBusInventoryHandler<T extends IAEStack<T>> extends MEInventoryHandler<T> {
private static final ThreadLocal<Map<Integer, Map<NetworkInventoryHandler<?>, IItemList<?>>>> networkItemsForIteration = new ThreadLocal<>();
public StorageBusInventoryHandler(IMEInventory<T> i, StorageChannel channel) {
super(i, channel);
}
@@ -39,65 +33,24 @@ public class StorageBusInventoryHandler<T extends IAEStack<T>> extends MEInvento
@Override
protected IItemList<T> filterAvailableItems(IItemList<T> out, int iteration) {
Predicate<T> filterCondition = this.getExtractFilterCondition();
getAvailableItems(out, iteration, filterCondition);
return out;
return getAvailableItems(out, iteration, filterCondition);
}
@SuppressWarnings("unchecked")
private IItemList<T> getAvailableItems(IItemList<T> out, int iteration, Predicate<T> filterCondition) {
final IItemList<T> allAvailableItems = this.getAllAvailableItems(iteration);
Iterator<T> it = allAvailableItems.iterator();
while (it.hasNext()) {
T items = it.next();
if (filterCondition.test(items)) {
out.add(items);
// have to remove the item otherwise it could be counted double
it.remove();
}
}
return out;
}
private IItemList<T> getAllAvailableItems(int iteration) {
NetworkInventoryHandler<T> networkInventoryHandler = getNetworkInventoryHandler();
if (networkInventoryHandler == null) {
return this.getInternal()
.getAvailableItems((IItemList<T>) this.getInternal().getChannel().createList(), iteration);
}
Map<Integer, Map<NetworkInventoryHandler<?>, IItemList<?>>> s = networkItemsForIteration.get();
if (s != null && !s.containsKey(iteration)) {
s = null;
}
if (s == null) {
s = Collections.singletonMap(iteration, new IdentityHashMap<>());
networkItemsForIteration.set(s);
}
Map<NetworkInventoryHandler<?>, IItemList<?>> networkInventoryItems = s.get(iteration);
if (!networkInventoryItems.containsKey(networkInventoryHandler)) {
IItemList<T> allAvailableItems = this.getInternal()
.getAvailableItems((IItemList<T>) this.getInternal().getChannel().createList(), iteration);
networkInventoryItems.put(networkInventoryHandler, allAvailableItems);
}
return (IItemList<T>) networkInventoryItems.get(networkInventoryHandler);
}
/**
* Find the NetworkInventoryHandler for this storage bus
*/
private NetworkInventoryHandler<T> getNetworkInventoryHandler() {
return (NetworkInventoryHandler<T>) findNetworkInventoryHandler(this.getInternal());
}
private NetworkInventoryHandler<?> findNetworkInventoryHandler(IMEInventory<?> inventory) {
if (inventory instanceof MEPassThrough<?>passThrough) {
return findNetworkInventoryHandler(passThrough.getInternal());
} else if (inventory instanceof NetworkMonitor<?>networkMonitor) {
return findNetworkInventoryHandler(networkMonitor.getHandler());
} else if (inventory instanceof NetworkInventoryHandler<?>networkInventoryHandler) {
return networkInventoryHandler;
final IItemList<T> availableItems = this.getInternal()
.getAvailableItems((IItemList<T>) getChannel().createList(), iteration);
if (availableItems instanceof NetworkItemList) {
NetworkItemList<T> networkItemList = new NetworkItemList<>((NetworkItemList<T>) availableItems);
networkItemList.addFilter(filterCondition);
return networkItemList;
} else {
return null;
for (T items : availableItems) {
if (filterCondition.test(items)) {
out.add(items);
}
}
return out;
}
}
}
@@ -0,0 +1,272 @@
/*
* This file is part of Applied Energistics 2. Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved. Applied
* Energistics 2 is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version. Applied Energistics 2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
* Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
* Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.util.item;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import appeng.api.config.FuzzyMode;
import appeng.api.storage.IMENetworkInventory;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IItemList;
/**
* A NetworkItemList contains one or more IItemLists from different networks. These IItemLists can themselves be
* NetworkItemLists. This allows us to filter items from multiple, or same networks with different filters while also
* including all their subnetworks and filters.
*
* @param <T>
*/
public class NetworkItemList<T extends IAEStack> implements IItemList<T> {
/**
* The network this list was created for.
*/
private final IMENetworkInventory<T> network;
/**
* The networks and their items that were read from this {@code network}. For non-network inventories the
* network-key is equal to {@code network}.
*/
private final Map<IMENetworkInventory<T>, IItemList<T>> networkItemLists;
private final Supplier<IItemList<T>> newItemListSupplier;
private final List<Predicate<T>> predicates;
public NetworkItemList(IMENetworkInventory<T> network, Supplier<IItemList<T>> newItemListSupplier) {
this.network = network;
this.networkItemLists = new HashMap<>();
this.predicates = new ArrayList<>();
this.newItemListSupplier = newItemListSupplier;
}
/**
* Creates a shallow copy of a network item list. Filters are not copied.
*
* @param networkItemList the list to copy from
*/
public NetworkItemList(NetworkItemList<T> networkItemList) {
this.network = networkItemList.network;
this.predicates = new ArrayList<>();
this.networkItemLists = networkItemList.networkItemLists;
this.newItemListSupplier = networkItemList.newItemListSupplier;
}
public void addNetworkItems(IMENetworkInventory<T> network, IItemList<T> itemList) {
IItemList<T> l = networkItemLists.get(network);
if (l instanceof NetworkItemList) {
if (itemList instanceof NetworkItemList) {
// since the network is the same we combine the predicates
for (Predicate<T> filter : ((NetworkItemList<T>) itemList).predicates) {
((NetworkItemList<T>) l).addFilter(filter);
}
return;
} else {
throw new RuntimeException(
"This NetworkItemList already contains a NetworkItemList for the provided network and cannot replace it with a non-NetworkItemList");
}
} else if (l != null) {
throw new RuntimeException(
"This NetworkItemList already contains a non-NetworkItemList for the provided network and cannot replace it");
}
networkItemLists.put(network, itemList);
}
private Stream<NetworkItemStack<T>> getNetworkItemStackStream(final Set<IMENetworkInventory<T>> visitedNetworks) {
return networkItemLists.entrySet().stream()
// equivalent to a worse performing mapMulti
.flatMap(entry -> {
if (entry.getValue() instanceof NetworkItemList) {
if (visitedNetworks.contains(entry.getKey())) {
return Stream.empty();
}
final Set<IMENetworkInventory<T>> localVisitedNetworks = new HashSet<>(visitedNetworks);
localVisitedNetworks.add(entry.getKey());
return ((NetworkItemList<T>) entry.getValue())
.getFilteredNetworkItemStackStream(Collections.unmodifiableSet(localVisitedNetworks));
} else {
List<NetworkItemStack<T>> buffer = new ArrayList<>();
StreamSupport.stream(entry.getValue().spliterator(), false)
.forEach(item -> buffer.add(new NetworkItemStack<>(entry.getKey(), item)));
return buffer.stream();
}
});
}
private Stream<NetworkItemStack<T>> filter(Stream<NetworkItemStack<T>> stream) {
Predicate<T> predicate = buildFilter();
if (predicate == null) return stream;
else return stream.filter(e -> predicate.test(e.getItemStack()));
}
public Stream<T> getItems() {
return getFilteredNetworkItemStackStream().distinct().map(NetworkItemStack::getItemStack);
}
private Stream<NetworkItemStack<T>> getFilteredNetworkItemStackStream() {
Set<IMENetworkInventory<T>> visitedNetworks = new HashSet<>();
visitedNetworks.add(this.network);
return filter(getNetworkItemStackStream(Collections.unmodifiableSet(visitedNetworks)));
}
private Stream<NetworkItemStack<T>> getFilteredNetworkItemStackStream(
final Set<IMENetworkInventory<T>> visitedNetworks) {
return filter(getNetworkItemStackStream(visitedNetworks));
}
public void addFilter(Predicate<T> filter) {
predicates.add(filter);
}
private Predicate<T> buildFilter() {
Predicate<T> predicate = null;
for (Predicate<T> filter : predicates) {
if (predicate == null) {
predicate = filter;
} else {
predicate = predicate.or(filter);
}
}
return predicate;
}
/**
* Writes all available items to a new combined list.
*
* @return the available items in the network as a combined IItemList
*/
public IItemList<T> buildFinalItemList() {
IItemList<T> out = newItemListSupplier.get();
return buildFinalItemList(out);
}
/**
* Writes all available items to the provided list.
*
* @param out the IItemList the results will be written to
* @return returns same list that was passed in, is passed out
*/
public IItemList<T> buildFinalItemList(IItemList<T> out) {
getItems().forEach(out::add);
return out;
}
@Override
public void addStorage(T option) {
throw new UnsupportedOperationException();
}
@Override
public void addCrafting(T option) {
throw new UnsupportedOperationException();
}
@Override
public void addRequestable(T option) {
throw new UnsupportedOperationException();
}
@Override
public T getFirstItem() {
return getItems().findFirst().orElse(null);
}
@Override
public int size() {
return (int) getItems().count();
}
@Override
public Iterator<T> iterator() {
return getItems().iterator();
}
@Override
public void resetStatus() {
for (IItemList<T> list : networkItemLists.values()) {
list.resetStatus();
}
}
@Override
public void add(T option) {
throw new UnsupportedOperationException();
}
@Override
public T findPrecise(T i) {
return buildFinalItemList().findPrecise(i);
}
@Override
public Collection<T> findFuzzy(final T input, final FuzzyMode fuzzy) {
return buildFinalItemList().findFuzzy(input, fuzzy);
}
@Override
public boolean isEmpty() {
return !getItems().findAny().isPresent();
}
@Override
public byte getStackType() {
IItemList<T> list = networkItemLists.values().stream().findAny().orElse(null);
return list == null ? LIST_NUll : list.getStackType();
}
private class NetworkItemStack<U extends IAEStack> {
private final IMENetworkInventory<U> networkInventory;
private final U itemStack;
public NetworkItemStack(IMENetworkInventory<U> networkInventory, U itemStack) {
this.networkInventory = networkInventory;
this.itemStack = itemStack;
}
public IMENetworkInventory<U> getNetworkInventory() {
return networkInventory;
}
public U getItemStack() {
return itemStack;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NetworkItemStack)) return false;
NetworkItemStack<?> that = (NetworkItemStack<?>) o;
// only use reference equality
return networkInventory == that.networkInventory && itemStack == that.itemStack;
}
@Override
public int hashCode() {
final int prime = 31;
int hash = 1;
// only use identity for hash
hash = hash * prime + System.identityHashCode(networkInventory);
hash = hash * prime + System.identityHashCode(itemStack);
return hash;
}
}
}