All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mmb.content.stn.planner.STNPlanner Maven / Gradle / Ivy

Go to download

Dependency for the MultiMachineBuilder, a voxel game about building an industrial empire in a finite world. THIS RELEASE IS NOT PLAYABLE. To play the game, donwload from >ITCH.IO LINK HERE< or >GH releases link here<

The newest version!
/**
 * 
 */
package mmb.content.stn.planner;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Set;
import java.util.function.Function;

import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
import mmb.NN;
import mmb.Nil;
import mmb.content.ditems.Stencil;
import mmb.content.stn.network.DataLayerSTN;
import mmb.content.stn.network.STNNetworkProcessing.STNRGroupTag.STNPRecipe;
import mmb.engine.item.ItemEntry;
import mmb.engine.recipe.RecipeOutput;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;

/**
 * A class used to plan STN crafting jobs.
 * 
 * Plans the processing sequence of the items before commencing the process.
 * 
 * The item flow must form an Acyclic Directed Graph
 * 
 * The planner uses a cyclic planner
 * 
 * 
 * 
 * 
Planning phases: *
    *
  1. Calculate required withdrawals, procurements, crafts and processes. * The method will fail with {@link ArithmeticException} if there are growing cycles in the recipe graph which involve planned items
  2. *
  3. Arrange the flow of process items. This step will fail if there are circular references in the item flow.
  4. *
  5. Assign the steps to the machines
  6. *
* Run phases: *
    *
  1. Take required items from the network * (this will fail if any required items were extracted between phase 1 and now, * or there is not enough space in the crafting helper)
  2. *
  3. Start the machines
  4. *
  5. Run until all processes are finished: *
      *
    1. Deliver items from a recipe to a crafter or machine if it is a crafting or processing operation
    2. *
    3. Receive items from a recipe, factory, or crafter and count arrivals
    4. *
    *
  6. *
  7. Deliver crafted items to the network, or the user
  8. *
* * Planning overview: *
    *
  1. Populate the process plan with head nodes for planned outputs
  2. *
  3. Look for valid sources in order of: exhaustion queue, inventory, procurement, crafting, processing. * If no valid option is found, report inventory access
  4. *
* * @author oskar */ public class STNPlanner { @NN public final DataLayerSTN stn; public STNPlanner(DataLayerSTN stn) { this.stn = stn; } //Phase 1 /** * Phase 1 planning report * @author oskar */ public static class Phase1{ /** Items which are going to be withdrawn */ @NN public final Object2IntOpenHashMap itemsWithdrawn; /** Used processing recipes */ @NN public final Object2IntOpenHashMap<@NN STNPRecipe> processes; /** Used crafts */ @NN public final Object2IntOpenHashMap<@NN Stencil> crafts; /** Used procurements */ @NN public final Object2IntOpenHashMap procurements; /** Missing items */ @NN public final Object2IntOpenHashMap missing; /** * Creates a finished Phase 1 plan * @param itemsWithdrawn * @param processes used processing recipes * @param crafts used crafts * @param procurements used procurements * @param missing missing items */ public Phase1(Object2IntOpenHashMap itemsWithdrawn, Object2IntOpenHashMap<@NN STNPRecipe> processes, Object2IntOpenHashMap<@NN Stencil> crafts, Object2IntOpenHashMap procurements, Object2IntOpenHashMap missing) { this.itemsWithdrawn = itemsWithdrawn; this.processes = processes; this.crafts = crafts; this.procurements = procurements; this.missing = missing; } } /** * 1st phase of planning: calculate items extracted, procured, crafted, processed and missing * * @param items * @return a crafting plan * @throws ArithmeticException if there are growing cycles which involve planned items * @throws IllegalStateException if there are additional required items (if planning logic fails) */ public Phase1 plan1(RecipeOutput items) { //Results Object2IntOpenHashMap<@NN STNPRecipe> processRecipes = new Object2IntOpenHashMap<>(); Object2IntOpenHashMap<@NN Stencil> craftRecipes = new Object2IntOpenHashMap<>(); Object2IntOpenHashMap procurements = new Object2IntOpenHashMap<>(); //The planning queue (negative values mean that there are excess items Queue queue = new ArrayDeque<>(); Object2IntOpenHashMap planMap = new Object2IntOpenHashMap<>(); Object2IntOpenHashMap invRemain = new Object2IntOpenHashMap<>(); Object2IntOpenHashMap missing = new Object2IntOpenHashMap<>(); //Populate the planning queue planMap.putAll(items.getContents()); queue.addAll(items.items()); stn.inv.contents(invRemain); //Run the planning ItemEntry entry; while((entry = queue.poll()) != null) { int plannedAmount = planMap.getInt(entry); if(plannedAmount <= 0) continue; //All items are planned for now //Option A: Inventory int itemsInInv = invRemain.getInt(entry); if(itemsInInv >= plannedAmount) { //Option A1: all planned items are stored invRemain.addTo(entry, -plannedAmount); continue; }else if(itemsInInv > 0) { //Option A2: some planned items are stored plannedAmount -= itemsInInv; invRemain.put(entry, 0); }else if(itemsInInv < 0) { //Error in planning logic throw new IllegalStateException("Counted negative items in inventory: "+itemsInInv+" for: "+entry); } //TODO Option B: Procurement //Option C: Crafting Set stencils = stn.processor.stencil2OutIndex.multimap().get(entry); Stencil stencil = findPlausibleRecipe(planMap, invRemain, stencils, s -> s.in().items()); //If no recipes are plausible, no recipe will be usable if(stencil != null) { //If a recipe is plausible, plan it Set toPlan = planItems(stencil.in(), stencil.out(), entry, plannedAmount, planMap, stencil, craftRecipes, queue); queue.addAll(toPlan); continue; } //Option D: Processing Set processables = stn.processor.processRecipe2OutIndex.multimap().get(entry); STNPRecipe processRecipe = findPlausibleRecipe(planMap, invRemain, processables, pr -> pr.in.items()); //If no recipes are plausible, no recipe will be usable if(processRecipe != null) { //If a recipe is plausible, plan it Set toPlan = planItems(processRecipe.in, processRecipe.out, entry, plannedAmount, planMap, processRecipe, processRecipes, queue); queue.addAll(toPlan); continue; } //Else: Missing items missing.addTo(entry, plannedAmount); } Object2IntOpenHashMap putback = new Object2IntOpenHashMap<>(); //Check for stray items for(Entry stack: planMap.object2IntEntrySet()) { int amount = -stack.getIntValue(); if(amount < 0) throw new IllegalStateException("Unnacounted-for items in the plans: "+(-amount)+" x "+stack.getKey()); putback.put(stack.getKey(), amount); } //Calculate extractions Object2IntOpenHashMap itemsInInv = new Object2IntOpenHashMap<>(); Object2IntOpenHashMap itemsToWithdraw = new Object2IntOpenHashMap<>(); stn.inv.contents(itemsInInv); for(Entry itemCompare: itemsInInv.object2IntEntrySet()) { ItemEntry item = itemCompare.getKey(); int remaining = invRemain.getInt(item); int stored = itemCompare.getIntValue(); itemsToWithdraw.put(item, stored-remaining); } //FINISH return new Phase1(itemsToWithdraw, processRecipes, craftRecipes, procurements, missing); } /** * Internal helper method for Phase 1 * @param type of recipes * @param planMap planning map * @param invRemain * @param possible all potential recipes * @param transformer obtains input items for the recipe * @return a iterator of plausible recipes, or null if not found */ private @Nil T findPlausibleRecipe(Object2IntOpenHashMap planMap, Object2IntOpenHashMap invRemain, @Nil Set possible, Function> transformer) { if(possible == null) return null; for(T recipe: possible) { Set inputs = transformer.apply(recipe); if(stn.processor.isAllObtainable(inputs, invRemain, planMap)) return recipe; } return null; } private static Set planItems(RecipeOutput inputs, RecipeOutput outputs, ItemEntry plannedItem, int plannedAmount, Object2IntOpenHashMap planMap, T recipe, Object2IntOpenHashMap<@NN T> recipesCounter, Queue queue) { int unitOutputQuantity = outputs.get(plannedItem); if(unitOutputQuantity <= 0) throw new InternalError("No such item: "+plannedItem); double recipeQuantity0 = (double)plannedAmount / unitOutputQuantity; int recipeQuantity = (int) Math.ceil(recipeQuantity0); recipesCounter.addTo(recipe, recipeQuantity); //Add the inputs to the plans and queue Object2IntOpenHashMap totalInputs = inputs.mul2map(recipeQuantity, Object2IntOpenHashMap::new); for(Entry input: totalInputs.object2IntEntrySet()) { planMap.addTo(input.getKey(), Math.multiplyExact(input.getIntValue(), recipeQuantity)); queue.add(input.getKey()); } //Add the outputs to the plans Object2IntOpenHashMap totalOutputs = outputs.mul2map(recipeQuantity, Object2IntOpenHashMap::new); for(Entry output: totalOutputs.object2IntEntrySet()) planMap.addTo(output.getKey(), Math.multiplyExact(-output.getIntValue(), recipeQuantity)); return totalInputs.keySet(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy