Transactor Optimization

It was an O(n^2) operation to insert an item. Now its just O(n). The
functions for checking to see if a stack is valid in a slot are
expensive and should never be called more than once per slot, let alone
n^2 times (where n is the length of the inventory).

Additionally, by wrapping inventory in a slot iterator, I was able to
eliminate the need to handle FSided and VSided differently from
IInventory.
This commit is contained in:
CovertJaguar 2013-05-27 20:06:27 -07:00
parent 0a18ae34ff
commit e3420842fe
7 changed files with 264 additions and 76 deletions

View file

@ -0,0 +1,43 @@
package buildcraft.core.inventory;
import buildcraft.core.inventory.InventoryIterator.IInvSlot;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.ForgeDirection;
/**
* @author CovertJaguar <http://www.railcraft.info>
*/
public class InventoryIterator {
/**
* Returns an Iterable object for the specified side of the inventory.
*
* @param inv
* @param side
* @return Iterable
*/
public static Iterable<IInvSlot> getIterable(IInventory inv, ForgeDirection side) {
if (inv instanceof ISidedInventory) {
return new InventoryIteratorSided((ISidedInventory) inv, side);
}
if (inv instanceof net.minecraftforge.common.ISidedInventory) {
return new InventoryIteratorSided(InventoryWrapper.getWrappedInventory(inv), side);
}
return new InventoryIteratorSimple(inv);
}
public interface IInvSlot {
boolean canPutStackInSlot(ItemStack stack);
boolean canTakeStackFromSlot(ItemStack stack);
ItemStack decreaseStackInSlot();
ItemStack getStackInSlot();
void setStackInSlot(ItemStack stack);
}
}

View file

@ -0,0 +1,80 @@
package buildcraft.core.inventory;
import buildcraft.core.inventory.InventoryIterator.IInvSlot;
import java.util.Iterator;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.ForgeDirection;
/**
* @author CovertJaguar <http://www.railcraft.info>
*/
public class InventoryIteratorSided implements Iterable<IInvSlot> {
private final ISidedInventory inv;
private final int side;
InventoryIteratorSided(ISidedInventory inv, ForgeDirection side) {
this.inv = inv;
this.side = side.ordinal();
}
@Override
public Iterator<IInvSlot> iterator() {
return new Iterator<IInvSlot>() {
int[] slots = inv.getAccessibleSlotsFromSide(side);
int index = 0;
@Override
public boolean hasNext() {
return index < slots.length;
}
@Override
public IInvSlot next() {
return new InvSlot(slots[index++]);
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported.");
}
};
}
private class InvSlot implements IInvSlot {
private int slot;
public InvSlot(int slot) {
this.slot = slot;
}
@Override
public ItemStack getStackInSlot() {
return inv.getStackInSlot(slot);
}
@Override
public void setStackInSlot(ItemStack stack) {
inv.setInventorySlotContents(slot, stack);
}
@Override
public boolean canPutStackInSlot(ItemStack stack) {
return inv.canInsertItem(slot, stack, side);
}
@Override
public boolean canTakeStackFromSlot(ItemStack stack) {
return inv.canExtractItem(slot, stack, side);
}
@Override
public ItemStack decreaseStackInSlot() {
return inv.decrStackSize(slot, 1);
}
}
}

View file

@ -0,0 +1,74 @@
package buildcraft.core.inventory;
import buildcraft.core.inventory.InventoryIterator.IInvSlot;
import java.util.Iterator;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
/**
* @author CovertJaguar <http://www.railcraft.info>
*/
public class InventoryIteratorSimple implements Iterable<IInvSlot> {
private final IInventory inv;
InventoryIteratorSimple(IInventory inv) {
this.inv = inv;
}
@Override
public Iterator<IInvSlot> iterator() {
return new Iterator<IInvSlot>() {
int slot = 0;
@Override
public boolean hasNext() {
return slot < inv.getSizeInventory();
}
@Override
public IInvSlot next() {
return new InvSlot(slot++);
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported.");
}
};
}
private class InvSlot implements IInvSlot {
private int slot;
public InvSlot(int slot) {
this.slot = slot;
}
@Override
public ItemStack getStackInSlot() {
return inv.getStackInSlot(slot);
}
@Override
public void setStackInSlot(ItemStack stack) {
inv.setInventorySlotContents(slot, stack);
}
@Override
public boolean canPutStackInSlot(ItemStack stack) {
return inv.isStackValidForSlot(slot, stack);
}
@Override
public boolean canTakeStackFromSlot(ItemStack stack) {
return true;
}
@Override
public ItemStack decreaseStackInSlot() {
return inv.decrStackSize(slot, 1);
}
}
}

View file

@ -20,7 +20,7 @@ public class InventoryWrapperSimple extends InventoryWrapper {
@Override
public boolean canInsertItem(int slotIndex, ItemStack itemstack, int side) {
return true;
return isStackValidForSlot(slotIndex, itemstack);
}
@Override

View file

@ -24,10 +24,10 @@ public abstract class Transactor implements ITransactor {
return new TransactorSpecial((ISpecialInventory) object);
else if (object instanceof ISidedInventory)
return new TransactorVanillaSided((ISidedInventory) object);
return new TransactorSimple((ISidedInventory) object);
else if (object instanceof net.minecraftforge.common.ISidedInventory)
return new TransactorForgeSided((net.minecraftforge.common.ISidedInventory) object);
return new TransactorSimple(InventoryWrapper.getWrappedInventory(object));
else if (object instanceof IInventory)
return new TransactorSimple(Utils.getInventory((IInventory) object));

View file

@ -1,5 +1,6 @@
package buildcraft.core.inventory;
import buildcraft.core.inventory.InventoryIterator.IInvSlot;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.ForgeDirection;
@ -13,16 +14,15 @@ public class TransactorRoundRobin extends TransactorSimple {
@Override
public int inject(ItemStack stack, ForgeDirection orientation, boolean doAdd) {
int oneLessThanStackSize = stack.stackSize - 1;
int added = 0;
for (int itemLoop = 0; itemLoop < stack.stackSize; ++itemLoop) { // add 1 item n times.
int minSimilar = Integer.MAX_VALUE;
int minSlot = -1;
int smallestStackSize = Integer.MAX_VALUE;
IInvSlot minSlot = null;
for (int j = 0; j < inventory.getSizeInventory() && minSimilar > 1; ++j) {
ItemStack stackInInventory = inventory.getStackInSlot(j);
for (IInvSlot slot : InventoryIterator.getIterable(inventory, orientation)) {
ItemStack stackInInventory = slot.getStackInSlot();
if (stackInInventory == null) {
continue;
@ -36,15 +36,17 @@ public class TransactorRoundRobin extends TransactorSimple {
continue;
}
if (stackInInventory.stackSize > 0 && stackInInventory.itemID == stack.itemID && stackInInventory.getItemDamage() == stack.getItemDamage()
&& stackInInventory.stackSize < minSimilar) {
minSimilar = stackInInventory.stackSize;
minSlot = j;
if (MERGE_HELPER.canStacksMerge(stack, stackInInventory) && stackInInventory.stackSize < smallestStackSize) {
smallestStackSize = stackInInventory.stackSize;
minSlot = slot;
}
if (smallestStackSize <= 1) {
break;
}
}
if (minSlot != -1) {
added += addToSlot(minSlot, stack, oneLessThanStackSize, doAdd); // add 1 item n times, into the selected slot
if (minSlot != null) {
added += addToSlot(minSlot, stack, stack.stackSize - 1, doAdd); // add 1 item n times, into the selected slot
} else {
break;
}
@ -53,5 +55,4 @@ public class TransactorRoundRobin extends TransactorSimple {
return added;
}
}

View file

@ -1,11 +1,15 @@
package buildcraft.core.inventory;
import buildcraft.core.inventory.InventoryIterator.IInvSlot;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.ForgeDirection;
public class TransactorSimple extends Transactor {
protected static final StackMergeHelper MERGE_HELPER = new StackMergeHelper();
protected IInventory inventory;
public TransactorSimple(IInventory inventory) {
@ -14,98 +18,84 @@ public class TransactorSimple extends Transactor {
@Override
public int inject(ItemStack stack, ForgeDirection orientation, boolean doAdd) {
List<IInvSlot> filledSlots = new ArrayList<IInvSlot>(inventory.getSizeInventory());
List<IInvSlot> emptySlots = new ArrayList<IInvSlot>(inventory.getSizeInventory());
for (IInvSlot slot : InventoryIterator.getIterable(inventory, orientation)) {
if (slot.canPutStackInSlot(stack)) {
if (slot.getStackInSlot() == null) {
emptySlots.add(slot);
} else {
filledSlots.add(slot);
}
}
}
int injected = 0;
injected = tryPut(stack, filledSlots, injected, doAdd);
injected = tryPut(stack, emptySlots, injected, doAdd);
int slotIndex = 0;
int slot = 0;
while (slotIndex < inventory.getSizeInventory() && injected < stack.stackSize) {
slot = getPartialSlot(stack, orientation, slotIndex++);
if (slot != -1){
injected += addToSlot(slot, stack, injected, doAdd);
}
}
slotIndex = 0;
while (slotIndex < inventory.getSizeInventory() && injected < stack.stackSize) {
slot = getEmptySlot(stack, orientation, slotIndex++);
if (slot != -1){
injected += addToSlot(slot, stack, injected, doAdd);
}
}
inventory.onInventoryChanged();
return injected;
}
protected int getPartialSlot(ItemStack stack, ForgeDirection orientation, int skipAhead) {
return getPartialSlot(stack, skipAhead, inventory.getSizeInventory());
}
protected int getPartialSlot(ItemStack stack, int startSlot, int endSlot) {
for (int i = startSlot; i < endSlot; i++) {
if (inventory.getStackInSlot(i) == null) {
continue;
}
if(!inventory.isStackValidForSlot(i, stack)){
continue;
}
if (!inventory.getStackInSlot(i).isItemEqual(stack) || !ItemStack.areItemStackTagsEqual(inventory.getStackInSlot(i), stack)) {
continue;
}
if (inventory.getStackInSlot(i).stackSize >= inventory.getStackInSlot(i).getMaxStackSize() || inventory.getStackInSlot(i).stackSize >= inventory.getInventoryStackLimit()) {
continue;
}
return i;
private int tryPut(ItemStack stack, List<IInvSlot> slots, int injected, boolean doAdd) {
if (injected >= stack.stackSize) {
return injected;
}
return -1;
for (IInvSlot slot : slots) {
ItemStack stackInSlot = slot.getStackInSlot();
if (stackInSlot == null || MERGE_HELPER.canStacksMerge(stackInSlot, stack)) {
int used = addToSlot(slot, stack, injected, doAdd);
if (used > 0) {
injected += used;
if (injected >= stack.stackSize) {
return injected;
}
}
}
}
return injected;
}
protected int getEmptySlot(ItemStack stack, ForgeDirection orientation, int slotIndex) {
return getEmptySlot(stack, 0, inventory.getSizeInventory());
}
protected int getEmptySlot(ItemStack stack, int startSlot, int endSlot) {
for (int i = startSlot; i < endSlot; i++)
if (inventory.getStackInSlot(i) == null && inventory.isStackValidForSlot(i, stack))
return i;
return -1;
}
protected int addToSlot(int slot, ItemStack stack, int injected, boolean doAdd) {
/**
*
* @param slot
* @param stack
* @param injected Amount not to move?
* @param doAdd
* @return Return the number of items moved.
*/
protected int addToSlot(IInvSlot slot, ItemStack stack, int injected, boolean doAdd) {
int available = stack.stackSize - injected;
int max = Math.min(stack.getMaxStackSize(), inventory.getInventoryStackLimit());
ItemStack stackInSlot = inventory.getStackInSlot(slot);
ItemStack stackInSlot = slot.getStackInSlot();
if (stackInSlot == null) {
int wanted = Math.min(available, max);
if (doAdd) {
stackInSlot = stack.copy();
stackInSlot.stackSize = wanted;
inventory.setInventorySlotContents(slot, stackInSlot);
slot.setStackInSlot(stackInSlot);
}
return wanted;
}
if (!stackInSlot.isItemEqual(stack) || !ItemStack.areItemStackTagsEqual(stackInSlot, stack))
if (!MERGE_HELPER.canStacksMerge(stack, stackInSlot)) {
return 0;
}
int wanted = max - stackInSlot.stackSize;
if (wanted <= 0)
if (wanted <= 0) {
return 0;
}
if (wanted > available)
if (wanted > available) {
wanted = available;
}
if (doAdd) {
stackInSlot.stackSize += wanted;
inventory.setInventorySlotContents(slot, stackInSlot);
slot.setStackInSlot(stackInSlot);
}
return wanted;
}