diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/StockingHatchList.java b/src/main/java/com/gregtechceu/gtceu/api/misc/StockingHatchList.java new file mode 100644 index 00000000000..279704c4e7c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/StockingHatchList.java @@ -0,0 +1,126 @@ +package com.gregtechceu.gtceu.api.misc; + +import appeng.api.stacks.AEKey; +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +// List of AEStacks, always sorted largest to smallest. +public class StockingHatchList implements Iterable { + + private final List list; + private final int capacity; + + public StockingHatchList(int capacity) { + this.capacity = capacity; + this.list = new ArrayList<>(capacity); + } + + public boolean insert(AEKey key, long amount) { + for (int i = 0; i < list.size(); i++) { + AEStack stack = list.get(i); + if (!key.equals(stack.key)) { + continue; + } + if (amount > stack.amount) { + // stack grew, resort the area before it + stack.amount = amount; + if (i > 0 && list.get(i - 1).amount < amount) { + for (int j = 0; j < i; j++) { + if (amount > list.get(j).amount) { + list.add(j, stack); + list.remove(i + 1); // remove old stack which is now shifted + return true; + } + } + } + // didn't need to move, already updated in place + return true; + } else if (amount < stack.amount) { + // stack shrunk, resort the area after it + stack.amount = amount; + if (i + 1 < list.size() && list.get(i + 1).amount > amount) { + for (int j = i + 1; j < list.size(); j++) { + if (amount > list.get(j).amount) { + list.add(j, stack); + list.remove(i); // remove old stack + return true; + } + } + } + // didn't need to move, already updated in place + return true; + } + // no change + return false; + } + // not enough space in list + if (list.size() >= capacity) { + return false; + } + // unseen item + for (int i = 0; i < list.size(); i++) { + AEStack stack = list.get(i); + if (amount > stack.amount) { + list.add(i, new AEStack(key, amount)); + return true; + } + } + list.add(new AEStack(key, amount)); + return true; + } + + public boolean remove(AEKey key) { + for (int i = 0; i < list.size(); i++) { + AEStack stack = list.get(i); + if (stack.key.equals(key)) { + list.remove(i); + return true; + } + } + return false; + } + + public boolean contains(AEKey what) { + for (var stack : list) { + if (stack.getKey().equals(what)) return true; + } + return false; + } + + @Override + public @NotNull Iterator iterator() { + return list.iterator(); + } + + public int size() { + return list.size(); + } + + public void clear() { + list.clear(); + } + + public StockingHatchList.AEStack get(int index) { + return list.get(index); + } + + public static class AEStack { + + @Getter + @Setter + public @NotNull AEKey key; + @Getter + @Setter + public long amount; + + public AEStack(@NotNull AEKey key, long amount) { + this.key = key; + this.amount = amount; + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEStockingBusPartMachine.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEStockingBusPartMachine.java index 288dac498f5..07c9ae03143 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEStockingBusPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEStockingBusPartMachine.java @@ -8,6 +8,7 @@ import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiController; import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiPart; import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler; +import com.gregtechceu.gtceu.api.misc.StockingHatchList; import com.gregtechceu.gtceu.common.item.IntCircuitBehaviour; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.integration.ae2.machine.feature.multiblock.IMEStockingPart; @@ -33,6 +34,9 @@ import appeng.api.config.Actionable; import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IStackWatcher; +import appeng.api.networking.storage.IStorageWatcherNode; import appeng.api.stacks.AEItemKey; import appeng.api.stacks.AEKey; import appeng.api.stacks.GenericStack; @@ -42,15 +46,13 @@ import lombok.Setter; import org.jetbrains.annotations.Nullable; -import java.util.Comparator; -import java.util.PriorityQueue; import java.util.function.Predicate; import javax.annotation.ParametersAreNonnullByDefault; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -public class MEStockingBusPartMachine extends MEInputBusPartMachine implements IMEStockingPart { +public class MEStockingBusPartMachine extends MEInputBusPartMachine implements IMEStockingPart, IStorageWatcherNode { protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( MEStockingBusPartMachine.class, MEInputBusPartMachine.MANAGED_FIELD_HOLDER); @@ -74,9 +76,15 @@ public class MEStockingBusPartMachine extends MEInputBusPartMachine implements I @Setter private Predicate autoPullTest; + private IStackWatcher watcher; + + private StockingHatchList topItems = new StockingHatchList(CONFIG_SIZE); + private boolean pendingWatcherSync; + public MEStockingBusPartMachine(IMachineBlockEntity holder, Object... args) { super(holder, args); this.autoPullTest = $ -> false; + this.nodeHolder.getMainNode().addService(IStorageWatcherNode.class, this); } ///////////////////////////////// @@ -95,6 +103,14 @@ public void removedFromController(IMultiController controller) { super.removedFromController(controller); } + @Override + public void onMainNodeStateChanged(IGridNodeListener.State reason) { + super.onMainNodeStateChanged(reason); + if (autoPull) { + refreshList(); + } + } + @Override protected NotifiableItemStackHandler createInventory(Object... args) { this.aeItemHandler = new ExportOnlyAEStockingItemList(this, CONFIG_SIZE); @@ -113,36 +129,80 @@ public ManagedFieldHolder getFieldHolder() { @Override public void autoIO() { super.autoIO(); - if (ticksPerCycle == 0) ticksPerCycle = ConfigHolder.INSTANCE.compat.ae2.updateIntervals; // Emergency Check to - // Avoid Crash loops. + if (ticksPerCycle == 0) { + // Emergency Check to avoid Crash loops. + ticksPerCycle = ConfigHolder.INSTANCE.compat.ae2.updateIntervals; + } if (getOffsetTimer() % ticksPerCycle == 0) { - if (autoPull) { - refreshList(); + syncWatchedStacks(); + } + if (pendingWatcherSync) { + pendingWatcherSync = false; + syncWatchedStacks(); + } + } + + // Refreshes what things are being watched + private void syncWatchedStacks() { + if (watcher == null) return; + watcher.reset(); + if (autoPull) { + watcher.setWatchAll(true); + } else { + watcher.setWatchAll(false); + for (ExportOnlyAEItemSlot slot : this.aeItemHandler.getInventory()) { + if (slot.getConfig() == null) continue; + watcher.add(slot.getConfig().what()); } - syncME(); } } @Override - protected void syncME() { - // Update the visual display for the fake items. This also is important for the item handler's - // getStackInSlot() method, as it uses the cached items set here. - MEStorage networkInv = this.getMainNode().getGrid().getStorageService().getInventory(); - for (ExportOnlyAEItemSlot slot : this.aeItemHandler.getInventory()) { - var config = slot.getConfig(); - if (config != null) { - // Try to fill the slot - var key = config.what(); - long extracted = networkInv.extract(key, Long.MAX_VALUE, Actionable.SIMULATE, actionSource); - if (extracted >= minStackSize) { - slot.setStock(new GenericStack(key, extracted)); + public void updateWatcher(IStackWatcher watcher) { + this.watcher = watcher; + this.syncWatchedStacks(); + } + + @Override + public void onStackChange(AEKey what, long amount) { + if (!(what instanceof AEItemKey itemKey)) return; + if (autoPull) { + if (autoPullTest != null && !autoPullTest.test(new GenericStack(itemKey, amount))) { + topItems.remove(what); + } else { + if (!topItems.insert(what, amount)) return; + } + + syncListToHandler(); + } else { + for (ExportOnlyAEItemSlot slot : this.aeItemHandler.getInventory()) { + if (slot.getConfig() == null) { + slot.setStock(null); continue; } + AEKey key = slot.getConfig().what(); + if (key.equals(what)) { + if (amount > 0) { + slot.setStock(new GenericStack(key, amount)); + } else { + slot.setStock(null); + } + } } - slot.setStock(null); } } + private void syncListToHandler() { + int index; + for (index = 0; index < topItems.size(); index++) { + var entry = topItems.get(index); + var slot = this.aeItemHandler.getInventory()[index]; + slot.setConfig(new GenericStack(entry.getKey(), 1)); + slot.setStock(new GenericStack(entry.getKey(), entry.getAmount())); + } + aeItemHandler.clearInventory(index); + } + @Override public void attachSideTabs(TabsWidget sideTabs) { sideTabs.setMainTab(this); // removes the cover configurator, it's pointless and clashes with layout. @@ -217,54 +277,26 @@ private void refreshList() { MEStorage networkStorage = grid.getStorageService().getInventory(); var counter = networkStorage.getAvailableStacks(); - // Use a PriorityQueue to sort the stacks on size, take the first CONFIG_SIZE - // biggest stacks. - PriorityQueue> topItems = new PriorityQueue<>( - Comparator.comparingLong(Object2LongMap.Entry::getLongValue)); + topItems.clear(); for (Object2LongMap.Entry entry : counter) { - long amount = entry.getLongValue(); AEKey what = entry.getKey(); - - if (amount <= 0) continue; if (!(what instanceof AEItemKey itemKey)) continue; - - long request = networkStorage.extract(what, amount, Actionable.SIMULATE, actionSource); - if (request == 0) continue; - - // Ensure that it is valid to configure with this stack - if (autoPullTest != null && !autoPullTest.test(new GenericStack(itemKey, amount))) continue; - if (amount >= minStackSize) { - if (topItems.size() < CONFIG_SIZE) { - topItems.offer(entry); - } else if (amount > topItems.peek().getLongValue()) { - topItems.poll(); - topItems.offer(entry); - } - } - } - - // Now, topItems is a PQ with CONFIG_SIZE highest amount items in the system. - int index; - int itemAmount = topItems.size(); - for (index = 0; index < CONFIG_SIZE; index++) { - if (topItems.isEmpty()) break; - Object2LongMap.Entry entry = topItems.poll(); - - AEKey what = entry.getKey(); long amount = entry.getLongValue(); - // If we get here, the item has already been checked by the PQ. - long request = networkStorage.extract(what, amount, Actionable.SIMULATE, actionSource); + if (autoPullTest != null && !autoPullTest.test(new GenericStack(what, amount))) continue; - // Since we want our items to be displayed from highest to lowest, but poll() returns - // the lowest first, we fill in the slots starting at itemAmount-1 - var slot = this.aeItemHandler.getInventory()[itemAmount - index - 1]; - slot.setConfig(new GenericStack(what, 1)); - slot.setStock(new GenericStack(what, request)); + topItems.insert(what, amount); } - aeItemHandler.clearInventory(index); + syncListToHandler(); + } + + // Expensive, use sparingly. + private long getAmountInNetwork(AEKey key) { + MEStorage networkInv = this.getMainNode().getGrid().getStorageService().getInventory(); + long extracted = networkInv.extract(key, Long.MAX_VALUE, Actionable.SIMULATE, actionSource); + return extracted; } /////////////////////////////// @@ -363,6 +395,21 @@ public ExportOnlyAEStockingItemSlot(@Nullable GenericStack config, @Nullable Gen super(config, stock); } + @Override + public void setConfig(@Nullable GenericStack config) { + var oldConfig = this.config; + this.config = config; + if (config == null) { + this.setStock(null); + } else { + if (!config.equals(oldConfig)) { + // Refresh to get stack amount from AE2 + this.setStock(new GenericStack(config.what(), getAmountInNetwork(config.what()))); + pendingWatcherSync = true; + } + } + } + @Override public ItemStack extractItem(int slot, int amount, boolean simulate) { if (slot == 0 && this.stock != null) { diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEStockingHatchPartMachine.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEStockingHatchPartMachine.java index 8b07ee7ad05..4fc2930c095 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEStockingHatchPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEStockingHatchPartMachine.java @@ -8,6 +8,7 @@ import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiController; import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiPart; import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank; +import com.gregtechceu.gtceu.api.misc.StockingHatchList; import com.gregtechceu.gtceu.common.item.IntCircuitBehaviour; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.integration.ae2.machine.feature.multiblock.IMEStockingPart; @@ -34,6 +35,9 @@ import appeng.api.config.Actionable; import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IStackWatcher; +import appeng.api.networking.storage.IStorageWatcherNode; import appeng.api.stacks.AEFluidKey; import appeng.api.stacks.AEKey; import appeng.api.stacks.GenericStack; @@ -43,15 +47,14 @@ import lombok.Setter; import org.jetbrains.annotations.Nullable; -import java.util.Comparator; -import java.util.PriorityQueue; import java.util.function.Predicate; import javax.annotation.ParametersAreNonnullByDefault; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -public class MEStockingHatchPartMachine extends MEInputHatchPartMachine implements IMEStockingPart { +public class MEStockingHatchPartMachine extends MEInputHatchPartMachine + implements IMEStockingPart, IStorageWatcherNode { protected static final ManagedFieldHolder MANAGED_FIELD_HOLDER = new ManagedFieldHolder( MEStockingHatchPartMachine.class, MEInputHatchPartMachine.MANAGED_FIELD_HOLDER); @@ -78,6 +81,11 @@ public class MEStockingHatchPartMachine extends MEInputHatchPartMachine implemen @Setter private Predicate autoPullTest; + private IStackWatcher watcher; + + private StockingHatchList topFluids = new StockingHatchList(CONFIG_SIZE); + private boolean pendingWatcherSync; + public MEStockingHatchPartMachine(IMachineBlockEntity holder, Object... args) { super(holder, args); this.autoPullTest = $ -> false; @@ -99,6 +107,14 @@ public void removedFromController(IMultiController controller) { super.removedFromController(controller); } + @Override + public void onMainNodeStateChanged(IGridNodeListener.State reason) { + super.onMainNodeStateChanged(reason); + if (autoPull) { + refreshList(); + } + } + @Override protected NotifiableFluidTank createTank(int initialCapacity, int slots, Object... args) { this.aeFluidHandler = new ExportOnlyAEStockingFluidList(this, CONFIG_SIZE); @@ -117,34 +133,80 @@ public ManagedFieldHolder getFieldHolder() { @Override public void autoIO() { super.autoIO(); - if (ticksPerCycle == 0) ticksPerCycle = ConfigHolder.INSTANCE.compat.ae2.updateIntervals; // Emergency Check to - // Avoid Crash loops. + if (ticksPerCycle == 0) { + // Emergency Check to avoid Crash loops. + ticksPerCycle = ConfigHolder.INSTANCE.compat.ae2.updateIntervals; + } if (getOffsetTimer() % ticksPerCycle == 0) { - if (autoPull) { - refreshList(); + syncWatchedStacks(); + } + if (pendingWatcherSync) { + pendingWatcherSync = false; + syncWatchedStacks(); + } + } + + // Refreshes what things are being watched + private void syncWatchedStacks() { + if (watcher == null) return; + watcher.reset(); + if (autoPull) { + watcher.setWatchAll(true); + } else { + watcher.setWatchAll(false); + for (ExportOnlyAEFluidSlot slot : this.aeFluidHandler.getInventory()) { + if (slot.getConfig() == null) continue; + watcher.add(slot.getConfig().what()); } - syncME(); } } @Override - protected void syncME() { - MEStorage networkInv = this.getMainNode().getGrid().getStorageService().getInventory(); - for (ExportOnlyAEFluidSlot slot : aeFluidHandler.getInventory()) { - var config = slot.getConfig(); - if (config != null) { - // Try to fill the slot - var key = config.what(); - long extracted = networkInv.extract(key, Long.MAX_VALUE, Actionable.SIMULATE, actionSource); - if (extracted >= minStackSize) { - slot.setStock(new GenericStack(key, extracted)); + public void updateWatcher(IStackWatcher watcher) { + this.watcher = watcher; + this.syncME(); + } + + @Override + public void onStackChange(AEKey what, long amount) { + if (!(what instanceof AEFluidKey itemKey)) return; + if (autoPull) { + if (autoPullTest != null && !autoPullTest.test(new GenericStack(itemKey, amount))) { + topFluids.remove(what); + } else { + if (!topFluids.insert(what, amount)) return; + } + + syncListToHandler(); + } else { + for (ExportOnlyAEFluidSlot slot : this.aeFluidHandler.getInventory()) { + if (slot.getConfig() == null) { + slot.setStock(null); continue; } + AEKey key = slot.getConfig().what(); + if (key.equals(what)) { + if (amount > 0) { + slot.setStock(new GenericStack(key, amount)); + } else { + slot.setStock(null); + } + } } - slot.setStock(null); } } + private void syncListToHandler() { + int index; + for (index = 0; index < topFluids.size(); index++) { + var entry = topFluids.get(index); + var slot = this.aeFluidHandler.getInventory()[index]; + slot.setConfig(new GenericStack(entry.getKey(), 1)); + slot.setStock(new GenericStack(entry.getKey(), entry.getAmount())); + } + aeFluidHandler.clearInventory(index); + } + @Override public void attachSideTabs(TabsWidget sideTabs) { sideTabs.setMainTab(this); // removes the cover configurator, it's pointless and clashes with layout. @@ -201,53 +263,26 @@ private void refreshList() { MEStorage networkStorage = grid.getStorageService().getInventory(); var counter = networkStorage.getAvailableStacks(); - // Use a PriorityQueue to sort the stacks on size, take the first CONFIG_SIZE - // biggest stacks. - PriorityQueue> topFluids = new PriorityQueue<>( - Comparator.comparingLong(Object2LongMap.Entry::getLongValue)); + topFluids.clear(); for (Object2LongMap.Entry entry : counter) { - long amount = entry.getLongValue(); AEKey what = entry.getKey(); - - if (amount <= 0) continue; if (!(what instanceof AEFluidKey fluidKey)) continue; - - long request = networkStorage.extract(what, amount, Actionable.SIMULATE, actionSource); - if (request == 0) continue; - - // Ensure that it is valid to configure with this stack - if (autoPullTest != null && !autoPullTest.test(new GenericStack(fluidKey, amount))) continue; - if (amount >= minStackSize) { - if (topFluids.size() < CONFIG_SIZE) { - topFluids.offer(entry); - } else if (amount > topFluids.peek().getLongValue()) { - topFluids.poll(); - topFluids.offer(entry); - } - } - } - - // Now, topFluids is a PQ with CONFIG_SIZE highest amount fluids in the system. - int index; - int fluidAmount = topFluids.size(); - for (index = 0; index < CONFIG_SIZE; index++) { - if (topFluids.isEmpty()) break; - Object2LongMap.Entry entry = topFluids.poll(); - AEKey what = entry.getKey(); long amount = entry.getLongValue(); - // If we get here, the fluid has already been checked by the PQ. - long request = networkStorage.extract(what, amount, Actionable.SIMULATE, actionSource); + if (autoPullTest != null && !autoPullTest.test(new GenericStack(what, amount))) continue; - // Since we want our fluids to be displayed from highest to lowest, but poll() returns - // the lowest first, we fill in the slots starting at fluidAmount-1 - var slot = this.aeFluidHandler.getInventory()[fluidAmount - index - 1]; - slot.setConfig(new GenericStack(what, 1)); - slot.setStock(new GenericStack(what, request)); + topFluids.insert(what, amount); } - aeFluidHandler.clearInventory(index); + syncListToHandler(); + } + + // Expensive, use sparingly. + private long getAmountInNetwork(AEKey key) { + MEStorage networkInv = this.getMainNode().getGrid().getStorageService().getInventory(); + long extracted = networkInv.extract(key, Long.MAX_VALUE, Actionable.SIMULATE, actionSource); + return extracted; } /////////////////////////////// @@ -350,6 +385,21 @@ public ExportOnlyAEStockingFluidSlot(@Nullable GenericStack config, @Nullable Ge super(config, stock); } + @Override + public void setConfig(@Nullable GenericStack config) { + var oldConfig = this.config; + this.config = config; + if (config == null) { + this.setStock(null); + } else { + if (!config.equals(oldConfig)) { + // Refresh to get stack amount from AE2 + this.setStock(new GenericStack(config.what(), getAmountInNetwork(config.what()))); + pendingWatcherSync = true; + } + } + } + @Override public ExportOnlyAEFluidSlot copy() { return new ExportOnlyAEStockingFluidSlot(