From 43c98dc3100bbe0ec6fc4e89a6a32b3c2881a054 Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Mon, 2 Sep 2024 09:48:55 -0700 Subject: [PATCH 1/9] init --- .../api/capability/impl/ItemHandlerList.java | 80 ++++++++++++++----- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index 4f45f7b6d45..576103a91b2 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -1,5 +1,7 @@ package gregtech.api.capability.impl; +import it.unimi.dsi.fastutil.objects.Object2IntMap; + import net.minecraft.item.ItemStack; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandlerModifiable; @@ -15,24 +17,15 @@ /** * Efficiently delegates calls into multiple item handlers */ -public class ItemHandlerList implements IItemHandlerModifiable { +public class ItemHandlerList extends AbstractList implements IItemHandlerModifiable { private final Int2ObjectMap handlerBySlotIndex = new Int2ObjectOpenHashMap<>(); private final Object2IntMap baseIndexOffset = new Object2IntArrayMap<>(); + private final List handlerList = new ArrayList<>(); + public ItemHandlerList(List itemHandlerList) { - int currentSlotIndex = 0; - for (IItemHandler itemHandler : itemHandlerList) { - if (baseIndexOffset.containsKey(itemHandler)) { - throw new IllegalArgumentException("Attempted to add item handler " + itemHandler + " twice"); - } - baseIndexOffset.put(itemHandler, currentSlotIndex); - int slotsCount = itemHandler.getSlots(); - for (int slotIndex = 0; slotIndex < slotsCount; slotIndex++) { - handlerBySlotIndex.put(currentSlotIndex + slotIndex, itemHandler); - } - currentSlotIndex += slotsCount; - } + addAll(itemHandlerList); } public int getIndexOffset(IItemHandler handler) { @@ -41,7 +34,7 @@ public int getIndexOffset(IItemHandler handler) { @Override public int getSlots() { - return handlerBySlotIndex.size(); + return size(); } @Override @@ -62,14 +55,14 @@ public void setStackInSlot(int slot, @NotNull ItemStack stack) { public ItemStack getStackInSlot(int slot) { if (invalidSlot(slot)) return ItemStack.EMPTY; IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.getStackInSlot(slot - baseIndexOffset.get(itemHandler)); + return itemHandler.getStackInSlot(slot - baseIndexOffset.getInt(itemHandler)); } @Override public int getSlotLimit(int slot) { if (invalidSlot(slot)) return 0; IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.getSlotLimit(slot - baseIndexOffset.get(itemHandler)); + return itemHandler.getSlotLimit(slot - baseIndexOffset.getInt(itemHandler)); } @NotNull @@ -77,7 +70,7 @@ public int getSlotLimit(int slot) { public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { if (invalidSlot(slot)) return stack; IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.insertItem(slot - baseIndexOffset.get(itemHandler), stack, simulate); + return itemHandler.insertItem(slot - baseIndexOffset.getInt(itemHandler), stack, simulate); } @NotNull @@ -85,15 +78,60 @@ public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate public ItemStack extractItem(int slot, int amount, boolean simulate) { if (invalidSlot(slot)) return ItemStack.EMPTY; IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.extractItem(slot - baseIndexOffset.get(itemHandler), amount, simulate); + return itemHandler.extractItem(slot - baseIndexOffset.getInt(itemHandler), amount, simulate); } @NotNull public Collection getBackingHandlers() { - return Collections.unmodifiableCollection(baseIndexOffset.keySet()); + return Collections.unmodifiableCollection(handlerList); + } + + @Override + public int size() { + return handlerList.size(); + } + + @Override + public boolean add(IItemHandler handler) { + int s = size(); + add(s, handler); + return s != size(); + } + + @Override + public void add(int index, IItemHandler element) { + if (invalidIndex(index)) return; + int currentSlotIndex = handlerBySlotIndex.size(); + if (baseIndexOffset.containsKey(element)) { + throw new IllegalArgumentException("Attempted to add item handler " + element + " twice"); + } + handlerList.add(element); + baseIndexOffset.put(element, currentSlotIndex); + for (int slotIndex = 0; slotIndex < element.getSlots(); slotIndex++) { + handlerBySlotIndex.put(currentSlotIndex + slotIndex, element); + } + } + + @Override + public IItemHandler get(int index) { + return handlerList.get(index); + } + + @Override + public IItemHandler remove(int index) { + if (invalidIndex(index)) return null; + var handler = get(index); + for (int i = index; i < size(); i++) { + int offset = baseIndexOffset.getInt(get(i)); + for (int j = 0; j < get(index).getSlots(); j++) { + handlerBySlotIndex.remove(offset + j); + } + baseIndexOffset.removeInt(handler); + } + return handler; } - private boolean invalidSlot(int slot) { - return slot < 0 && slot >= this.getSlots(); + private boolean invalidIndex(int index) { + return index < 0 || index >= handlerList.size(); } } From 5096c9c74278f3852294c333a024b74ab273a7f7 Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:22:26 -0700 Subject: [PATCH 2/9] more work + tests --- .../api/capability/impl/ItemHandlerList.java | 39 ++++++++++++------- .../capability/impl/ItemHandlerListTest.java | 36 +++++++++++++++++ 2 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index 576103a91b2..82cf7eece87 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -100,15 +100,15 @@ public boolean add(IItemHandler handler) { @Override public void add(int index, IItemHandler element) { - if (invalidIndex(index)) return; - int currentSlotIndex = handlerBySlotIndex.size(); - if (baseIndexOffset.containsKey(element)) { +// Objects.checkIndex(index, size()); + if (handlerList.contains(element)) { throw new IllegalArgumentException("Attempted to add item handler " + element + " twice"); } - handlerList.add(element); - baseIndexOffset.put(element, currentSlotIndex); + handlerList.add(index, element); + int offset = handlerBySlotIndex.size(); + baseIndexOffset.put(element, offset); for (int slotIndex = 0; slotIndex < element.getSlots(); slotIndex++) { - handlerBySlotIndex.put(currentSlotIndex + slotIndex, element); + handlerBySlotIndex.put(offset + slotIndex, element); } } @@ -119,19 +119,28 @@ public IItemHandler get(int index) { @Override public IItemHandler remove(int index) { - if (invalidIndex(index)) return null; - var handler = get(index); +// Objects.checkIndex(index, size()); + var handler2 = get(index); + int offset2 = baseIndexOffset.getInt(handler2); + for (int i = index; i < size(); i++) { - int offset = baseIndexOffset.getInt(get(i)); - for (int j = 0; j < get(index).getSlots(); j++) { + int offset = baseIndexOffset.removeInt(get(i)); + for (int j = 0; j < get(i).getSlots(); j++) { handlerBySlotIndex.remove(offset + j); } - baseIndexOffset.removeInt(handler); } - return handler; - } - private boolean invalidIndex(int index) { - return index < 0 || index >= handlerList.size(); + var removed = handlerList.remove(index); + for (var handler : handlerList) { + if (baseIndexOffset.containsKey(handler)) + continue; + + int offset = handlerBySlotIndex.size(); + baseIndexOffset.put(handler, offset); + for (int i = 0; i < handler.getSlots(); i++) { + handlerBySlotIndex.put(offset + i, handler); + } + } + return removed; } } diff --git a/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java new file mode 100644 index 00000000000..eda1da8faed --- /dev/null +++ b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java @@ -0,0 +1,36 @@ +package gregtech.api.capability.impl; + +import gregtech.Bootstrap; + +import net.minecraftforge.items.ItemStackHandler; + +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +public class ItemHandlerListTest { + + @BeforeAll + public static void bootstrap() { + Bootstrap.perform(); + } + + @Test + public void testListOperations() { + var list = new ItemHandlerList(Collections.emptyList()); + + // test add + list.add(new ItemStackHandler(16)); +// MatcherAssert.assertThat("size is wrong", list.getSlots() == 16); + list.add(new ItemStackHandler(16)); +// MatcherAssert.assertThat("size is wrong", list.getSlots() == 32); +// MatcherAssert.assertThat("size is wrong", list.size() == 2); + + // test removal + list.remove(0); +// MatcherAssert.assertThat("size is wrong", list.getSlots() == 16); +// MatcherAssert.assertThat("size is wrong", list.size() == 1); + } +} From 52de6ec637e8108ab59977e38e0e03bb3ae3c947 Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Wed, 28 May 2025 19:47:58 -0700 Subject: [PATCH 3/9] more work on list and tests --- .../api/capability/impl/ItemHandlerList.java | 90 ++++++++++++------- .../capability/impl/ItemHandlerListTest.java | 27 +++--- 2 files changed, 77 insertions(+), 40 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index 82cf7eece87..0966106090d 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -1,7 +1,5 @@ package gregtech.api.capability.impl; -import it.unimi.dsi.fastutil.objects.Object2IntMap; - import net.minecraft.item.ItemStack; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandlerModifiable; @@ -10,6 +8,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -22,19 +21,24 @@ public class ItemHandlerList extends AbstractList implements IItem private final Int2ObjectMap handlerBySlotIndex = new Int2ObjectOpenHashMap<>(); private final Object2IntMap baseIndexOffset = new Object2IntArrayMap<>(); - private final List handlerList = new ArrayList<>(); + public ItemHandlerList() {} - public ItemHandlerList(List itemHandlerList) { + public ItemHandlerList(Collection itemHandlerList) { addAll(itemHandlerList); } + public ItemHandlerList(ItemHandlerList parent, IItemHandler... additional) { + addAll(parent.getBackingHandlers()); + Collections.addAll(this, additional); + } + public int getIndexOffset(IItemHandler handler) { return baseIndexOffset.getOrDefault(handler, -1); } @Override public int getSlots() { - return size(); + return handlerBySlotIndex.size(); } @Override @@ -81,14 +85,19 @@ public ItemStack extractItem(int slot, int amount, boolean simulate) { return itemHandler.extractItem(slot - baseIndexOffset.getInt(itemHandler), amount, simulate); } + private boolean invalidSlot(int slot) { + if (handlerBySlotIndex.isEmpty()) return false; + return slot < 0 || slot >= handlerBySlotIndex.size(); + } + @NotNull public Collection getBackingHandlers() { - return Collections.unmodifiableCollection(handlerList); + return Collections.unmodifiableCollection(baseIndexOffset.keySet()); } @Override public int size() { - return handlerList.size(); + return baseIndexOffset.size(); } @Override @@ -99,12 +108,18 @@ public boolean add(IItemHandler handler) { } @Override - public void add(int index, IItemHandler element) { -// Objects.checkIndex(index, size()); - if (handlerList.contains(element)) { + public void add(int unused, IItemHandler element) { + Objects.requireNonNull(element); + if (baseIndexOffset.containsKey(element)) { throw new IllegalArgumentException("Attempted to add item handler " + element + " twice"); } - handlerList.add(index, element); + if (element instanceof ItemHandlerList list) { + // possible infinite recursion + // throw instead? + addAll(list); + return; + } + int offset = handlerBySlotIndex.size(); baseIndexOffset.put(element, offset); for (int slotIndex = 0; slotIndex < element.getSlots(); slotIndex++) { @@ -114,33 +129,48 @@ public void add(int index, IItemHandler element) { @Override public IItemHandler get(int index) { - return handlerList.get(index); + if (invalidIndex(index)) throw new IndexOutOfBoundsException(); + ObjectIterator itr = baseIndexOffset.keySet().iterator(); + itr.skip(index); // skip n-1 elements + return itr.next(); // get nth element } @Override public IItemHandler remove(int index) { -// Objects.checkIndex(index, size()); - var handler2 = get(index); - int offset2 = baseIndexOffset.getInt(handler2); - - for (int i = index; i < size(); i++) { - int offset = baseIndexOffset.removeInt(get(i)); - for (int j = 0; j < get(i).getSlots(); j++) { - handlerBySlotIndex.remove(offset + j); - } + if (invalidIndex(index)) { + throw new IndexOutOfBoundsException(); } - var removed = handlerList.remove(index); - for (var handler : handlerList) { - if (baseIndexOffset.containsKey(handler)) - continue; + IItemHandler handler = get(index); - int offset = handlerBySlotIndex.size(); - baseIndexOffset.put(handler, offset); - for (int i = 0; i < handler.getSlots(); i++) { - handlerBySlotIndex.put(offset + i, handler); + // remove handler + int lower = baseIndexOffset.removeInt(handler); + + // remove slot indices + int upper = lower + handler.getSlots(); + for (int i = lower; i < upper; i++) { + handlerBySlotIndex.remove(i); + } + + // update slot indices ahead of the removed handler + for (int slot = upper; slot < getSlots() + handler.getSlots(); slot++) { + IItemHandler remove = handlerBySlotIndex.remove(slot); + handlerBySlotIndex.put(slot - upper, remove); + } + + // update handlers ahead of the removed handler + for (IItemHandler h : baseIndexOffset.keySet()) { + int offset = baseIndexOffset.getInt(h); + if (offset > lower) { + baseIndexOffset.put(h, offset - handler.getSlots()); } } - return removed; + + return handler; + } + + public boolean invalidIndex(int index) { + if (baseIndexOffset.isEmpty()) return false; + return index < 0 || index >= baseIndexOffset.size(); } } diff --git a/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java index eda1da8faed..2e0383b8cf1 100644 --- a/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java +++ b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java @@ -2,13 +2,14 @@ import gregtech.Bootstrap; +import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.ItemStackHandler; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.util.Collections; +import java.util.Objects; public class ItemHandlerListTest { @@ -19,18 +20,24 @@ public static void bootstrap() { @Test public void testListOperations() { - var list = new ItemHandlerList(Collections.emptyList()); + var list = new ItemHandlerList(); + ItemStackHandler firstHandler = new ItemStackHandler(16); + ItemStackHandler secondHandler = new ItemStackHandler(16); // test add - list.add(new ItemStackHandler(16)); -// MatcherAssert.assertThat("size is wrong", list.getSlots() == 16); - list.add(new ItemStackHandler(16)); -// MatcherAssert.assertThat("size is wrong", list.getSlots() == 32); -// MatcherAssert.assertThat("size is wrong", list.size() == 2); + list.add(firstHandler); + MatcherAssert.assertThat("wrong number of slots!", list.getSlots() == 16); + MatcherAssert.assertThat("wrong number of handlers!", list.size() == 1); + list.add(secondHandler); + MatcherAssert.assertThat("wrong number of slots!", list.getSlots() == 32); + MatcherAssert.assertThat("wrong number of handlers!", list.size() == 2); // test removal - list.remove(0); -// MatcherAssert.assertThat("size is wrong", list.getSlots() == 16); -// MatcherAssert.assertThat("size is wrong", list.size() == 1); + IItemHandler removed = list.remove(0); + MatcherAssert.assertThat("wrong number of slots!", list.getSlots() == 16); + MatcherAssert.assertThat("wrong number of handlers!", list.size() == 1); + MatcherAssert.assertThat("removed handler is not the first handler!", Objects.equals(removed, firstHandler)); + int newIndex = list.getIndexOffset(secondHandler); + MatcherAssert.assertThat("second handler was not updated!", newIndex == 0); } } From 90bc538f05c137c979fd0696258fd25ebf1eede0 Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Thu, 29 May 2025 17:36:52 -0700 Subject: [PATCH 4/9] improve constructor add methods to simplify handler logic improve handler list adding improve get add immutable handler list improve handler list tests --- .../api/capability/impl/ItemHandlerList.java | 127 +++++++++++++----- .../capability/impl/ItemHandlerListTest.java | 49 +++++-- 2 files changed, 135 insertions(+), 41 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index 0966106090d..56ecd1ff606 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -8,7 +8,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.ObjectIterator; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -18,22 +17,45 @@ */ public class ItemHandlerList extends AbstractList implements IItemHandlerModifiable { - private final Int2ObjectMap handlerBySlotIndex = new Int2ObjectOpenHashMap<>(); - private final Object2IntMap baseIndexOffset = new Object2IntArrayMap<>(); + protected final Int2ObjectMap handlerBySlotIndex = new Int2ObjectOpenHashMap<>(); + protected final Object2IntMap baseIndexOffset = new Object2IntArrayMap<>(); - public ItemHandlerList() {} + // this is only used for get() + protected IItemHandler[] handlers = new IItemHandler[0]; public ItemHandlerList(Collection itemHandlerList) { addAll(itemHandlerList); + baseIndexOffset.defaultReturnValue(-1); + } + + public ItemHandlerList() { + this(Collections.emptyList()); } public ItemHandlerList(ItemHandlerList parent, IItemHandler... additional) { - addAll(parent.getBackingHandlers()); + this(parent); Collections.addAll(this, additional); } + /** + * @param handler the handler to get the slot offset of + * @return the slot offset + * @throws IllegalArgumentException if the handler is not in this list + */ public int getIndexOffset(IItemHandler handler) { - return baseIndexOffset.getOrDefault(handler, -1); + int offset = baseIndexOffset.get(handler); + if (offset == -1) throw new IllegalArgumentException(); + return offset; + } + + @NotNull + protected IItemHandler getHandlerBySlot(int slot) { + if (invalidSlot(slot)) throw new IndexOutOfBoundsException(); + return handlerBySlotIndex.get(slot); + } + + protected int getInternalSlot(int slot) { + return slot - getIndexOffset(getHandlerBySlot(slot)); } @Override @@ -44,11 +66,12 @@ public int getSlots() { @Override public void setStackInSlot(int slot, @NotNull ItemStack stack) { if (invalidSlot(slot)) return; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - int actualSlot = slot - baseIndexOffset.get(itemHandler); + IItemHandler itemHandler = getHandlerBySlot(slot); + int actualSlot = getInternalSlot(slot); if (itemHandler instanceof IItemHandlerModifiable modifiable) { modifiable.setStackInSlot(actualSlot, stack); } else { + // should this no-op instead? itemHandler.extractItem(actualSlot, Integer.MAX_VALUE, false); itemHandler.insertItem(actualSlot, stack, false); } @@ -58,36 +81,27 @@ public void setStackInSlot(int slot, @NotNull ItemStack stack) { @Override public ItemStack getStackInSlot(int slot) { if (invalidSlot(slot)) return ItemStack.EMPTY; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.getStackInSlot(slot - baseIndexOffset.getInt(itemHandler)); + return getHandlerBySlot(slot).getStackInSlot(getInternalSlot(slot)); } @Override public int getSlotLimit(int slot) { if (invalidSlot(slot)) return 0; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.getSlotLimit(slot - baseIndexOffset.getInt(itemHandler)); + return getHandlerBySlot(slot).getSlotLimit(getInternalSlot(slot)); } @NotNull @Override public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { if (invalidSlot(slot)) return stack; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.insertItem(slot - baseIndexOffset.getInt(itemHandler), stack, simulate); + return getHandlerBySlot(slot).insertItem(getInternalSlot(slot), stack, simulate); } @NotNull @Override public ItemStack extractItem(int slot, int amount, boolean simulate) { if (invalidSlot(slot)) return ItemStack.EMPTY; - IItemHandler itemHandler = handlerBySlotIndex.get(slot); - return itemHandler.extractItem(slot - baseIndexOffset.getInt(itemHandler), amount, simulate); - } - - private boolean invalidSlot(int slot) { - if (handlerBySlotIndex.isEmpty()) return false; - return slot < 0 || slot >= handlerBySlotIndex.size(); + return getHandlerBySlot(slot).extractItem(getInternalSlot(slot), amount, simulate); } @NotNull @@ -103,7 +117,13 @@ public int size() { @Override public boolean add(IItemHandler handler) { int s = size(); - add(s, handler); + if (handler instanceof ItemHandlerList list) { + // possible infinite recursion + // throw instead? + addAll(s, list); + } else { + add(s, handler); + } return s != size(); } @@ -113,12 +133,6 @@ public void add(int unused, IItemHandler element) { if (baseIndexOffset.containsKey(element)) { throw new IllegalArgumentException("Attempted to add item handler " + element + " twice"); } - if (element instanceof ItemHandlerList list) { - // possible infinite recursion - // throw instead? - addAll(list); - return; - } int offset = handlerBySlotIndex.size(); baseIndexOffset.put(element, offset); @@ -127,12 +141,16 @@ public void add(int unused, IItemHandler element) { } } + @Override + public @NotNull Iterator iterator() { + return baseIndexOffset.keySet().iterator(); + } + @Override public IItemHandler get(int index) { if (invalidIndex(index)) throw new IndexOutOfBoundsException(); - ObjectIterator itr = baseIndexOffset.keySet().iterator(); - itr.skip(index); // skip n-1 elements - return itr.next(); // get nth element + updateHandlerArray(); + return handlers[index]; } @Override @@ -169,8 +187,53 @@ public IItemHandler remove(int index) { return handler; } - public boolean invalidIndex(int index) { + private boolean invalidSlot(int slot) { + if (handlerBySlotIndex.isEmpty()) return false; + return slot < 0 || slot >= handlerBySlotIndex.size(); + } + + private boolean invalidIndex(int index) { if (baseIndexOffset.isEmpty()) return false; return index < 0 || index >= baseIndexOffset.size(); } + + private void updateHandlerArray() { + if (handlers.length != size()) { + handlers = new IItemHandler[size()]; + int i = 0; + for (IItemHandler h : baseIndexOffset.keySet()) { + handlers[i++] = h; + } + } + } + + public ItemHandlerList toImmutable() { + return new Immutable(this); + } + + private static class Immutable extends ItemHandlerList { + + private Immutable(ItemHandlerList list) { + this.handlers = list.handlers; + this.baseIndexOffset.putAll(list.baseIndexOffset); + this.handlerBySlotIndex.putAll(list.handlerBySlotIndex); + } + + @Override + public void add(int unused, IItemHandler element) { + // no op? + throw new UnsupportedOperationException(); + } + + @Override + public IItemHandler remove(int index) { + // no op? + throw new UnsupportedOperationException(); + } + + @Override + public IItemHandler get(int index) { + return handlers[index]; + } + } } diff --git a/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java index 2e0383b8cf1..b99e5ea7aac 100644 --- a/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java +++ b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java @@ -5,12 +5,13 @@ import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.ItemStackHandler; -import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.util.Objects; +import static org.hamcrest.MatcherAssert.assertThat; + public class ItemHandlerListTest { @BeforeAll @@ -26,18 +27,48 @@ public void testListOperations() { // test add list.add(firstHandler); - MatcherAssert.assertThat("wrong number of slots!", list.getSlots() == 16); - MatcherAssert.assertThat("wrong number of handlers!", list.size() == 1); + assertThat("wrong number of slots!", list.getSlots() == 16); + assertThat("wrong number of handlers!", list.size() == 1); list.add(secondHandler); - MatcherAssert.assertThat("wrong number of slots!", list.getSlots() == 32); - MatcherAssert.assertThat("wrong number of handlers!", list.size() == 2); + assertThat("wrong number of slots!", list.getSlots() == 32); + assertThat("wrong number of handlers!", list.size() == 2); // test removal IItemHandler removed = list.remove(0); - MatcherAssert.assertThat("wrong number of slots!", list.getSlots() == 16); - MatcherAssert.assertThat("wrong number of handlers!", list.size() == 1); - MatcherAssert.assertThat("removed handler is not the first handler!", Objects.equals(removed, firstHandler)); + assertThat("wrong number of slots!", list.getSlots() == 16); + assertThat("wrong number of handlers!", list.size() == 1); + assertThat("removed handler is not the first handler!", Objects.equals(removed, firstHandler)); int newIndex = list.getIndexOffset(secondHandler); - MatcherAssert.assertThat("second handler was not updated!", newIndex == 0); + assertThat("second handler was not updated!", newIndex == 0); + + IItemHandler get = list.get(0); + assertThat("Second handler is not first!", get == secondHandler); + + // test add after removal + list.add(firstHandler); + + get = list.get(1); + assertThat("First handler is not second!", get == firstHandler); + assertThat("wrong number of slots!", list.getSlots() == 32); + assertThat("wrong number of handlers!", list.size() == 2); + + // test immutable + ItemHandlerList immutable = list.toImmutable(); + + boolean testRemove = false; + try { + immutable.remove(0); + } catch (UnsupportedOperationException ignored) { + testRemove = true; + } + assertThat("list was modified!", testRemove); + + boolean testAdd = false; + try { + immutable.add(firstHandler); + } catch (UnsupportedOperationException ignored) { + testAdd = true; + } + assertThat("list was modified!", testAdd); } } From 09aa24b47e022eaa23779e8279e4914626d0d316 Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Thu, 29 May 2025 17:40:01 -0700 Subject: [PATCH 5/9] make handler list notifiable and pass to inner handlers --- .../api/capability/impl/ItemHandlerList.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index 56ecd1ff606..8e00e0a6698 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -1,5 +1,8 @@ package gregtech.api.capability.impl; +import gregtech.api.capability.INotifiableHandler; +import gregtech.api.metatileentity.MetaTileEntity; + import net.minecraft.item.ItemStack; import net.minecraftforge.items.IItemHandler; import net.minecraftforge.items.IItemHandlerModifiable; @@ -15,7 +18,7 @@ /** * Efficiently delegates calls into multiple item handlers */ -public class ItemHandlerList extends AbstractList implements IItemHandlerModifiable { +public class ItemHandlerList extends AbstractList implements IItemHandlerModifiable, INotifiableHandler { protected final Int2ObjectMap handlerBySlotIndex = new Int2ObjectOpenHashMap<>(); protected final Object2IntMap baseIndexOffset = new Object2IntArrayMap<>(); @@ -109,6 +112,24 @@ public Collection getBackingHandlers() { return Collections.unmodifiableCollection(baseIndexOffset.keySet()); } + @Override + public void addNotifiableMetaTileEntity(MetaTileEntity metaTileEntity) { + for (IItemHandler handler : this) { + if (handler instanceof INotifiableHandler notifiableHandler) { + notifiableHandler.addNotifiableMetaTileEntity(metaTileEntity); + } + } + } + + @Override + public void removeNotifiableMetaTileEntity(MetaTileEntity metaTileEntity) { + for (IItemHandler handler : this) { + if (handler instanceof INotifiableHandler notifiableHandler) { + notifiableHandler.removeNotifiableMetaTileEntity(metaTileEntity); + } + } + } + @Override public int size() { return baseIndexOffset.size(); From 0cafbbfc99008a0479543f5ffe7c0daba643cd6c Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Thu, 29 May 2025 17:43:06 -0700 Subject: [PATCH 6/9] use getSlots --- src/main/java/gregtech/api/capability/impl/ItemHandlerList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index 8e00e0a6698..ea2296fcc7e 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -155,7 +155,7 @@ public void add(int unused, IItemHandler element) { throw new IllegalArgumentException("Attempted to add item handler " + element + " twice"); } - int offset = handlerBySlotIndex.size(); + int offset = getSlots(); baseIndexOffset.put(element, offset); for (int slotIndex = 0; slotIndex < element.getSlots(); slotIndex++) { handlerBySlotIndex.put(offset + slotIndex, element); From 5f90d32e9d2a538e3fb96a49671212eebec45fac Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Fri, 30 May 2025 18:10:11 -0700 Subject: [PATCH 7/9] replace a constructor with an `of()` method don't throw in `getIndexOffset()` --- .../api/capability/impl/ItemHandlerList.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index ea2296fcc7e..4e96537f2b2 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -35,20 +35,20 @@ public ItemHandlerList() { this(Collections.emptyList()); } - public ItemHandlerList(ItemHandlerList parent, IItemHandler... additional) { - this(parent); - Collections.addAll(this, additional); + public static ItemHandlerList of(IItemHandler... handlers) { + ItemHandlerList list = new ItemHandlerList(); + if (handlers != null && handlers.length > 0) { + Collections.addAll(list, handlers); + } + return list.toImmutable(); } /** * @param handler the handler to get the slot offset of - * @return the slot offset - * @throws IllegalArgumentException if the handler is not in this list + * @return the slot offset, or {@code -1} if the handler does not exist */ public int getIndexOffset(IItemHandler handler) { - int offset = baseIndexOffset.get(handler); - if (offset == -1) throw new IllegalArgumentException(); - return offset; + return baseIndexOffset.get(handler); } @NotNull From 48411b505c77e237f68ad61879590056587469bf Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Fri, 30 May 2025 18:42:50 -0700 Subject: [PATCH 8/9] isEmpty check is not necessary implement indexOf() implement lastIndexOf() remove braces --- .../api/capability/impl/ItemHandlerList.java | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index 4e96537f2b2..ad45e34a2bf 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -149,7 +149,7 @@ public boolean add(IItemHandler handler) { } @Override - public void add(int unused, IItemHandler element) { + public void add(int unused, @NotNull IItemHandler element) { Objects.requireNonNull(element); if (baseIndexOffset.containsKey(element)) { throw new IllegalArgumentException("Attempted to add item handler " + element + " twice"); @@ -176,9 +176,7 @@ public IItemHandler get(int index) { @Override public IItemHandler remove(int index) { - if (invalidIndex(index)) { - throw new IndexOutOfBoundsException(); - } + if (invalidIndex(index)) throw new IndexOutOfBoundsException(); IItemHandler handler = get(index); @@ -208,13 +206,29 @@ public IItemHandler remove(int index) { return handler; } + @Override + public int indexOf(@NotNull Object o) { + for (int i = 0; i < size(); i++) { + if (Objects.equals(o, get(i))) + return i; + } + return -1; + } + + @Override + public int lastIndexOf(@NotNull Object o) { + for (int i = size() - 1; i >= 0; i--) { + if (Objects.equals(o, get(i))) + return i; + } + return -1; + } + private boolean invalidSlot(int slot) { - if (handlerBySlotIndex.isEmpty()) return false; return slot < 0 || slot >= handlerBySlotIndex.size(); } private boolean invalidIndex(int index) { - if (baseIndexOffset.isEmpty()) return false; return index < 0 || index >= baseIndexOffset.size(); } @@ -241,7 +255,7 @@ private Immutable(ItemHandlerList list) { } @Override - public void add(int unused, IItemHandler element) { + public void add(int unused, @NotNull IItemHandler element) { // no op? throw new UnsupportedOperationException(); } From 61b409b873bf1b340ad996b286102f26e1c89527 Mon Sep 17 00:00:00 2001 From: Ghzdude <44148655+ghzdude@users.noreply.github.com> Date: Fri, 30 May 2025 23:47:51 -0700 Subject: [PATCH 9/9] merge slot index for loop use methods instead of field access improve immutable tests --- .../api/capability/impl/ItemHandlerList.java | 31 ++++++++++--------- .../capability/impl/ItemHandlerListTest.java | 14 +++------ 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java index ad45e34a2bf..15eae2f8bd9 100644 --- a/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java +++ b/src/main/java/gregtech/api/capability/impl/ItemHandlerList.java @@ -178,32 +178,33 @@ public IItemHandler get(int index) { public IItemHandler remove(int index) { if (invalidIndex(index)) throw new IndexOutOfBoundsException(); - IItemHandler handler = get(index); + IItemHandler removed = get(index); + int removedSlots = removed.getSlots(); // remove handler - int lower = baseIndexOffset.removeInt(handler); + int lower = baseIndexOffset.removeInt(removed); + // update slot indices ahead of the removed handler and // remove slot indices - int upper = lower + handler.getSlots(); - for (int i = lower; i < upper; i++) { - handlerBySlotIndex.remove(i); - } - - // update slot indices ahead of the removed handler - for (int slot = upper; slot < getSlots() + handler.getSlots(); slot++) { - IItemHandler remove = handlerBySlotIndex.remove(slot); - handlerBySlotIndex.put(slot - upper, remove); + int upper = lower + removedSlots; + int size = getSlots(); // slot size will be mutated + for (int i = lower; i < size; i++) { + if (i < upper) { + handlerBySlotIndex.put(i, getHandlerBySlot(i + removedSlots)); + } else { + handlerBySlotIndex.remove(i); + } } // update handlers ahead of the removed handler - for (IItemHandler h : baseIndexOffset.keySet()) { - int offset = baseIndexOffset.getInt(h); + for (IItemHandler h : this) { + int offset = getIndexOffset(h); if (offset > lower) { - baseIndexOffset.put(h, offset - handler.getSlots()); + baseIndexOffset.put(h, offset - removedSlots); } } - return handler; + return removed; } @Override diff --git a/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java index b99e5ea7aac..8372d2b93ca 100644 --- a/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java +++ b/src/test/java/gregtech/api/capability/impl/ItemHandlerListTest.java @@ -55,20 +55,14 @@ public void testListOperations() { // test immutable ItemHandlerList immutable = list.toImmutable(); - boolean testRemove = false; try { immutable.remove(0); - } catch (UnsupportedOperationException ignored) { - testRemove = true; - } - assertThat("list was modified!", testRemove); + assertThat("list was modified!", false); + } catch (UnsupportedOperationException ignored) {} - boolean testAdd = false; try { immutable.add(firstHandler); - } catch (UnsupportedOperationException ignored) { - testAdd = true; - } - assertThat("list was modified!", testAdd); + assertThat("list was modified!", false); + } catch (UnsupportedOperationException ignored) {} } }