heavily tweaked auto-crafting

This commit is contained in:
PrototypeTrousers
2022-03-09 23:08:47 -03:00
parent 146d3f5628
commit 036f4e1c5b
11 changed files with 571 additions and 387 deletions
+16 -11
View File
@@ -65,6 +65,7 @@ public class CraftingJob implements Runnable, ICraftingJob
private final Object monitor = new Object();
private final Stopwatch tickSpreadingWatch = Stopwatch.createUnstarted();
private final Stopwatch craftingTreeWatch = Stopwatch.createUnstarted();
private final ICraftingGrid cc;
private CraftingTreeNode tree;
private final IAEItemStack output;
private boolean simulate = false;
@@ -90,7 +91,7 @@ public class CraftingJob implements Runnable, ICraftingJob
this.callback = callback;
final ICraftingGrid cc = grid.getCache( ICraftingGrid.class );
this.cc = grid.getCache( ICraftingGrid.class );
final GridStorageCache sg = grid.getCache( IStorageGrid.class );
this.original = new MECraftingInventory( sg.getExtractableList( actionSrc ) );
@@ -159,13 +160,13 @@ public class CraftingJob implements Runnable, ICraftingJob
TickHandler.INSTANCE.registerCraftingSimulation( this.world, this );
this.handlePausing();
craftingTreeWatch.start();
final MECraftingInventory craftingInventory = new MECraftingInventory( this.original, true, false, true );
craftingInventory.ignore( this.output );
this.availableCheck = new MECraftingInventory( this.original, false, false, false );
craftingTreeWatch.start();
this.getTree().request( craftingInventory, this.output.getStackSize(), this.actionSrc );
craftingTreeWatch.stop();
this.getTree().dive( this );
for( final String s : this.opsAndMultiplier.keySet() )
@@ -174,8 +175,14 @@ public class CraftingJob implements Runnable, ICraftingJob
AELog.crafting( s + " * " + ti.times + " = " + ( ti.perOp * ti.times ) );
}
craftingTreeWatch.stop();
this.logCraftingJob( "real, success", craftingTreeWatch );
if( actionSrc.player().isPresent() )
{
this.logCraftingJob( "simulated, success", craftingTreeWatch );
}
else
{
this.logCraftingJob( "real, success", craftingTreeWatch );
}
}
catch( final CraftBranchFailure e )
{
@@ -185,14 +192,14 @@ public class CraftingJob implements Runnable, ICraftingJob
{
if( actionSrc.player().isPresent() )
{
craftingTreeWatch.reset().start();
final MECraftingInventory craftingInventory = new MECraftingInventory( this.original, true, false, true );
craftingInventory.ignore( this.output );
this.availableCheck = new MECraftingInventory( this.original, false, false, false );
this.getTree().setSimulate();
this.availableCheck = new MECraftingInventory( this.original, false, false, false );
craftingTreeWatch.reset().start();
this.getTree().request( craftingInventory, this.output.getStackSize(), this.actionSrc );
craftingTreeWatch.stop();
this.getTree().dive( this );
for( final String s : this.opsAndMultiplier.keySet() )
@@ -201,12 +208,10 @@ public class CraftingJob implements Runnable, ICraftingJob
AELog.crafting( s + " * " + ti.times + " = " + ( ti.perOp * ti.times ) );
}
craftingTreeWatch.stop();
this.logCraftingJob( "simulate", craftingTreeWatch );
this.logCraftingJob( "simulated, failed", craftingTreeWatch );
}
else
{
craftingTreeWatch.stop();
this.logCraftingJob( "real, failed", craftingTreeWatch );
}
}
@@ -24,7 +24,6 @@ import java.util.Collection;
import java.util.List;
import appeng.api.config.FuzzyMode;
import com.google.common.collect.Lists;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.world.World;
@@ -53,14 +52,14 @@ public class CraftingTreeNode
private final IAEItemStack what;
// what are the crafting patterns for this?
private final ArrayList<CraftingTreeProcess> nodes = new ArrayList<>();
private final ICraftingGrid cc;
private final int depth;
private int bytes = 0;
private boolean canEmit = false;
private long missing = 0;
private long howManyEmitted = 0;
private boolean exhausted = false;
private boolean sim;
public CraftingTreeNode( final ICraftingGrid cc, final CraftingJob job, final IAEItemStack wat, final CraftingTreeProcess par, final int slot, final int depth )
{
this.what = wat;
@@ -68,9 +67,18 @@ public class CraftingTreeNode
this.slot = slot;
this.world = job.getWorld();
this.job = job;
this.sim = false;
this.cc = cc;
this.depth = depth;
this.canEmit = cc.canEmitFor( this.what );
}
public void addNode()
{
if( !nodes.isEmpty() )
{
return;
}
if( this.canEmit )
{
@@ -89,19 +97,42 @@ public class CraftingTreeNode
IAEItemStack request( final MECraftingInventory inv, long l, final IActionSource src ) throws CraftBranchFailure, InterruptedException
{
addNode();
this.job.handlePausing();
if( this.canEmit )
{
final IAEItemStack wat = this.what.copy();
wat.setStackSize( l );
this.howManyEmitted = wat.getStackSize();
this.bytes += wat.getStackSize();
return wat;
}
final IItemList<IAEItemStack> inventoryList = inv.getItemList();
final List<IAEItemStack> thingsUsed = new ArrayList<>();
this.what.setStackSize( l );
if( this.getSlot() >= 0 && this.parent != null && this.parent.details.isCraftable() )
{
Collection<IAEItemStack> itemList = new ArrayList<>();
if( this.parent.getContainerItems() != null && !this.parent.getContainerItems().findFuzzy( this.what, FuzzyMode.IGNORE_ALL ).isEmpty() )
if( this.what.getItem().hasContainerItem( this.what.getDefinition() ) )
{
itemList = inventoryList.findFuzzy( this.what, FuzzyMode.IGNORE_ALL );
itemList.addAll( inventoryList.findFuzzy( this.what, FuzzyMode.IGNORE_ALL ) );
if( this.parent.details.canSubstitute() )
{
for( IAEItemStack is : inventoryList )
{
if( is.fuzzyComparison( this.what, FuzzyMode.IGNORE_ALL ) )
{
itemList.add( is );
}
}
}
}
else
{
@@ -110,6 +141,10 @@ public class CraftingTreeNode
{
itemList.add( item );
}
if( this.parent.details.canSubstitute() )
{
itemList.addAll( inventoryList.findFuzzy( this.what, FuzzyMode.IGNORE_ALL ) );
}
}
for( IAEItemStack fuzz : itemList )
@@ -172,17 +207,6 @@ public class CraftingTreeNode
}
}
if( this.canEmit )
{
final IAEItemStack wat = this.what.copy();
wat.setStackSize( l );
this.howManyEmitted = wat.getStackSize();
this.bytes += wat.getStackSize();
return wat;
}
this.exhausted = true;
if( this.nodes.size() == 1 )
@@ -192,11 +216,9 @@ public class CraftingTreeNode
while ( pro.possible && l > 0 )
{
final IAEItemStack madeWhat = pro.getAmountCrafted( this.what );
pro.request( inv, pro.getTimes( l, madeWhat.getStackSize() ), src );
madeWhat.setStackSize( l );
final IAEItemStack available = inv.extractItems( madeWhat, Actionable.MODULATE, src );
if( available != null )
@@ -257,10 +279,10 @@ public class CraftingTreeNode
}
}
if( this.sim )
if( job.isSimulation() )
{
this.missing += l;
this.bytes += l;
this.missing += l;
final IAEItemStack rv = this.what.copy();
rv.setStackSize( l );
return rv;
@@ -306,7 +328,6 @@ public class CraftingTreeNode
void setSimulate()
{
this.sim = true;
this.missing = 0;
this.bytes = 0;
this.used.resetStatus();
@@ -20,15 +20,13 @@ package appeng.crafting;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import appeng.api.config.FuzzyMode;
import appeng.me.cache.CraftingGridCache;
import appeng.util.item.AEItemStack;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.objects.Object2LongArrayMap;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
@@ -52,11 +50,11 @@ public class CraftingTreeProcess
private final CraftingJob job;
private final Object2LongArrayMap<CraftingTreeNode> nodes = new Object2LongArrayMap<>();
private final int depth;
private final ICraftingGrid cc;
private final World world;
boolean possible = true;
private long crafts = 0;
private IItemList<IAEItemStack> containerItems;
private boolean limitQty;
private List<IAEItemStack> containerItemsList;
private long bytes = 0;
public CraftingTreeProcess( final ICraftingGrid cc, final CraftingJob job, final ICraftingPatternDetails details, final CraftingTreeNode craftingTreeNode, final int depth )
@@ -65,20 +63,22 @@ public class CraftingTreeProcess
this.details = details;
this.job = job;
this.depth = depth;
final World world = job.getWorld();
this.cc = cc;
this.world = job.getWorld();
}
public void addProcess()
{
if( !nodes.isEmpty() )
{
return;
}
final IAEItemStack[] list = details.getInputs();
for( final IAEItemStack part : details.getCondensedInputs() )
{
if( part.getItem().hasContainerItem( part.getDefinition() ) )
{
if( containerItems == null )
{
containerItems = AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList();
containerItemsList = new ArrayList<>();
}
containerItems.add( part );
this.limitQty = true;
//break;
}
@@ -96,13 +96,33 @@ public class CraftingTreeProcess
if( part.equals( comparePart ) )
{
boolean isPartContainer = false;
if( containerItems != null && !containerItems.findFuzzy( list[x], FuzzyMode.IGNORE_ALL ).isEmpty() )
if( part.getItem().hasContainerItem( part.getDefinition() ) )
{
part = list[x];
isPartContainer = true;
}
long wantedSize = part.getStackSize();
if( isPartContainer && wantedSize > 0 )
{
if( details.canSubstitute() && cc.getCraftingFor( part, details, x, world ).isEmpty() )
{
IItemList<IAEItemStack> aa = ( (CraftingGridCache) cc ).getAvailableItems( AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList() );
for( IAEItemStack is : aa )
{
if( is.fuzzyComparison( part, FuzzyMode.IGNORE_ALL ) )
{
wantedSize -= 1;
this.nodes.put( new CraftingTreeNode( cc, job, is.copy().setStackSize( 1 ), this, x, depth + 1 ), 1 );
if( wantedSize == 0 )
{
break;
}
}
}
}
}
if( !isPartContainer )
{
IAEItemStack found = job.checkAvailable( part );
@@ -270,11 +290,6 @@ public class CraftingTreeProcess
}
}
IItemList<IAEItemStack> getContainerItems()
{
return this.containerItems;
}
boolean notRecursive()
{
return this.parent == null || this.parent.notRecursive();
@@ -291,28 +306,34 @@ public class CraftingTreeProcess
void request( final MECraftingInventory inv, final long amountOfTimes, final IActionSource src ) throws CraftBranchFailure, InterruptedException
{
addProcess();
this.job.handlePausing();
List<IAEItemStack> containerItems = null;
// request and remove inputs...
for( final Entry<CraftingTreeNode, Long> entry : this.nodes.object2LongEntrySet() )
{
final IAEItemStack stack = entry.getKey().request( inv, entry.getValue() * amountOfTimes, src );
if( containerItems != null && !this.containerItems.findFuzzy( stack, FuzzyMode.IGNORE_ALL ).isEmpty() )
if( stack.getItem().hasContainerItem( stack.getDefinition() ) )
{
final ItemStack is = Platform.getContainerItem( stack.createItemStack() );
final IAEItemStack o = AEItemStack.fromItemStack( is );
if( o != null )
{
if( containerItems == null )
{
containerItems = new ArrayList<>();
}
this.bytes++;
containerItemsList.add( o );
containerItems.add( o );
}
}
}
if( containerItems != null )
{
for( IAEItemStack i : containerItemsList )
for( IAEItemStack i : containerItems )
{
inv.injectItems( i, Actionable.MODULATE, src );
}
@@ -138,7 +138,7 @@ public class MECraftingInventory implements IMEInventory<IAEItemStack>
this.localCache = new ItemListIgnoreCrafting<>( AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList() );
for( final IAEItemStack is : target.getStorageList() )
{
this.localCache.add( target.extractItems( is, Actionable.SIMULATE, src ) );
this.localCache.add( target.extractItems( is.copy().setStackSize( Long.MAX_VALUE ), Actionable.SIMULATE, src ) );
}
this.par = null;
+54 -32
View File
@@ -21,6 +21,7 @@ package appeng.helpers;
import java.util.*;
import gregtech.common.items.MetaTool;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
@@ -247,7 +248,7 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable<Patter
final TestStatus result = this.getStatus( slotIndex, i );
switch( result )
switch ( result )
{
case ACCEPT:
return true;
@@ -266,9 +267,11 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable<Patter
this.testFrame.setInventorySlotContents( slotIndex, i );
// If we cannot substitute, the items must match exactly
if (!canSubstitute && slotIndex < inputs.length) {
if (!inputs[slotIndex].isSameType(i)) {
this.markItemAs(slotIndex, i, TestStatus.DECLINE);
if( ( !( i.getItem().isDamageable() || i.getItem() instanceof MetaTool ) && !canSubstitute ) && slotIndex < inputs.length )
{
if( !inputs[slotIndex].isSameType( i ) )
{
this.markItemAs( slotIndex, i, TestStatus.DECLINE );
return false;
}
}
@@ -326,23 +329,26 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable<Patter
}
@Override
public List<IAEItemStack> getSubstituteInputs(int slot) {
if (this.inputs[slot] == null) {
public List<IAEItemStack> getSubstituteInputs( int slot )
{
if( this.inputs[slot] == null )
{
return Collections.emptyList();
}
return this.substituteInputs.computeIfAbsent(slot, value -> {
ItemStack[] matchingStacks = getRecipeIngredient(slot).getMatchingStacks();
List<IAEItemStack> itemList = new ArrayList<>(matchingStacks.length + 1);
for (ItemStack matchingStack : matchingStacks) {
itemList.add(AEItemStack.fromItemStack(matchingStack));
return this.substituteInputs.computeIfAbsent( slot, value -> {
ItemStack[] matchingStacks = getRecipeIngredient( slot ).getMatchingStacks();
List<IAEItemStack> itemList = new ArrayList<>( matchingStacks.length + 1 );
for( ItemStack matchingStack : matchingStacks )
{
itemList.add( AEItemStack.fromItemStack( matchingStack ) );
}
// Ensure that the specific item put in by the user is at the beginning,
// so that it takes precedence over substitutions
itemList.add(0, this.inputs[slot]);
itemList.add( 0, this.inputs[slot] );
return itemList;
});
} );
}
/**
@@ -352,31 +358,40 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable<Patter
* ingredient list will be condensed to the actual recipe's grid size. In addition, in our 3x3 grid, the user can
* shift the actual recipe input to the right and down.
*/
private Ingredient getRecipeIngredient(int slot) {
private Ingredient getRecipeIngredient( int slot )
{
if (standardRecipe instanceof IShapedRecipe ) {
if( standardRecipe instanceof IShapedRecipe )
{
IShapedRecipe shapedRecipe = (IShapedRecipe) standardRecipe;
return getShapedRecipeIngredient(slot, shapedRecipe.getRecipeWidth());
} else {
return getShapelessRecipeIngredient(slot);
return getShapedRecipeIngredient( slot, shapedRecipe.getRecipeWidth() );
}
else
{
return getShapelessRecipeIngredient( slot );
}
}
private Ingredient getShapedRecipeIngredient(int slot, int recipeWidth) {
private Ingredient getShapedRecipeIngredient( int slot, int recipeWidth )
{
// Compute the offset of the user's input vs. crafting grid origin
// Which is >0 if they have empty rows above or to the left of their input
int topOffset = 0;
if (inputs[0] == null && inputs[1] == null && inputs[2] == null) {
if( inputs[0] == null && inputs[1] == null && inputs[2] == null )
{
topOffset++; // First row is fully empty
if (inputs[3] == null && inputs[4] == null && inputs[5] == null) {
if( inputs[3] == null && inputs[4] == null && inputs[5] == null )
{
topOffset++; // Second row is fully empty
}
}
int leftOffset = 0;
if (inputs[0] == null && inputs[3] == null && inputs[6] == null) {
if( inputs[0] == null && inputs[3] == null && inputs[6] == null )
{
leftOffset++; // First column is fully empty
if (inputs[1] == null && inputs[4] == null && inputs[7] == null) {
if( inputs[1] == null && inputs[4] == null && inputs[7] == null )
{
leftOffset++; // Second column is fully empty
}
}
@@ -390,32 +405,37 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable<Patter
NonNullList<Ingredient> ingredients = standardRecipe.getIngredients();
if (ingredientIndex < 0 || ingredientIndex > ingredients.size()) {
if( ingredientIndex < 0 || ingredientIndex > ingredients.size() )
{
return Ingredient.EMPTY;
}
return ingredients.get(ingredientIndex);
return ingredients.get( ingredientIndex );
}
private Ingredient getShapelessRecipeIngredient(int slot) {
private Ingredient getShapelessRecipeIngredient( int slot )
{
// We map the list of *filled* sparse inputs to the shapeless (ergo unordered)
// ingredients. While these do not actually correspond to each other,
// since both lists have the same length, the mapping is at least stable.
int ingredientIndex = 0;
for (int i = 0; i < slot; i++) {
if (inputs[i] != null) {
for( int i = 0; i < slot; i++ )
{
if( inputs[i] != null )
{
ingredientIndex++;
}
}
NonNullList<Ingredient> ingredients = standardRecipe.getIngredients();
if (ingredientIndex < ingredients.size()) {
return ingredients.get(ingredientIndex);
if( ingredientIndex < ingredients.size() )
{
return ingredients.get( ingredientIndex );
}
return Ingredient.EMPTY;
}
@Override
public ItemStack getOutput( final InventoryCrafting craftingInv, final World w )
{
@@ -517,7 +537,9 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable<Patter
private enum TestStatus
{
ACCEPT, DECLINE, TEST
ACCEPT,
DECLINE,
TEST
}
private static final class TestLookup
@@ -19,7 +19,6 @@
package appeng.parts.misc;
import javax.annotation.Nullable;
import appeng.api.AEApi;
import appeng.api.config.AccessRestriction;
import appeng.api.config.Actionable;
@@ -147,43 +146,64 @@ class ItemHandlerAdapter implements IMEInventory<IAEItemStack>, IBaseMonitor<IAE
}
ItemStack extracted;
int stackSizeCurrentSlot = stackInInventorySlot.getCount();
int remainingCurrentSlot = Math.min( remainingSize, stackSizeCurrentSlot );
// We have to loop here because according to the docs, the handler shouldn't return a stack with size >
// maxSize, even if we request more. So even if it returns a valid stack, it might have more stuff.
do
if( !simulate )
{
extracted = this.itemHandler.extractItem( i, remainingCurrentSlot, simulate );
int stackSizeCurrentSlot = stackInInventorySlot.getCount();
int remainingCurrentSlot = Math.min( remainingSize, stackSizeCurrentSlot );
// We have to loop here because according to the docs, the handler shouldn't return a stack with size >
// maxSize, even if we request more. So even if it returns a valid stack, it might have more stuff.
do
{
extracted = this.itemHandler.extractItem( i, remainingCurrentSlot, false );
if( !extracted.isEmpty() )
{
if( extracted.getCount() > remainingCurrentSlot )
{
// Something broke. It should never return more than we requested...
// We're going to silently eat the remainder
AELog.warn( "Mod that provided item handler %s is broken. Returned %s items while only requesting %d.", this.itemHandler.getClass().getName(), extracted.toString(), remainingCurrentSlot );
extracted.setCount( remainingCurrentSlot );
}
// We're just gonna use the first stack we get our hands on as the template for the rest.
// In case some stupid itemhandler (aka forge) returns an internal state we have to do a second
// expensive copy again.
if( gathered.isEmpty() )
{
gathered = extracted;
}
else
{
gathered.grow( extracted.getCount() );
}
remainingCurrentSlot -= extracted.getCount();
}
} while ( !extracted.isEmpty() && remainingCurrentSlot > 0 );
remainingSize -= stackSizeCurrentSlot - remainingCurrentSlot;
}
else
{
extracted = this.itemHandler.extractItem( i, remainingSize, true );
if( !extracted.isEmpty() )
{
if( extracted.getCount() > remainingCurrentSlot )
{
// Something broke. It should never return more than we requested...
// We're going to silently eat the remainder
AELog.warn( "Mod that provided item handler %s is broken. Returned %s items while only requesting %d.", this.itemHandler.getClass().getName(), extracted.toString(), remainingCurrentSlot );
extracted.setCount( remainingCurrentSlot );
}
// We're just gonna use the first stack we get our hands on as the template for the rest.
// In case some stupid itemhandler (aka forge) returns an internal state we have to do a second
// expensive copy again.
extracted.setCount( Math.min( stackInInventorySlot.getCount(), remainingSize ) );
if( gathered.isEmpty() )
{
gathered = extracted.copy();
gathered = extracted;
}
else
{
gathered.grow( extracted.getCount() );
}
remainingCurrentSlot -= extracted.getCount();
remainingSize -= extracted.getCount();
}
} while ( !extracted.isEmpty() && remainingCurrentSlot > 0 );
remainingSize -= stackSizeCurrentSlot - remainingCurrentSlot;
// Done?
}
if( remainingSize <= 0 )
{
break;
@@ -27,6 +27,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.primitives.Ints;
import gregtech.common.items.MetaTool;
import io.netty.buffer.ByteBuf;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
@@ -370,20 +371,44 @@ public final class AEItemStack extends AEStack<IAEItemStack> implements IAEItemS
private boolean fuzzyItemStackComparison( ItemStack a, ItemStack b, FuzzyMode mode )
{
if( a.getItem() == b.getItem() && a.getItem().isDamageable() )
if( a.getItem() == b.getItem() && ( a.getItem().isDamageable() || a.getItem() instanceof MetaTool ) )
{
if( mode == FuzzyMode.IGNORE_ALL )
{
return true;
if( a.getItem().isDamageable() )
{
return true;
}
else if( a.getItem() instanceof MetaTool )
{
return a.getItemDamage() == b.getItemDamage();
}
}
else if( mode == FuzzyMode.PERCENT_99 )
{
return a.getItemDamage() > 1 == b.getItemDamage() > 1;
if( a.getItem().isDamageable() )
{
return a.getItemDamage() > 1 == b.getItemDamage() > 1;
}
else if( a.getItem() instanceof MetaTool )
{
return ( (MetaTool) a.getItem() ).getItemDamage( a ) == ( (MetaTool) b.getItem() ).getItemDamage( b );
}
}
else
{
final float percentDamageOfA = (float) a.getItemDamage() / a.getMaxDamage();
final float percentDamageOfB = (float) b.getItemDamage() / b.getMaxDamage();
float percentDamageOfA = 0;
float percentDamageOfB = 0;
if( a.getItem().isDamageable() )
{
percentDamageOfA = (float) a.getItemDamage() / a.getMaxDamage();
percentDamageOfB = (float) b.getItemDamage() / b.getMaxDamage();
}
else if( a.getItem() instanceof MetaTool )
{
percentDamageOfA = (float) ( (MetaTool) a.getItem() ).getItemDamage( a ) / ( (MetaTool) a.getItem() ).getMaxItemDamage( a );
percentDamageOfB = (float) ( (MetaTool) b.getItem() ).getItemDamage( b ) / ( (MetaTool) b.getItem() ).getMaxItemDamage( b );
}
return percentDamageOfA > mode.breakPoint == percentDamageOfB > mode.breakPoint;
}
@@ -25,7 +25,9 @@ import java.util.Map;
import appeng.util.Platform;
import com.google.common.base.Preconditions;
import gregtech.api.items.toolitem.ToolMetaItem;
import gregtech.common.items.MetaTool;
import it.unimi.dsi.fastutil.objects.ObjectCollection;
import net.minecraft.item.ItemStack;
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap;
@@ -34,159 +36,187 @@ 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 {
class FuzzyItemVariantList extends ItemVariantList
{
static final SharedStackComparator COMPARATOR = new SharedStackComparator();
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<Object, IAEItemStack> records = new Object2ObjectAVLTreeMap<>(COMPARATOR);
// 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<Object, IAEItemStack> records = new Object2ObjectAVLTreeMap<>( COMPARATOR );
@Override
public Collection<IAEItemStack> findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy) {
ItemStack itemStack = filter.getDefinition();
@Override
public Collection<IAEItemStack> 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);
ItemDamageBound lowerBound = makeLowerBound( itemStack, fuzzy );
ItemDamageBound upperBound = makeUpperBound( itemStack, fuzzy );
Preconditions.checkState( lowerBound.itemDamage > upperBound.itemDamage );
return this.records.subMap(lowerBound, upperBound).values();
}
return this.records.subMap( lowerBound, upperBound ).values();
}
@SuppressWarnings("unchecked")
@Override
Map<AESharedItemStack, IAEItemStack> getRecords() {
// We ensure on our end that we NEVER use anything but AESharedItemStack as the key in this map
return (Map<AESharedItemStack, IAEItemStack>) (Object) this.records;
}
@SuppressWarnings( "unchecked" )
@Override
Map<AESharedItemStack, IAEItemStack> getRecords()
{
// We ensure on our end that we NEVER use anything but AESharedItemStack as the key in this map
return (Map<AESharedItemStack, IAEItemStack>) (Object) this.records;
}
static class ItemDamageBound {
final int itemDamage;
static class ItemDamageBound
{
final int itemDamage;
public ItemDamageBound(int itemDamage) {
this.itemDamage = 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<Object> {
@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();
}
/**
* 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<Object>
{
@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);
}
// 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");
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;
}
if( itemStackA == itemStackB )
{
return 0;
}
// Damaged items are sorted before undamaged items
final int damageValue = Integer.compare(itemDamageB, itemDamageA);
if (damageValue != 0) {
return damageValue;
}
// 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));
}
}
// 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.
* <p>
* 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;
/**
* Minecraft reverses the damage values. So anything with a damage of 0 is undamaged and increases the more damaged
* the item is.
* <p>
* 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" );
/*
* 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() || stack.getItem() instanceof MetaTool, "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();
}
int damage;
if( fuzzy == FuzzyMode.IGNORE_ALL )
{
int maxDamage;
if( stack.getItem() instanceof MetaTool )
{
maxDamage = ( (MetaTool) stack.getItem() ).getMaxItemDamage( stack );
damage = ( (MetaTool) stack.getItem() ).getItemDamage( stack );
}
else
{
maxDamage = stack.getMaxDamage();
damage = stack.getItemDamage();
}
if( maxDamage != 0 )
{
damage = maxDamage;
}
}
else
{
final int breakpoint = fuzzy.calculateBreakPoint( stack.getItem().isDamageable() ? stack.getMaxDamage() : ( (MetaTool) stack.getItem() ).getMaxItemDamage( stack ) );
damage = stack.getItem().isDamageable() ? stack.getItemDamage() : ( (MetaTool) stack.getItem() ).getItemDamage( stack ) <= breakpoint ? breakpoint : stack.getItem().isDamageable() ? stack.getMaxDamage() : ( (MetaTool) stack.getItem() ).getMaxItemDamage( stack );
}
return new ItemDamageBound( damage );
}
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");
/*
* 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() || stack.getItem() instanceof MetaTool, "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;
}
int damage;
if( fuzzy == FuzzyMode.IGNORE_ALL )
{
damage = MIN_DAMAGE_VALUE;
}
else
{
final int breakpoint = fuzzy.calculateBreakPoint( stack.getItem().isDamageable() ? stack.getMaxDamage() : ( (MetaTool) stack.getItem() ).getMaxItemDamage( stack ) );
damage = stack.getItem().isDamageable() ? stack.getItemDamage() : ( (MetaTool) stack.getItem() ).getItemDamage( stack ) <= breakpoint ? MIN_DAMAGE_VALUE : breakpoint;
}
return new ItemDamageBound(damage);
}
return new ItemDamageBound( damage );
}
}
+177 -140
View File
@@ -36,180 +36,217 @@ import appeng.api.config.FuzzyMode;
import appeng.api.storage.data.IAEItemStack;
import appeng.api.storage.data.IItemList;
public final class ItemList implements IItemList<IAEItemStack> {
private final Reference2ObjectMap<Item, ItemVariantList> 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);
public final class ItemList implements IItemList<IAEItemStack>
{
@Override
public IAEItemStack findPrecise(final IAEItemStack itemStack) {
if (itemStack == null) {
return null;
}
private final Reference2ObjectMap<Item, ItemVariantList> 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 );
ItemVariantList record = this.records.get(itemStack.getItem());
return record != null ? record.findPrecise(itemStack) : null;
}
@Override
public IAEItemStack findPrecise( final IAEItemStack itemStack )
{
if( itemStack == null )
{
return null;
}
@Override
public Collection<IAEItemStack> findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy) {
if (filter == null) {
return Collections.emptyList();
}
ItemVariantList record = this.records.get( itemStack.getItem() );
return record != null ? record.findPrecise( itemStack ) : null;
}
ItemVariantList record = this.records.get(filter.getItem());
return record != null ? record.findFuzzy(filter, fuzzy) : Collections.emptyList();
}
@Override
public Collection<IAEItemStack> findFuzzy( final IAEItemStack filter, final FuzzyMode fuzzy )
{
if( filter == null )
{
return Collections.emptyList();
}
@Override
public boolean isEmpty() {
return !this.iterator().hasNext();
}
ItemVariantList record = this.records.get( filter.getItem() );
return record != null ? record.findFuzzy( filter, fuzzy ) : Collections.emptyList();
}
@Override
public void add(final IAEItemStack itemStack) {
version.incrementAndGet();
@Override
public boolean isEmpty()
{
return !this.iterator().hasNext();
}
if (itemStack == null) {
return;
}
@Override
public void add( final IAEItemStack itemStack )
{
version.incrementAndGet();
this.getOrCreateRecord(itemStack.getItem()).add(itemStack);
}
if( itemStack == null )
{
return;
}
@Override
public void addStorage(final IAEItemStack itemStack) {
version.incrementAndGet();
this.getOrCreateRecord( itemStack.getItem() ).add( itemStack );
}
if (itemStack == null) {
return;
}
@Override
public void addStorage( final IAEItemStack itemStack )
{
version.incrementAndGet();
this.getOrCreateRecord(itemStack.getItem()).addStorage(itemStack);
}
if( itemStack == null )
{
return;
}
@Override
public void addCrafting(final IAEItemStack itemStack) {
version.incrementAndGet();
this.getOrCreateRecord( itemStack.getItem() ).addStorage( itemStack );
}
if (itemStack == null) {
return;
}
@Override
public void addCrafting( final IAEItemStack itemStack )
{
version.incrementAndGet();
this.getOrCreateRecord(itemStack.getItem()).addCrafting(itemStack);
}
if( itemStack == null )
{
return;
}
@Override
public void addRequestable(final IAEItemStack itemStack) {
version.incrementAndGet();
this.getOrCreateRecord( itemStack.getItem() ).addCrafting( itemStack );
}
if (itemStack == null) {
return;
}
@Override
public void addRequestable( final IAEItemStack itemStack )
{
version.incrementAndGet();
this.getOrCreateRecord(itemStack.getItem()).addRequestable(itemStack);
}
if( itemStack == null )
{
return;
}
@Override
public IAEItemStack getFirstItem() {
for (final IAEItemStack stackType : this) {
return stackType;
}
this.getOrCreateRecord( itemStack.getItem() ).addRequestable( itemStack );
}
return null;
}
@Override
public IAEItemStack getFirstItem()
{
for( final IAEItemStack stackType : this )
{
return stackType;
}
@Override
public int size() {
int size = 0;
for (ItemVariantList entry : records.values()) {
size += entry.size();
}
return null;
}
return size;
}
@Override
public int size()
{
int size = 0;
for( ItemVariantList entry : records.values() )
{
size += entry.size();
}
@Override
public Iterator<IAEItemStack> iterator() {
return new ChainedIterator(this.records.values().iterator(), version);
}
return size;
}
@Override
public void resetStatus() {
for (final IAEItemStack i : this) {
i.reset();
}
}
@Override
public Iterator<IAEItemStack> iterator()
{
return new ChainedIterator( this.records.values().iterator(), version );
}
private ItemVariantList getOrCreateRecord(Item item) {
return this.records.computeIfAbsent(item, this::makeRecordMap);
}
@Override
public void resetStatus()
{
for( final IAEItemStack i : this )
{
i.reset();
}
}
private ItemVariantList makeRecordMap(Item item) {
if (item.isDamageable() ) {
return new FuzzyItemVariantList();
} else {
return new NormalItemVariantList();
}
}
private ItemVariantList getOrCreateRecord( Item item )
{
return this.records.computeIfAbsent( item, this::makeRecordMap );
}
/**
* Iterates over multiple item lists as if they were one list.
*/
private static class ChainedIterator implements Iterator<IAEItemStack> {
private ItemVariantList makeRecordMap( Item item )
{
if( item.isDamageable() || item instanceof MetaTool )
{
return new FuzzyItemVariantList();
}
else
{
return new NormalItemVariantList();
}
}
private final AtomicInteger parentVersion;
private final int version;
private final Iterator<ItemVariantList> parent;
private Iterator<IAEItemStack> next;
/**
* Iterates over multiple item lists as if they were one list.
*/
private static class ChainedIterator implements Iterator<IAEItemStack>
{
public ChainedIterator(Iterator<ItemVariantList> iterator, AtomicInteger parentVersion) {
this.parent = iterator;
this.parentVersion = parentVersion;
this.version = parentVersion.get();
this.ensureItems();
}
private final AtomicInteger parentVersion;
private final int version;
private final Iterator<ItemVariantList> parent;
private Iterator<IAEItemStack> next;
@Override
public boolean hasNext() {
return next != null && next.hasNext();
}
public ChainedIterator( Iterator<ItemVariantList> iterator, AtomicInteger parentVersion )
{
this.parent = iterator;
this.parentVersion = parentVersion;
this.version = parentVersion.get();
this.ensureItems();
}
@Override
public IAEItemStack next() {
if (this.next == null) {
throw new NoSuchElementException();
}
if (this.version != this.parentVersion.get()) {
throw new ConcurrentModificationException();
}
@Override
public boolean hasNext()
{
return next != null && next.hasNext();
}
IAEItemStack result = this.next.next();
this.ensureItems();
return result;
}
@Override
public IAEItemStack next()
{
if( this.next == null )
{
throw new NoSuchElementException();
}
if( this.version != this.parentVersion.get() )
{
throw new ConcurrentModificationException();
}
private void ensureItems() {
if (hasNext()) {
return; // Still items left in the current one
}
IAEItemStack result = this.next.next();
this.ensureItems();
return result;
}
// Find the next iterator willing to return some items...
while (this.parent.hasNext()) {
this.next = this.parent.next().iterator();
private void ensureItems()
{
if( hasNext() )
{
return; // Still items left in the current one
}
if (this.next.hasNext()) {
return; // Found one!
}
}
// Find the next iterator willing to return some items...
while ( this.parent.hasNext() )
{
this.next = this.parent.next().iterator();
// No more items
this.next = null;
}
}
if( this.next.hasNext() )
{
return; // Found one!
}
}
// No more items
this.next = null;
}
}
}