package blusunrize.immersiveengineering.common.crafting; import blusunrize.immersiveengineering.api.ApiUtils; import blusunrize.immersiveengineering.api.IEApi; import blusunrize.immersiveengineering.api.crafting.ArcFurnaceRecipe; import blusunrize.immersiveengineering.common.util.IELogger; import blusunrize.immersiveengineering.common.util.Utils; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Sets; import net.minecraft.item.*; import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.IRecipe; import net.minecraft.item.crafting.ShapedRecipes; import net.minecraft.item.crafting.ShapelessRecipes; import net.minecraftforge.oredict.OreDictionary; import net.minecraftforge.oredict.ShapedOreRecipe; import net.minecraftforge.oredict.ShapelessOreRecipe; import java.util.*; public class ArcRecyclingThreadHandler extends Thread { static boolean hasProfiled = false; public static List<ArcFurnaceRecipe> recipesToAdd = null; private List<IRecipe> recipeList = null; private ArcRecyclingThreadHandler(List<IRecipe> r) { recipeList = r; } public static void doRecipeProfiling() { Iterator<ArcFurnaceRecipe> prevRecipeIt = ArcFurnaceRecipe.recipeList.iterator(); int r = 0; if(hasProfiled) while(prevRecipeIt.hasNext()) if("Recycling".equals(prevRecipeIt.next().specialRecipeType)) { prevRecipeIt.remove(); r++; } IELogger.info("Arc Recycling: Removed "+r+" old recipes"); recipesToAdd = null; new ArcRecyclingThreadHandler(CraftingManager.getInstance().getRecipeList()).start(); } @Override public void run() { int threadAmount = Runtime.getRuntime().availableProcessors(); IELogger.info("Starting recipe profiler for Arc Recycling, using "+threadAmount+" Threads"); long timestamp = System.currentTimeMillis(); RegistryIterationThread[] threads = new RegistryIterationThread[threadAmount]; boolean divisable = recipeList.size()%threadAmount==0; int limit = divisable?(recipeList.size()/threadAmount) : (recipeList.size()/(threadAmount-1)); int leftOver = divisable?limit:(recipeList.size()-(threadAmount-1)*limit); for(int i = 0; i < threadAmount; i++) threads[i] = new RegistryIterationThread(recipeList, limit*i, i==(threadAmount-1)?leftOver:limit); //iterate over each thread individually ArrayList<RecyclingCalculation> validated = new ArrayList<RecyclingCalculation>(); ArrayListMultimap<ItemStack, RecyclingCalculation> nonValidated = ArrayListMultimap.create(); int invalidCount = 0; for(int i=0; i<threads.length; i++) { RegistryIterationThread thread = threads[i]; try { thread.join(); for(RecyclingCalculation calc : thread.calculatedOutputs) if(calc.isValid()) validated.add(calc); else { for(ItemStack s : calc.queriedSubcomponents) nonValidated.put(s, calc); invalidCount++; } } catch (InterruptedException e) { e.printStackTrace(); } } int timeout = 0; while(!nonValidated.isEmpty() && timeout++<(invalidCount*10)) { ArrayList<RecyclingCalculation> newlyValid = new ArrayList<RecyclingCalculation>(); for(RecyclingCalculation valid : validated) { Iterator<Map.Entry<ItemStack,RecyclingCalculation>> itNonValid = nonValidated.entries().iterator(); while(itNonValid.hasNext()) { Map.Entry<ItemStack,RecyclingCalculation> e = itNonValid.next(); if(OreDictionary.itemMatches(e.getKey(), valid.stack, false)) { RecyclingCalculation nonValid = e.getValue(); if(nonValid.validateSubcomponent(valid)) newlyValid.add(nonValid); } } } nonValidated.values().removeAll(newlyValid); validated.addAll(newlyValid); } //HashSet to avoid duplicates HashSet<String> finishedRecycles = new HashSet<String>(); List<ArcFurnaceRecipe> ret = new ArrayList<>(); for(RecyclingCalculation valid : validated) if(finishedRecycles.add(valid.stack.toString())) ret.add(new ArcRecyclingRecipe(valid.outputs, valid.stack, 100, 512)); for(RecyclingCalculation invalid : Sets.newHashSet(nonValidated.values())) if(finishedRecycles.add(invalid.stack.toString())) { // IELogger.info("Couldn't fully analyze "+invalid.stack+", missing knowledge for "+invalid.queriedSubcomponents); ret.add(new ArcRecyclingRecipe(invalid.outputs, invalid.stack, 100, 512)); } IELogger.info("Finished recipe profiler for Arc Recycling, took "+(System.currentTimeMillis()-timestamp)+" milliseconds"); recipesToAdd = ret; hasProfiled = true; } public static class RegistryIterationThread extends Thread { final List<IRecipe> recipeList; final int baseOffset; final int passes; ArrayList<RecyclingCalculation> calculatedOutputs = new ArrayList<RecyclingCalculation>(); public RegistryIterationThread(List<IRecipe> recipeList, int baseOffset, int passes) { setName("Immersive Engineering Registry Iteratoration Thread"); setDaemon(true); start(); this.recipeList = recipeList; this.baseOffset = baseOffset; this.passes = passes; } @Override public void run() { for(int pass=0; pass<passes; pass++) { IRecipe recipe = recipeList.get(baseOffset+pass); if(recipe.getRecipeOutput()!=null && isValidForRecycling(recipe.getRecipeOutput())) { RecyclingCalculation calc = getRecycleCalculation(recipe.getRecipeOutput(), recipe); if(calc!=null) calculatedOutputs.add(calc); } } } } public static boolean isValidForRecycling(ItemStack stack) { if(stack==null) return false; Item item = stack.getItem(); if(item instanceof ItemTool || item instanceof ItemSword || item instanceof ItemHoe || item instanceof ItemArmor) return true; for(Object recycle : ArcFurnaceRecipe.recyclingAllowed) if(Utils.stackMatchesObject(stack, recycle)) return true; return false; } public static RecyclingCalculation getRecycleCalculation(ItemStack stack, IRecipe recipe) { Object[] inputs = null; if(recipe instanceof ShapedOreRecipe) inputs = ((ShapedOreRecipe)recipe).getInput(); else if(recipe instanceof ShapelessOreRecipe) inputs = ((ShapelessOreRecipe)recipe).getInput().toArray(); else if(recipe instanceof ShapedRecipes) inputs = ((ShapedRecipes)recipe).recipeItems; else if(recipe instanceof ShapelessRecipes) inputs = ((ShapelessRecipes)recipe).recipeItems.toArray(); if(inputs!=null) { int inputSize = stack.stackSize; List<ItemStack> missingSub = new ArrayList<ItemStack>(); HashMap<ItemStack,Double> outputs = new HashMap<ItemStack,Double>(); for(Object in : inputs) if(in!=null) { ItemStack inputStack = null; if(in instanceof ItemStack) inputStack = (ItemStack)in; else if(in instanceof List) inputStack = ((List<ItemStack>)in).get(0); else if(in instanceof String) inputStack = IEApi.getPreferredOreStack((String)in); if(inputStack==null) continue; Object[] brokenDown = ApiUtils.breakStackIntoPreciseIngots(inputStack); if(brokenDown==null) { if(isValidForRecycling(inputStack)) missingSub.add(inputStack); continue; } if(brokenDown[0]!=null && brokenDown[0] instanceof ItemStack && brokenDown[1]!=null && (Double)brokenDown[1] > 0) { boolean invalidOutput = false; for(Object invalid : ArcFurnaceRecipe.invalidRecyclingOutput) if(Utils.stackMatchesObject((ItemStack)brokenDown[0], invalid)) invalidOutput=true; if(!invalidOutput) { boolean b = false; for(ItemStack storedOut : outputs.keySet()) if(OreDictionary.itemMatches((ItemStack)brokenDown[0], storedOut, false)) { outputs.put(storedOut, outputs.get(storedOut)+(Double)brokenDown[1]/inputSize); b=true; } if(!b) outputs.put(Utils.copyStackWithAmount((ItemStack)brokenDown[0],1), (Double)brokenDown[1]/inputSize); } } } if(!outputs.isEmpty() || !missingSub.isEmpty()) { RecyclingCalculation calc = new RecyclingCalculation(recipe, Utils.copyStackWithAmount(stack,1), outputs); calc.queriedSubcomponents.addAll(missingSub); return calc; } } return null; } public static class RecyclingCalculation { IRecipe recipe; ItemStack stack; HashMap<ItemStack, Double> outputs; ArrayList<ItemStack> queriedSubcomponents = new ArrayList<ItemStack>(); public RecyclingCalculation(IRecipe recipe, ItemStack stack, HashMap<ItemStack, Double> outputs) { this.recipe = recipe; this.stack = stack; this.outputs = outputs; } public boolean isValid() { return !outputs.isEmpty() && queriedSubcomponents.isEmpty(); } public boolean validateSubcomponent(RecyclingCalculation calc) { if(isValid()) return true; if(!calc.isValid()) return false; Iterator<ItemStack> it = queriedSubcomponents.iterator(); while(it.hasNext()) { ItemStack next = it.next(); if(OreDictionary.itemMatches(next, calc.stack, false)) { for(Map.Entry<ItemStack,Double> e : calc.outputs.entrySet()) { boolean b = true; for(ItemStack key : outputs.keySet()) if(OreDictionary.itemMatches(key, e.getKey(), false)) { outputs.put(key, outputs.get(key)+e.getValue()); b = false; break; } if(b) outputs.put(e.getKey(), e.getValue()); } it.remove(); } } return isValid(); } } }