diff --git a/src/api/java/appeng/api/storage/data/IAEItemStack.java b/src/api/java/appeng/api/storage/data/IAEItemStack.java index f0e983c97..eb3dc1595 100644 --- a/src/api/java/appeng/api/storage/data/IAEItemStack.java +++ b/src/api/java/appeng/api/storage/data/IAEItemStack.java @@ -112,4 +112,13 @@ public interface IAEItemStack extends IAEStack * @return definition stack */ ItemStack getDefinition(); + + /** + * Compare this AE item stack to another item stack, but ignores + * the amount. It checks the item type, NBT and damage values. + * + * @param is An item stack + */ + boolean equals(ItemStack is); + } \ No newline at end of file diff --git a/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java b/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java index 8812073db..de9d01bc5 100644 --- a/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java +++ b/src/main/java/appeng/me/cluster/implementations/CraftingCPUCluster.java @@ -23,6 +23,7 @@ import java.util.*; import java.util.Map.Entry; import java.util.stream.Collectors; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import net.minecraft.inventory.InventoryCrafting; @@ -233,8 +234,9 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU public IAEItemStack injectItems( final IAEItemStack input, final Actionable type, final IActionSource src ) { - if( !( input instanceof IAEItemStack ) ) - { + // also stop accepting items when the job is complete, i.e. to prevent re-insertion when pushing out + // items during storeItems + if (input == null || isComplete) { return input; } @@ -431,6 +433,11 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU AELog.crafting( LOG_MARK_AS_COMPLETE, logStack ); } + // Waiting for can potentially contain items at this point, if the user has a 64xplank->64xbutton processing + // recipe for example, but only requested 1xbutton. We just ignore the rest since it will be dumped + // back into the network inventory regardless. For this to work it's important that injectItems in this CPU + // does not accept any further items if isComplete is true. + this.waitingFor.resetStatus(); this.remainingItemCount = 0; this.startItemCount = 0; this.lastTime = 0; @@ -828,6 +835,7 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU private void storeItems() { + Preconditions.checkState(isComplete, "CPU should be complete to prevent re-insertion when dumping items"); final IGrid g = this.getGrid(); if( g == null ) @@ -838,23 +846,24 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU final IStorageGrid sg = g.getCache( IStorageGrid.class ); final IMEInventory ii = sg.getInventory( AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ) ); - for( IAEItemStack is : this.inventory.getItemList() ) + IItemList itemList = this.inventory.getItemList(); + for( IAEItemStack is : itemList ) { - is = this.inventory.extractItems( is.copy(), Actionable.MODULATE, this.machineSrc ); + this.postChange( is, this.machineSrc ); + IAEItemStack remainder = ii.injectItems( is.copy(), Actionable.MODULATE, this.machineSrc ); - if( is != null ) + // The network was unable to receive all of the items, i.e. no or not enough storage space left + if( remainder != null ) { - this.postChange( is, this.machineSrc ); - is = ii.injectItems( is, Actionable.MODULATE, this.machineSrc ); + is.setStackSize( remainder.getStackSize() ); } - - if( is != null ) + else { - this.inventory.injectItems( is, Actionable.MODULATE, this.machineSrc ); + is.reset(); } } - if( this.inventory.getItemList().isEmpty() ) + if( itemList.isEmpty() ) { this.inventory = new MECraftingInventory(); } diff --git a/src/main/java/appeng/util/item/AEItemStack.java b/src/main/java/appeng/util/item/AEItemStack.java index b2047f4bc..b1d5ee4e0 100644 --- a/src/main/java/appeng/util/item/AEItemStack.java +++ b/src/main/java/appeng/util/item/AEItemStack.java @@ -1,6 +1,6 @@ /* * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. + * Copyright (c) 2013 - 2020, 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 @@ -18,7 +18,6 @@ package appeng.util.item; - import java.util.List; import java.util.Objects; import java.util.Optional; @@ -27,347 +26,291 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import io.netty.buffer.ByteBuf; - import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.common.network.ByteBufUtils; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.items.ItemHandlerHelper; -import appeng.api.AEApi; import appeng.api.config.FuzzyMode; import appeng.api.storage.IStorageChannel; import appeng.api.storage.channels.IItemStorageChannel; import appeng.api.storage.data.IAEItemStack; +import appeng.core.Api; import appeng.util.Platform; +public final class AEItemStack extends AEStack implements IAEItemStack { + private static final String NBT_STACKSIZE = "Cnt"; + private static final String NBT_REQUESTABLE = "Req"; + private static final String NBT_CRAFTABLE = "Craft"; -public final class AEItemStack extends AEStack implements IAEItemStack -{ - private AESharedItemStack sharedStack; - private Optional oreReference; + private final AESharedItemStack sharedStack; + private Optional oreReference; - @SideOnly( Side.CLIENT ) - private String displayName; - @SideOnly( Side.CLIENT ) - private List tooltip; - @SideOnly( Side.CLIENT ) - private ResourceLocation uniqueID; + @SideOnly(Side.CLIENT) + private String displayName; + @SideOnly(Side.CLIENT) + private List tooltip; - private AEItemStack( final AEItemStack is ) - { - this.setStackSize( is.getStackSize() ); - this.setCraftable( is.isCraftable() ); - this.setCountRequestable( is.getCountRequestable() ); - this.sharedStack = is.sharedStack; - this.oreReference = is.oreReference; - } + private AEItemStack(final AEItemStack is) { + this.setStackSize(is.getStackSize()); + this.setCraftable(is.isCraftable()); + this.setCountRequestable(is.getCountRequestable()); + this.sharedStack = is.sharedStack; + this.oreReference = is.oreReference; + } - private AEItemStack( final AESharedItemStack is, long size ) - { - this.sharedStack = is; - this.setStackSize( size ); - this.setCraftable( false ); - this.setCountRequestable( 0 ); - this.oreReference = OreHelper.INSTANCE.getOre( is.getDefinition() ); - } + private AEItemStack(final AESharedItemStack is, long size) { + this.sharedStack = is; + this.setStackSize(size); + this.setCraftable(false); + this.setCountRequestable(0); + this.oreReference = OreHelper.INSTANCE.getOre( is.getDefinition() ); + } - @Nullable - public static AEItemStack fromItemStack( @Nonnull final ItemStack stack ) - { - if( stack.isEmpty() ) - { - return null; - } + @Nullable + public static AEItemStack fromItemStack(@Nonnull final ItemStack stack) { + if (stack.isEmpty()) { + return null; + } - return new AEItemStack( AEItemStackRegistry.getRegisteredStack( stack ), stack.getCount() ); - } + return new AEItemStack(AEItemStackRegistry.getRegisteredStack(stack), stack.getCount()); + } - public static IAEItemStack fromNBT( final NBTTagCompound i ) - { - if( i == null ) - { - return null; - } + public static IAEItemStack fromNBT(final NBTTagCompound i) { + if (i == null) { + return null; + } - final ItemStack itemstack = new ItemStack( i ); - if( itemstack.isEmpty() ) - { - return null; - } + final ItemStack itemstack = new ItemStack(i); + final AEItemStack item = AEItemStack.fromItemStack(itemstack); + if (item == null) { + return null; + } - final AEItemStack item = AEItemStack.fromItemStack( itemstack ); - item.setStackSize( i.getLong( "Cnt" ) ); - item.setCountRequestable( i.getLong( "Req" ) ); - item.setCraftable( i.getBoolean( "Craft" ) ); - return item; - } + item.setStackSize(i.getLong(NBT_STACKSIZE)); + item.setCountRequestable(i.getLong(NBT_REQUESTABLE)); + item.setCraftable(i.getBoolean(NBT_CRAFTABLE)); + return item; + } - @Override - public void writeToNBT( final NBTTagCompound i ) - { - this.getDefinition().writeToNBT( i ); - i.setLong( "Cnt", this.getStackSize() ); - i.setLong( "Req", this.getCountRequestable() ); - i.setBoolean( "Craft", this.isCraftable() ); - } + @Override + public void writeToNBT(final NBTTagCompound i) { + final NBTTagCompound itemStack = new NBTTagCompound(); + this.getDefinition().writeToNBT(itemStack); - public static AEItemStack fromPacket( final ByteBuf data ) - { - final byte mask = data.readByte(); - final byte stackType = (byte) ( ( mask & 0x0C ) >> 2 ); - final byte countReqType = (byte) ( ( mask & 0x30 ) >> 4 ); - final boolean isCraftable = ( mask & 0x40 ) > 0; + i.setLong(NBT_STACKSIZE, this.getStackSize()); + i.setLong(NBT_REQUESTABLE, this.getCountRequestable()); + i.setBoolean(NBT_CRAFTABLE, this.isCraftable()); + } - final ItemStack itemstack = new ItemStack( ByteBufUtils.readTag( data ) ); - final long stackSize = getPacketValue( stackType, data ); - final long countRequestable = getPacketValue( countReqType, data ); + public static AEItemStack fromPacket( final ByteBuf data ) + { + final byte mask = data.readByte(); + final byte stackType = (byte) ( ( mask & 0x0C ) >> 2 ); + final byte countReqType = (byte) ( ( mask & 0x30 ) >> 4 ); + final boolean isCraftable = ( mask & 0x40 ) > 0; - if( itemstack.isEmpty() ) - { - return null; - } + final ItemStack itemstack = new ItemStack( ByteBufUtils.readTag( data ) ); + final long stackSize = getPacketValue( stackType, data ); + final long countRequestable = getPacketValue( countReqType, data ); - final AEItemStack item = new AEItemStack( AEItemStackRegistry.getRegisteredStack( itemstack ), stackSize ); - item.setCountRequestable( countRequestable ); - item.setCraftable( isCraftable ); - return item; - } + if( itemstack.isEmpty() ) + { + return null; + } - @Override - public void writeToPacket( final ByteBuf i ) - { - final byte mask = (byte) ( ( this.getType( this.getStackSize() ) << 2 ) | ( this - .getType( this.getCountRequestable() ) << 4 ) | ( (byte) ( this.isCraftable() ? 1 : 0 ) << 6 ) | ( this.hasTagCompound() ? 1 : 0 ) << 7 ); + final AEItemStack item = new AEItemStack( AEItemStackRegistry.getRegisteredStack( itemstack ), stackSize ); + item.setCountRequestable( countRequestable ); + item.setCraftable( isCraftable ); + return item; + } - i.writeByte( mask ); - ByteBufUtils.writeTag( i, this.getDefinition().serializeNBT() ); - this.putPacketValue( i, this.getStackSize() ); - this.putPacketValue( i, this.getCountRequestable() ); - } + @Override + public void writeToPacket( final ByteBuf i ) + { + final byte mask = (byte) ( ( this.getType( this.getStackSize() ) << 2 ) | ( this + .getType( this.getCountRequestable() ) << 4 ) | ( (byte) ( this.isCraftable() ? 1 : 0 ) << 6 ) | ( this.hasTagCompound() ? 1 : 0 ) << 7 ); - @Override - public void add( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + i.writeByte( mask ); + ByteBufUtils.writeTag( i, this.getDefinition().serializeNBT() ); + this.putPacketValue( i, this.getStackSize() ); + this.putPacketValue( i, this.getCountRequestable() ); + } - this.incStackSize( option.getStackSize() ); - this.setCountRequestable( this.getCountRequestable() + option.getCountRequestable() ); - this.setCraftable( this.isCraftable() || option.isCraftable() ); - } + @Override + public void add(final IAEItemStack option) { + if (option == null) { + return; + } - @Override - public boolean fuzzyComparison( final IAEItemStack other, final FuzzyMode mode ) - { - if( mode == FuzzyMode.IGNORE_ALL && OreHelper.INSTANCE.sameOre( this, other ) ) - { - return true; - } + this.incStackSize(option.getStackSize()); + this.setCountRequestable(this.getCountRequestable() + option.getCountRequestable()); + this.setCraftable(this.isCraftable() || option.isCraftable()); + } - final ItemStack itemStack = this.getDefinition(); - final ItemStack otherStack = other.getDefinition(); + @Override + public boolean fuzzyComparison(final IAEItemStack other, final FuzzyMode mode) { + final ItemStack itemStack = this.getDefinition(); + final ItemStack otherStack = other.getDefinition(); - return this.fuzzyItemStackComparison( itemStack, otherStack, mode ); - } + return this.fuzzyItemStackComparison(itemStack, otherStack, mode); + } - @Override - public IAEItemStack copy() - { - return new AEItemStack( this ); - } + @Override + public IAEItemStack copy() { + return new AEItemStack(this); + } - @Override - public boolean isItem() - { - return true; - } + @Override + public boolean isItem() + { + return true; + } - @Override - public boolean isFluid() - { - return false; - } + @Override + public boolean isFluid() + { + return false; + } - @Override - public IStorageChannel getChannel() - { - return AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ); - } + @Override + public IStorageChannel getChannel() { + return Api.INSTANCE.storage().getStorageChannel(IItemStorageChannel.class); + } - @Override - public ItemStack createItemStack() - { - return ItemHandlerHelper.copyStackWithSize( this.getDefinition(), (int) Math.min( Integer.MAX_VALUE, this.getStackSize() ) ); - } + @Override + public ItemStack createItemStack() { + return ItemHandlerHelper.copyStackWithSize(this.getDefinition(), + (int) Math.min(Integer.MAX_VALUE, this.getStackSize())); + } - @Override - public Item getItem() - { - return this.getDefinition().getItem(); - } + @Override + public Item getItem() { + return this.getDefinition().getItem(); + } - @Override - public int getItemDamage() - { - return this.sharedStack.getItemDamage(); - } + @Override + public int getItemDamage() { + return this.sharedStack.getItemDamage(); + } - @Override - public boolean sameOre( final IAEItemStack is ) - { - return OreHelper.INSTANCE.sameOre( this, is ); - } + @Override + public boolean sameOre( final IAEItemStack is ) + { + return OreHelper.INSTANCE.sameOre( this, is ); + } - @Override - public boolean isSameType( final IAEItemStack otherStack ) - { - if( otherStack == null ) - { - return false; - } + @Override + public boolean isSameType(final IAEItemStack otherStack) { + if (otherStack == null) { + return false; + } - return Objects.equals( this.sharedStack, ( (AEItemStack) otherStack ).sharedStack ); - } + return Objects.equals(this.sharedStack, ((AEItemStack) otherStack).sharedStack); + } - @Override - public boolean isSameType( final ItemStack otherStack ) - { - if( otherStack.isEmpty() ) - { - return false; - } - int oldSize = otherStack.getCount(); + @Override + public boolean isSameType(final ItemStack otherStack) { + if (otherStack.isEmpty()) { + return false; + } + int oldSize = otherStack.getCount(); - otherStack.setCount( 1 ); - boolean ret = ItemStack.areItemStacksEqual( this.getDefinition(), otherStack ); - otherStack.setCount( oldSize ); + otherStack.setCount(1); + boolean ret = ItemStack.areItemStacksEqual(this.getDefinition(), otherStack); + otherStack.setCount(oldSize); - return ret; - } + return ret; + } - @Override - public int hashCode() - { - return this.sharedStack.hashCode(); - } + @Override + public int hashCode() { + return this.sharedStack.hashCode(); + } - @Override - public boolean equals( final Object ia ) - { - if( ia instanceof AEItemStack ) - { - return this.isSameType( (AEItemStack) ia ); - } - else if( ia instanceof ItemStack ) - { - return this.isSameType( (ItemStack) ia ); - } - return false; - } + @Override + public boolean equals(final Object ia) { + if (ia instanceof AEItemStack) { + return this.isSameType((AEItemStack) ia); + } else if (ia instanceof ItemStack) { + // this actually breaks the equals contract (being equals to unrelated classes) + return equals((ItemStack) ia); + } + return false; + } - @Override - public String toString() - { - return this.getStackSize() + "x" + this.getDefinition().getItem().getUnlocalizedName() + "@" + this.getDefinition().getItemDamage(); - } + @Override + public boolean equals(final ItemStack is) { + return this.isSameType(is); + } - @SideOnly( Side.CLIENT ) - public List getToolTip() - { - if( this.tooltip == null ) - { - this.tooltip = Platform.getTooltip( this.asItemStackRepresentation() ); - } - return this.tooltip; - } + @Override + public String toString() { + return this.getStackSize() + "x" + this.getDefinition().getItem().getRegistryName(); + } - @SideOnly( Side.CLIENT ) - public String getDisplayName() - { - if( this.displayName == null ) - { - this.displayName = Platform.getItemDisplayName( this.asItemStackRepresentation() ); - } - return this.displayName; - } + @SideOnly(Side.CLIENT) + public List getToolTip() { + if (this.tooltip == null) { + this.tooltip = Platform.getTooltip(this.asItemStackRepresentation()); + } + return this.tooltip; + } - @SideOnly( Side.CLIENT ) - public String getModID() - { - if( this.uniqueID == null ) - { - this.uniqueID = Item.REGISTRY.getNameForObject( this.getDefinition().getItem() ); - } + @SideOnly(Side.CLIENT) + public String getDisplayName() { + if (this.displayName == null) { + this.displayName = Platform.getItemDisplayName(this.asItemStackRepresentation()); + } + return this.displayName; + } - if( this.uniqueID == null ) - { - return "** Null"; - } + @SideOnly(Side.CLIENT) + public String getModID() { + return this.getDefinition().getItem().getRegistryName().getResourceDomain(); + } - return this.uniqueID.getResourceDomain() == null ? "** Null" : this.uniqueID.getResourceDomain(); - } + public Optional getOre() + { + return this.oreReference; + } - public Optional getOre() - { - return this.oreReference; - } + @Override + public boolean hasTagCompound() { + return this.getDefinition().hasTagCompound(); + } - @Override - public boolean hasTagCompound() - { - return this.getDefinition().hasTagCompound(); - } + @Override + public ItemStack asItemStackRepresentation() { + return this.getDefinition().copy(); + } - @Override - public ItemStack asItemStackRepresentation() - { - return this.getDefinition().copy(); - } + @Override + public ItemStack getDefinition() { + return this.sharedStack.getDefinition(); + } - @Override - public ItemStack getDefinition() - { - return this.sharedStack.getDefinition(); - } + AESharedItemStack getSharedStack() { + return this.sharedStack; + } - AESharedItemStack getSharedStack() - { - return this.sharedStack; - } + private boolean fuzzyItemStackComparison(ItemStack a, ItemStack b, FuzzyMode mode) { + if (a.getItem() == b.getItem() && a.getItem().isDamageable()) { + if (mode == FuzzyMode.IGNORE_ALL) { + return true; + } else if (mode == FuzzyMode.PERCENT_99) { + return a.getItemDamage() > 1 == b.getItemDamage() > 1; + } else { + final float percentDamageOfA = (float) a.getItemDamage() / a.getMaxDamage(); + final float percentDamageOfB = (float) b.getItemDamage() / b.getMaxDamage(); - private boolean fuzzyItemStackComparison( ItemStack a, ItemStack b, FuzzyMode mode ) - { - if( a.getItem() == b.getItem() ) - { - if( a.getItem().isDamageable() ) - { - if( mode == FuzzyMode.IGNORE_ALL ) - { - return true; - } - else if( mode == FuzzyMode.PERCENT_99 ) - { - return ( a.getItemDamage() > 1 ) == ( b.getItemDamage() > 1 ); - } - else - { - final float percentDamageOfA = (float) a.getItemDamage() / (float) a.getMaxDamage(); - final float percentDamageOfB = (float) b.getItemDamage() / (float) b.getMaxDamage(); - - return ( percentDamageOfA > mode.breakPoint ) == ( percentDamageOfB > mode.breakPoint ); - } - } - - return a.getMetadata() == b.getMetadata(); - } - - return false; - } + return percentDamageOfA > mode.breakPoint == percentDamageOfB > mode.breakPoint; + } + } + return false; + } } diff --git a/src/main/java/appeng/util/item/AEItemStackRegistry.java b/src/main/java/appeng/util/item/AEItemStackRegistry.java index 2bea02b88..0fce27e36 100644 --- a/src/main/java/appeng/util/item/AEItemStackRegistry.java +++ b/src/main/java/appeng/util/item/AEItemStackRegistry.java @@ -23,7 +23,6 @@ package appeng.util.item; - import java.lang.ref.WeakReference; import java.util.WeakHashMap; @@ -31,56 +30,34 @@ import javax.annotation.Nonnull; import net.minecraft.item.ItemStack; -import appeng.util.Platform; +public final class AEItemStackRegistry { + private static final WeakHashMap> REGISTRY = new WeakHashMap<>(); + private AEItemStackRegistry() { + } -public final class AEItemStackRegistry -{ - private static final WeakHashMap> SERVER_REGISTRY = new WeakHashMap<>(); - private static final WeakHashMap> CLIENT_REGISTRY = new WeakHashMap<>(); + static synchronized AESharedItemStack getRegisteredStack(final @Nonnull ItemStack itemStack) { + if (itemStack.isEmpty()) { + throw new IllegalArgumentException("stack cannot be empty"); + } - private AEItemStackRegistry() - { - } + int oldStackSize = itemStack.getCount(); + itemStack.setCount(1); - private static WeakHashMap> registry() - { - if( Platform.isClient() ) - { - return CLIENT_REGISTRY; - } - else - { - return SERVER_REGISTRY; - } - } + AESharedItemStack search = new AESharedItemStack(itemStack); + WeakReference weak = REGISTRY.get(search); + AESharedItemStack ret = null; - static synchronized AESharedItemStack getRegisteredStack( final @Nonnull ItemStack itemStack ) - { - if( itemStack.isEmpty() ) - { - throw new IllegalArgumentException( "stack cannot be empty" ); - } + if (weak != null) { + ret = weak.get(); + } - int oldStackSize = itemStack.getCount(); - itemStack.setCount( 1 ); + if (ret == null) { + ret = new AESharedItemStack(itemStack.copy()); + REGISTRY.put(ret, new WeakReference<>(ret)); + } + itemStack.setCount(oldStackSize); - AESharedItemStack search = new AESharedItemStack( itemStack ); - WeakReference weak = registry().get( search ); - AESharedItemStack ret = null; - - if( weak != null ) - { - ret = weak.get(); - } - - if( ret == null ) - { - ret = new AESharedItemStack( itemStack.copy() ); - registry().put( ret, new WeakReference<>( ret ) ); - } - itemStack.setCount( oldStackSize ); - - return ret; - } + return ret; + } } diff --git a/src/main/java/appeng/util/item/AESharedItemStack.java b/src/main/java/appeng/util/item/AESharedItemStack.java index 5ce9e784d..81686915b 100644 --- a/src/main/java/appeng/util/item/AESharedItemStack.java +++ b/src/main/java/appeng/util/item/AESharedItemStack.java @@ -18,263 +18,76 @@ package appeng.util.item; - import java.util.Objects; import com.google.common.base.Preconditions; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NBTTagCompound; -import appeng.api.config.FuzzyMode; +final class AESharedItemStack { + private final ItemStack itemStack; + private final int itemId; + private final int itemDamage; + private final int hashCode; -final class AESharedItemStack implements Comparable -{ - private static final NBTTagCompound LOW_TAG = new NBTTagCompound(); - private static final NBTTagCompound HIGH_TAG = new NBTTagCompound(); + public AESharedItemStack(final ItemStack itemStack) { + this(itemStack, itemStack.getItemDamage()); + } - private final ItemStack itemStack; - private final int itemId; - private final int itemDamage; - private final int hashCode; + /** + * A constructor to explicitly set the damage value and not fetch it from the {@link ItemStack} + * + * @param itemStack The {@link ItemStack} to filter + * @param damage The damage of the item + */ + private AESharedItemStack(ItemStack itemStack, int damage) { + this.itemStack = itemStack; + this.itemId = Item.getIdFromItem(itemStack.getItem()); + this.itemDamage = damage; - public AESharedItemStack( final ItemStack itemStack ) - { - this.itemStack = itemStack; - this.itemId = Item.getIdFromItem( itemStack.getItem() ); - this.itemDamage = itemStack.getItemDamage(); - this.hashCode = this.makeHashCode(); - } + // Ensure this is always called last. + this.hashCode = this.makeHashCode(); + } - Bounds getBounds( final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - return new Bounds( this.itemStack, fuzzy, ignoreMeta ); - } + ItemStack getDefinition() { + return this.itemStack; + } - ItemStack getDefinition() - { - return this.itemStack; - } + int getItemDamage() { + return this.itemDamage; + } - int getItemDamage() - { - return this.itemDamage; - } + @Override + public int hashCode() { + return this.hashCode; + } - int getItemID() - { - return this.itemId; - } + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AESharedItemStack)) { + return false; + } - @Override - public int hashCode() - { - return this.hashCode; - } + final AESharedItemStack other = (AESharedItemStack) obj; + Preconditions.checkState(this.itemStack.getCount() == 1, "ItemStack#getCount() has to be 1"); + Preconditions.checkArgument(other.getDefinition().getCount() == 1, "ItemStack#getCount() has to be 1"); - @Override - public boolean equals( final Object obj ) - { - if( obj instanceof AESharedItemStack ) - { - final AESharedItemStack other = (AESharedItemStack) obj; + if (this.itemStack == other.itemStack) { + return true; + } + return ItemStack.areItemStacksEqual(this.itemStack, other.itemStack); + } - Preconditions.checkState( this.itemStack.getCount() == 1, "ItemStack#getCount() has to be 1" ); - Preconditions.checkArgument( other.getDefinition().getCount() == 1, "ItemStack#getCount() has to be 1" ); + private int makeHashCode() { + return Objects.hash( + this.itemId, + this.itemDamage, + this.itemStack.hasTagCompound() ? this.itemStack.getTagCompound() : 0); + } - if( this.itemStack == other.itemStack ) - { - return true; - } - return ItemStack.areItemStacksEqual( this.itemStack, other.itemStack ); - } - return false; - } - - @Override - public int compareTo( final AESharedItemStack b ) - { - Preconditions.checkState( this.itemStack.getCount() == 1, "ItemStack#getCount() has to be 1" ); - Preconditions.checkArgument( b.getDefinition().getCount() == 1, "ItemStack#getCount() has to be 1" ); - - if( this.itemStack == b.getDefinition() ) - { - return 0; - } - - final int id = this.itemId - b.itemId; - if( id != 0 ) - { - return id; - } - - final int damageValue = this.itemDamage - b.itemDamage; - if( damageValue != 0 ) - { - return damageValue; - } - - final int nbt = this.compareNBT( b.getDefinition() ); - if( nbt != 0 ) - { - return nbt; - } - - if( !this.itemStack.areCapsCompatible( b.getDefinition() ) ) - { - return System.identityHashCode( this.itemStack ) - System.identityHashCode( b.getDefinition() ); - } - return 0; - } - - private int compareNBT( final ItemStack b ) - { - if( this.itemStack.getTagCompound() == b.getTagCompound() ) - { - return 0; - } - if( this.itemStack.getTagCompound() == LOW_TAG || b.getTagCompound() == HIGH_TAG ) - { - return -1; - } - if( this.itemStack.getTagCompound() == HIGH_TAG || b.getTagCompound() == LOW_TAG ) - { - return 1; - } - return System.identityHashCode( this.itemStack.getTagCompound() ) - System.identityHashCode( b.getTagCompound() ); - } - - private int makeHashCode() - { - return Objects.hash( this.itemId, this.itemDamage, this.itemStack.hasTagCompound() ? this.itemStack.getTagCompound() : 0 ); - } - - /** - * Creates the lower and upper bounds for a specific shared itemstack. - */ - public static final class Bounds - { - /** - * Bounds enforced by {@link ItemStack#isEmpty()} - */ - private static final int MIN_DAMAGE_VALUE = 0; - private static final int MAX_DAMAGE_VALUE = 65535; - - private final AESharedItemStack lower; - private final AESharedItemStack upper; - - public Bounds( final ItemStack stack, final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - Preconditions.checkState( !stack.isEmpty(), "ItemStack#isEmpty() has to be false" ); - Preconditions.checkState( stack.getCount() == 1, "ItemStack#getCount() has to be 1" ); - - final NBTTagCompound tag = stack.hasTagCompound() ? stack.getTagCompound() : null; - - this.lower = this.makeLowerBound( stack, tag, fuzzy, ignoreMeta ); - this.upper = this.makeUpperBound( stack, tag, fuzzy, ignoreMeta ); - } - - public AESharedItemStack lower() - { - return this.lower; - } - - public AESharedItemStack upper() - { - return this.upper; - } - - private AESharedItemStack makeLowerBound( final ItemStack itemStack, final NBTTagCompound tag, final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - final ItemStack newDef = itemStack.copy(); - - if( ignoreMeta ) - { - newDef.setItemDamage( MIN_DAMAGE_VALUE ); - newDef.setTagCompound( tag ); - } - else - { - if( newDef.getItem().isDamageable() ) - { - if (itemStack.getMaxDamage() == 0) - { - newDef.setItemDamage(itemStack.getItemDamage() ); - } - else if ( fuzzy == FuzzyMode.IGNORE_ALL ) - { - newDef.setItemDamage( MIN_DAMAGE_VALUE ); - } - else if( fuzzy == FuzzyMode.PERCENT_99 ) - { - if( itemStack.getItemDamage() == MIN_DAMAGE_VALUE ) - { - newDef.setItemDamage( MIN_DAMAGE_VALUE ); - } - else - { - newDef.setItemDamage( MIN_DAMAGE_VALUE + 1 ); - } - } - else - { - final int breakpoint = fuzzy.calculateBreakPoint( itemStack.getMaxDamage() ); - final int damage = breakpoint <= itemStack.getItemDamage() ? breakpoint : 0; - newDef.setItemDamage( damage ); - } - } - newDef.setTagCompound( LOW_TAG ); - } - - return new AESharedItemStack( newDef ); - } - - private AESharedItemStack makeUpperBound( final ItemStack itemStack, final NBTTagCompound tag, final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - final ItemStack newDef = itemStack.copy(); - - if( ignoreMeta ) - { - newDef.setItemDamage( MAX_DAMAGE_VALUE ); - newDef.setTagCompound( tag ); - } - else - { - if( newDef.getItem().isDamageable() ) - { - if (itemStack.getMaxDamage() == 0) - { - newDef.setItemDamage(itemStack.getItemDamage() ); - } - else if ( fuzzy == FuzzyMode.IGNORE_ALL ) - { - newDef.setItemDamage( itemStack.getMaxDamage() + 1 ); - } - else if( fuzzy == FuzzyMode.PERCENT_99 ) - { - if( itemStack.getItemDamage() == MIN_DAMAGE_VALUE ) - { - newDef.setItemDamage( MIN_DAMAGE_VALUE ); - } - else - { - newDef.setItemDamage( itemStack.getMaxDamage() + 1 ); - } - } - else - { - final int breakpoint = fuzzy.calculateBreakPoint( itemStack.getMaxDamage() ); - final int damage = itemStack.getItemDamage() < breakpoint ? breakpoint - 1 : itemStack.getMaxDamage() + 1; - newDef.setItemDamage( damage ); - } - } - newDef.setTagCompound( HIGH_TAG ); - } - - return new AESharedItemStack( newDef ); - } - - } } diff --git a/src/main/java/appeng/util/item/AEStack.java b/src/main/java/appeng/util/item/AEStack.java index f2b42351d..0003b9e0e 100644 --- a/src/main/java/appeng/util/item/AEStack.java +++ b/src/main/java/appeng/util/item/AEStack.java @@ -18,169 +18,152 @@ package appeng.util.item; - +import appeng.api.storage.data.IAEStack; import io.netty.buffer.ByteBuf; -import appeng.api.storage.data.IAEStack; +public abstract class AEStack> implements IAEStack { -public abstract class AEStack> implements IAEStack -{ + private boolean isCraftable; + private long stackSize; + private long countRequestable; - private boolean isCraftable; - private long stackSize; - private long countRequestable; + protected static long getPacketValue( final byte type, final ByteBuf tag ) + { + if( type == 0 ) + { + long l = tag.readByte(); + l -= Byte.MIN_VALUE; + return l; + } + else if( type == 1 ) + { + long l = tag.readShort(); + l -= Short.MIN_VALUE; + return l; + } + else if( type == 2 ) + { + long l = tag.readInt(); + l -= Integer.MIN_VALUE; + return l; + } - protected static long getPacketValue( final byte type, final ByteBuf tag ) - { - if( type == 0 ) - { - long l = tag.readByte(); - l -= Byte.MIN_VALUE; - return l; - } - else if( type == 1 ) - { - long l = tag.readShort(); - l -= Short.MIN_VALUE; - return l; - } - else if( type == 2 ) - { - long l = tag.readInt(); - l -= Integer.MIN_VALUE; - return l; - } + return tag.readLong(); + } - return tag.readLong(); - } + @Override + public long getStackSize() { + return this.stackSize; + } - @Override - public long getStackSize() - { - return this.stackSize; - } + @Override + public T setStackSize(final long ss) { + this.stackSize = ss; + return (T) this; + } - @Override - public StackType setStackSize( final long ss ) - { - this.stackSize = ss; - return (StackType) this; - } + @Override + public long getCountRequestable() { + return this.countRequestable; + } - @Override - public long getCountRequestable() - { - return this.countRequestable; - } + @Override + public T setCountRequestable(final long countRequestable) { + this.countRequestable = countRequestable; + return (T) this; + } - @Override - public StackType setCountRequestable( final long countRequestable ) - { - this.countRequestable = countRequestable; - return (StackType) this; - } + @Override + public boolean isCraftable() { + return this.isCraftable; + } - @Override - public boolean isCraftable() - { - return this.isCraftable; - } + @Override + public T setCraftable(final boolean isCraftable) { + this.isCraftable = isCraftable; + return (T) this; + } - @Override - public StackType setCraftable( final boolean isCraftable ) - { - this.isCraftable = isCraftable; - return (StackType) this; - } + @Override + public T reset() { + this.stackSize = 0; + this.setCountRequestable(0); + this.setCraftable(false); + return (T) this; + } - @Override - public StackType reset() - { - this.stackSize = 0; - // priority = Integer.MIN_VALUE; - this.setCountRequestable( 0 ); - this.setCraftable( false ); - return (StackType) this; - } + @Override + public T empty() { + final T dup = this.copy(); + dup.reset(); + return dup; + } - @Override - public StackType empty() - { - final StackType dup = this.copy(); - dup.reset(); - return dup; - } + @Override + public boolean isMeaningful() { + return this.stackSize != 0 || this.countRequestable > 0 || this.isCraftable; + } - @Override - public boolean isMeaningful() - { - return this.stackSize != 0 || this.countRequestable > 0 || this.isCraftable; - } + @Override + public void incStackSize(final long i) { + this.stackSize += i; + } - @Override - public void incStackSize( final long i ) - { - this.stackSize += i; - } + @Override + public void decStackSize(final long i) { + this.stackSize -= i; + } - @Override - public void decStackSize( final long i ) - { - this.stackSize -= i; - } + @Override + public void incCountRequestable(final long i) { + this.countRequestable += i; + } - @Override - public void incCountRequestable( final long i ) - { - this.countRequestable += i; - } + @Override + public void decCountRequestable(final long i) { + this.countRequestable -= i; + } - @Override - public void decCountRequestable( final long i ) - { - this.countRequestable -= i; - } + protected byte getType( final long num ) + { + if( num <= 255 ) + { + return 0; + } + else if( num <= 65535 ) + { + return 1; + } + else if( num <= 4294967295L ) + { + return 2; + } + else + { + return 3; + } + } - protected byte getType( final long num ) - { - if( num <= 255 ) - { - return 0; - } - else if( num <= 65535 ) - { - return 1; - } - else if( num <= 4294967295L ) - { - return 2; - } - else - { - return 3; - } - } + protected abstract boolean hasTagCompound(); - protected abstract boolean hasTagCompound(); - - protected void putPacketValue( final ByteBuf tag, final long num ) - { - if( num <= 255 ) - { - tag.writeByte( (byte) ( num + Byte.MIN_VALUE ) ); - } - else if( num <= 65535 ) - { - tag.writeShort( (short) ( num + Short.MIN_VALUE ) ); - } - else if( num <= 4294967295L ) - { - tag.writeInt( (int) ( num + Integer.MIN_VALUE ) ); - } - else - { - tag.writeLong( num ); - } - } + protected void putPacketValue( final ByteBuf tag, final long num ) + { + if( num <= 255 ) + { + tag.writeByte( (byte) ( num + Byte.MIN_VALUE ) ); + } + else if( num <= 65535 ) + { + tag.writeShort( (short) ( num + Short.MIN_VALUE ) ); + } + else if( num <= 4294967295L ) + { + tag.writeInt( (int) ( num + Integer.MIN_VALUE ) ); + } + else + { + tag.writeLong( num ); + } + } } diff --git a/src/main/java/appeng/util/item/FuzzyItemVariantList.java b/src/main/java/appeng/util/item/FuzzyItemVariantList.java new file mode 100644 index 000000000..1208f65c9 --- /dev/null +++ b/src/main/java/appeng/util/item/FuzzyItemVariantList.java @@ -0,0 +1,190 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2021, TeamAppliedEnergistics, 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 . + */ + +package appeng.util.item; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; + +import com.google.common.base.Preconditions; + +import net.minecraft.item.ItemStack; + +import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap; + +import appeng.api.config.FuzzyMode; +import appeng.api.storage.data.IAEItemStack; + +/** + * This variant list is optimized for damageable items, and supports selecting durability ranges with + * {@link #findFuzzy(IAEItemStack, FuzzyMode)}. + */ +class FuzzyItemVariantList extends ItemVariantList { + + static final SharedStackComparator COMPARATOR = new SharedStackComparator(); + + // NOTE: We only use Object as they key here so we can pass our special DamageBounds to the subMap method. + // We NEVER put any keys in this map that are not AESharedItemStacks. + private final Object2ObjectSortedMap records = new Object2ObjectAVLTreeMap<>(COMPARATOR); + + @Override + public Collection findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy) { + ItemStack itemStack = filter.getDefinition(); + + ItemDamageBound lowerBound = makeLowerBound(itemStack, fuzzy); + ItemDamageBound upperBound = makeUpperBound(itemStack, fuzzy); + Preconditions.checkState(lowerBound.itemDamage > upperBound.itemDamage); + + return this.records.subMap(lowerBound, upperBound).values(); + } + + @SuppressWarnings("unchecked") + @Override + Map getRecords() { + // We ensure on our end that we NEVER use anything but AESharedItemStack as the key in this map + return (Map) (Object) this.records; + } + + static class ItemDamageBound { + final int itemDamage; + + public ItemDamageBound(int itemDamage) { + this.itemDamage = itemDamage; + } + } + + /** + * This comparator creates a strict and total ordering over all {@link AESharedItemStack} of the same item. To + * support selecting ranges of durability, it is defined for type {@link Object} and also accepts + * {@link ItemDamageBound} as an argument to compare against. + */ + static class SharedStackComparator implements Comparator { + @Override + public int compare(Object a, Object b) { + // Either argument can either be a damage bound or a shared item stack + // Since we never put damage bounds into the map as keys, only one + // of the two arguments can possibly be a bound + ItemDamageBound boundA = null; + AESharedItemStack stackA = null; + int itemDamageA; + if (a instanceof ItemDamageBound) { + boundA = (ItemDamageBound) a; + itemDamageA = boundA.itemDamage; + } else { + stackA = (AESharedItemStack) a; + itemDamageA = stackA.getItemDamage(); + } + ItemDamageBound boundB = null; + AESharedItemStack stackB = null; + int itemDamageB; + if (b instanceof ItemDamageBound) { + boundB = (ItemDamageBound) b; + itemDamageB = boundB.itemDamage; + } else { + stackB = (AESharedItemStack) b; + itemDamageB = stackB.getItemDamage(); + } + + // When either argument is a damage bound, we just compare the damage values because it is used + // only to get a certain damage range out of the map. + if (boundA != null || boundB != null) { + return Integer.compare(itemDamageB, itemDamageA); + } + + ItemStack itemStackA = stackA.getDefinition(); + ItemStack itemStackB = stackB.getDefinition(); + Preconditions.checkState(itemStackA.getCount() == 1, "ItemStack#getCount() has to be 1"); + Preconditions.checkArgument(itemStackB.getCount() == 1, "ItemStack#getCount() has to be 1"); + + if (itemStackA == itemStackB) { + return 0; + } + + // Damaged items are sorted before undamaged items + final int damageValue = Integer.compare(itemDamageB, itemDamageA); + if (damageValue != 0) { + return damageValue; + } + + // As a final tie breaker, order by the object identity of the item stack + // While this will order seemingly at random, we only need the order of + // damage values to be predictable, while still having to satisfy the + // complete order requirements of the sorted map + return Long.compare(System.identityHashCode(itemStackA), System.identityHashCode(itemStackB)); + } + } + + /** + * Minecraft reverses the damage values. So anything with a damage of 0 is undamaged and increases the more damaged + * the item is. + *

+ * Further the used subMap follows [MAX_DAMAGE, MIN_DAMAGE), so to include undamaged items, we have to start with a + * lower damage value than 0, while it is fine to use {@link ItemStack#getMaxDamage()} for the upper bound. + */ + private static final int MIN_DAMAGE_VALUE = -1; + + /* + * Keep in mind that the stack order is from most damaged to least damaged, so this lower bound will actually be a + * higher number than the upper bound. + */ + static ItemDamageBound makeLowerBound(final ItemStack stack, final FuzzyMode fuzzy) + { + Preconditions.checkState( stack.getItem().isDamageable(), "Item#isDamageable() has to be true" ); + + int damage; + if( fuzzy == FuzzyMode.IGNORE_ALL ) + { + if( stack.getMaxDamage() == 0 ) + { + damage = stack.getItemDamage(); + } + else + { + damage = stack.getMaxDamage(); + } + } + else + { + final int breakpoint = fuzzy.calculateBreakPoint( stack.getMaxDamage() ); + damage = stack.getItemDamage() <= breakpoint ? breakpoint : stack.getMaxDamage(); + } + + return new ItemDamageBound( damage ); + } + + /* + * Keep in mind that the stack order is from most damaged to least damaged, so this upper bound will actually be a + * lower number than the lower bound. It also is exclusive. + */ + static ItemDamageBound makeUpperBound(final ItemStack stack, final FuzzyMode fuzzy) { + Preconditions.checkState(stack.getItem().isDamageable(), "Item#isDamageable() has to be true"); + + int damage; + if (fuzzy == FuzzyMode.IGNORE_ALL) { + damage = MIN_DAMAGE_VALUE; + } else { + final int breakpoint = fuzzy.calculateBreakPoint(stack.getMaxDamage()); + damage = stack.getItemDamage() <= breakpoint ? MIN_DAMAGE_VALUE : breakpoint; + } + + return new ItemDamageBound(damage); + } + +} diff --git a/src/main/java/appeng/util/item/ItemList.java b/src/main/java/appeng/util/item/ItemList.java index c8e22b796..e7a4672a2 100644 --- a/src/main/java/appeng/util/item/ItemList.java +++ b/src/main/java/appeng/util/item/ItemList.java @@ -1,6 +1,6 @@ /* * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved. + * Copyright (c) 2013 - 2020, 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 @@ -18,212 +18,196 @@ package appeng.util.item; - -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.Iterator; -import java.util.NavigableMap; -import java.util.concurrent.ConcurrentSkipListMap; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicInteger; -import net.minecraftforge.oredict.OreDictionary; +import net.minecraft.item.Item; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import appeng.api.config.FuzzyMode; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IItemList; -import appeng.util.item.AESharedItemStack.Bounds; +public final class ItemList implements IItemList { -public final class ItemList implements IItemList -{ + private final Reference2ObjectMap records = new Reference2ObjectOpenHashMap<>(); + /** + * We increment this version field everytime an attempt to mutate this item list (or potentially one of its + * sub-lists) is made. Iterators will copy the version when they are created and compare it against the current + * version whenever they advance to trigger a {@link ConcurrentModificationException}. + */ + private final AtomicInteger version = new AtomicInteger(0); - private final NavigableMap records = new ConcurrentSkipListMap<>(); + @Override + public IAEItemStack findPrecise(final IAEItemStack itemStack) { + if (itemStack == null) { + return null; + } - @Override - public void add( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + ItemVariantList record = this.records.get(itemStack.getItem()); + return record != null ? record.findPrecise(itemStack) : null; + } - final IAEItemStack st = this.records.get( ( (AEItemStack) option ).getSharedStack() ); + @Override + public Collection findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy) { + if (filter == null) { + return Collections.emptyList(); + } - if( st != null ) - { - st.add( option ); - return; - } + ItemVariantList record = this.records.get(filter.getItem()); + return record != null ? record.findFuzzy(filter, fuzzy) : Collections.emptyList(); + } - final IAEItemStack opt = option.copy(); + @Override + public boolean isEmpty() { + return !this.iterator().hasNext(); + } - this.putItemRecord( opt ); - } + @Override + public void add(final IAEItemStack itemStack) { + version.incrementAndGet(); - @Override - public IAEItemStack findPrecise( final IAEItemStack itemStack ) - { - if( itemStack == null ) - { - return null; - } + if (itemStack == null) { + return; + } - return this.records.get( ( (AEItemStack) itemStack ).getSharedStack() ); - } + this.getOrCreateRecord(itemStack.getItem()).add(itemStack); + } - @Override - public Collection findFuzzy( final IAEItemStack filter, final FuzzyMode fuzzy ) - { - if( filter == null ) - { - return Collections.emptyList(); - } + @Override + public void addStorage(final IAEItemStack itemStack) { + version.incrementAndGet(); - final AEItemStack ais = (AEItemStack) filter; + if (itemStack == null) { + return; + } - return ais.getOre().map( or -> - { - if( or.getAEEquivalents().size() == 1 ) - { - final IAEItemStack is = or.getAEEquivalents().get( 0 ); + this.getOrCreateRecord(itemStack.getItem()).addStorage(itemStack); + } - return this.findFuzzyDamage( is, fuzzy, is.getItemDamage() == OreDictionary.WILDCARD_VALUE ); - } - else - { - final Collection output = new ArrayList<>(); + @Override + public void addCrafting(final IAEItemStack itemStack) { + version.incrementAndGet(); - for( final IAEItemStack is : or.getAEEquivalents() ) - { - output.addAll( this.findFuzzyDamage( is, fuzzy, is.getItemDamage() == OreDictionary.WILDCARD_VALUE ) ); - } + if (itemStack == null) { + return; + } - return output; - } - } ).orElse( this.findFuzzyDamage( ais, fuzzy, false ) ); - } + this.getOrCreateRecord(itemStack.getItem()).addCrafting(itemStack); + } - @Override - public boolean isEmpty() - { - return !this.iterator().hasNext(); - } + @Override + public void addRequestable(final IAEItemStack itemStack) { + version.incrementAndGet(); - @Override - public void addStorage( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + if (itemStack == null) { + return; + } - final IAEItemStack st = this.records.get( ( (AEItemStack) option ).getSharedStack() ); + this.getOrCreateRecord(itemStack.getItem()).addRequestable(itemStack); + } - if( st != null ) - { - st.incStackSize( option.getStackSize() ); - return; - } + @Override + public IAEItemStack getFirstItem() { + for (final IAEItemStack stackType : this) { + return stackType; + } - final IAEItemStack opt = option.copy(); + return null; + } - this.putItemRecord( opt ); - } + @Override + public int size() { + int size = 0; + for (ItemVariantList entry : records.values()) { + size += entry.size(); + } - /* - * public void clean() { Iterator i = iterator(); while (i.hasNext()) { StackType AEI = - * i.next(); if ( !AEI.isMeaningful() ) i.remove(); } } - */ + return size; + } - @Override - public void addCrafting( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + @Override + public Iterator iterator() { + return new ChainedIterator(this.records.values().iterator(), version); + } - final IAEItemStack st = this.records.get( ( (AEItemStack) option ).getSharedStack() ); + @Override + public void resetStatus() { + for (final IAEItemStack i : this) { + i.reset(); + } + } - if( st != null ) - { - st.setCraftable( true ); - return; - } + private ItemVariantList getOrCreateRecord(Item item) { + return this.records.computeIfAbsent(item, this::makeRecordMap); + } - final IAEItemStack opt = option.copy(); - opt.setStackSize( 0 ); - opt.setCraftable( true ); + private ItemVariantList makeRecordMap(Item item) { + if (item.isDamageable()) { + return new FuzzyItemVariantList(); + } else { + return new NormalItemVariantList(); + } + } - this.putItemRecord( opt ); - } + /** + * Iterates over multiple item lists as if they were one list. + */ + private static class ChainedIterator implements Iterator { - @Override - public void addRequestable( final IAEItemStack option ) - { - if( option == null ) - { - return; - } + private final AtomicInteger parentVersion; + private final int version; + private final Iterator parent; + private Iterator next; - final IAEItemStack st = this.records.get( ( (AEItemStack) option ).getSharedStack() ); + public ChainedIterator(Iterator iterator, AtomicInteger parentVersion) { + this.parent = iterator; + this.parentVersion = parentVersion; + this.version = parentVersion.get(); + this.ensureItems(); + } - if( st != null ) - { - st.setCountRequestable( st.getCountRequestable() + option.getCountRequestable() ); - return; - } + @Override + public boolean hasNext() { + return next != null && next.hasNext(); + } - final IAEItemStack opt = option.copy(); - opt.setStackSize( 0 ); - opt.setCraftable( false ); - opt.setCountRequestable( option.getCountRequestable() ); + @Override + public IAEItemStack next() { + if (this.next == null) { + throw new NoSuchElementException(); + } + if (this.version != this.parentVersion.get()) { + throw new ConcurrentModificationException(); + } - this.putItemRecord( opt ); - } + IAEItemStack result = this.next.next(); + this.ensureItems(); + return result; + } - @Override - public IAEItemStack getFirstItem() - { - for( final IAEItemStack stackType : this ) - { - return stackType; - } + private void ensureItems() { + if (hasNext()) { + return; // Still items left in the current one + } - return null; - } + // Find the next iterator willing to return some items... + while (this.parent.hasNext()) { + this.next = this.parent.next().iterator(); - @Override - public int size() - { - return this.records.size(); - } + if (this.next.hasNext()) { + return; // Found one! + } + } - @Override - public Iterator iterator() - { - return new MeaningfulItemIterator<>( this.records.values().iterator() ); - } - - @Override - public void resetStatus() - { - for( final IAEItemStack i : this ) - { - i.reset(); - } - } - - private IAEItemStack putItemRecord( final IAEItemStack itemStack ) - { - return this.records.put( ( (AEItemStack) itemStack ).getSharedStack(), itemStack ); - } - - private Collection findFuzzyDamage( final IAEItemStack filter, final FuzzyMode fuzzy, final boolean ignoreMeta ) - { - final AEItemStack itemStack = (AEItemStack) filter; - final Bounds bounds = itemStack.getSharedStack().getBounds( fuzzy, ignoreMeta ); - - return this.records.subMap( bounds.lower(), true, bounds.upper(), true ).descendingMap().values(); - } + // No more items + this.next = null; + } + } } diff --git a/src/main/java/appeng/util/item/ItemVariantList.java b/src/main/java/appeng/util/item/ItemVariantList.java new file mode 100644 index 000000000..01af28937 --- /dev/null +++ b/src/main/java/appeng/util/item/ItemVariantList.java @@ -0,0 +1,118 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2021, TeamAppliedEnergistics, 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 . + */ + +package appeng.util.item; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import appeng.api.config.FuzzyMode; +import appeng.api.storage.data.IAEItemStack; + +/** + * Stores variants of a single type of {@link net.minecraft.item.Item}, i.e. versions with different durability, or + * different NBT or capabilities. + */ +abstract class ItemVariantList { + + public void add(final IAEItemStack option) { + final IAEItemStack st = this.getRecords().get(((AEItemStack) option).getSharedStack()); + + if (st != null) { + st.add(option); + return; + } + + final IAEItemStack opt = option.copy(); + + this.putItemRecord(opt); + } + + public IAEItemStack findPrecise(final IAEItemStack itemStack) { + return this.getRecords().get(((AEItemStack) itemStack).getSharedStack()); + } + + public void addStorage(final IAEItemStack option) { + final IAEItemStack st = this.getRecords().get(((AEItemStack) option).getSharedStack()); + + if (st != null) { + st.incStackSize(option.getStackSize()); + return; + } + + final IAEItemStack opt = option.copy(); + + this.putItemRecord(opt); + } + + public void addCrafting(final IAEItemStack option) { + final IAEItemStack st = this.getRecords().get(((AEItemStack) option).getSharedStack()); + + if (st != null) { + st.setCraftable(true); + return; + } + + final IAEItemStack opt = option.copy(); + opt.setStackSize(0); + opt.setCraftable(true); + + this.putItemRecord(opt); + } + + public void addRequestable(final IAEItemStack option) { + final IAEItemStack st = this.getRecords().get(((AEItemStack) option).getSharedStack()); + + if (st != null) { + st.setCountRequestable(st.getCountRequestable() + option.getCountRequestable()); + return; + } + + final IAEItemStack opt = option.copy(); + opt.setStackSize(0); + opt.setCraftable(false); + opt.setCountRequestable(option.getCountRequestable()); + + this.putItemRecord(opt); + } + + public int size() { + int size = 0; + for (IAEItemStack entry : getRecords().values()) { + if (entry.isMeaningful()) { + size++; + } + } + + return size; + } + + public Iterator iterator() { + return new MeaningfulItemIterator<>(this.getRecords().values()); + } + + private void putItemRecord(final IAEItemStack itemStack) { + this.getRecords().put(((AEItemStack) itemStack).getSharedStack(), itemStack); + } + + abstract Map getRecords(); + + public abstract Collection findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy); + +} diff --git a/src/main/java/appeng/util/item/MeaningfulItemIterator.java b/src/main/java/appeng/util/item/MeaningfulItemIterator.java index 7d7c8b016..2d84f7933 100644 --- a/src/main/java/appeng/util/item/MeaningfulItemIterator.java +++ b/src/main/java/appeng/util/item/MeaningfulItemIterator.java @@ -1,6 +1,6 @@ /* * This file is part of Applied Energistics 2. - * Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved. + * Copyright (c) 2021, TeamAppliedEnergistics, 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 @@ -18,59 +18,53 @@ package appeng.util.item; - +import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import appeng.api.storage.data.IAEItemStack; +/** + * This iterator will only return items from a collection that are meaningful (w.r.t. + * {@link IAEItemStack#isMeaningful()}. Items that are not meaningful are automatically removed from the collection as + * it is being iterated. + */ +public class MeaningfulItemIterator implements Iterator { + private final Iterator parent; + private T next; -public class MeaningfulItemIterator implements Iterator -{ + public MeaningfulItemIterator(final Collection collection) { + this.parent = collection.iterator(); + this.next = seekNext(); + } - private final Iterator parent; - private T next; + @Override + public boolean hasNext() { + return this.next != null; + } - public MeaningfulItemIterator( final Iterator iterator ) - { - this.parent = iterator; - } + @Override + public T next() { + if (this.next == null) { + throw new NoSuchElementException(); + } - @Override - public boolean hasNext() - { - while( this.parent.hasNext() ) - { - this.next = this.parent.next(); + T result = this.next; + this.next = this.seekNext(); + return result; + } - if( this.next.isMeaningful() ) - { - return true; - } - else - { - this.parent.remove(); // self cleaning :3 - } - } + private T seekNext() { + while (this.parent.hasNext()) { + T item = this.parent.next(); - this.next = null; - return false; - } + if (item.isMeaningful()) { + return item; + } else { + this.parent.remove(); + } + } - @Override - public T next() - { - if( this.next == null ) - { - throw new NoSuchElementException(); - } - - return this.next; - } - - @Override - public void remove() - { - this.parent.remove(); - } + return null; + } } diff --git a/src/main/java/appeng/util/item/NormalItemVariantList.java b/src/main/java/appeng/util/item/NormalItemVariantList.java new file mode 100644 index 000000000..89a92a155 --- /dev/null +++ b/src/main/java/appeng/util/item/NormalItemVariantList.java @@ -0,0 +1,51 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2021, TeamAppliedEnergistics, 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 . + */ + +package appeng.util.item; + +import java.util.Collection; +import java.util.Map; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; + +import appeng.api.config.FuzzyMode; +import appeng.api.storage.data.IAEItemStack; + +/** + * This variant list is optimized for items that cannot be damaged and thus do not support querying durability ranges + * via {@link #findFuzzy(IAEItemStack, FuzzyMode)}. + */ +class NormalItemVariantList extends ItemVariantList { + + private final Reference2ObjectMap records = new Reference2ObjectOpenHashMap<>(); + + @Override + Map getRecords() { + return this.records; + } + + /** + * For items that do not support durability, we just return all variants to a fuzzy search. + */ + @Override + public Collection findFuzzy(IAEItemStack filter, FuzzyMode fuzzy) { + return this.getRecords().values(); + } + +}