Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.cyclops.integratedcrafting.api.crafting;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
Expand All @@ -16,9 +17,9 @@

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* A CraftingJobDependencyGraph stores dependencies between crafting jobs based on their unique ID.
Expand Down Expand Up @@ -52,11 +53,7 @@ public CraftingJob getCraftingJob(int id) {
}

public Collection<CraftingJob> getDependencies(CraftingJob craftingJob) {
return dependencies.getOrDefault(craftingJob.getId(), new IntArrayList())
.stream()
.map(craftingJobs::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
return getCraftingJobs(dependencies.get(craftingJob.getId()));
}

public boolean hasDependencies(CraftingJob craftingJob) {
Expand All @@ -77,11 +74,27 @@ public boolean hasDependencies(int craftingJobId) {
}

public Collection<CraftingJob> getDependents(CraftingJob craftingJob) {
return dependents.getOrDefault(craftingJob.getId(), new IntArrayList())
.stream()
.map(craftingJobs::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
return getCraftingJobs(dependents.get(craftingJob.getId()));
}

/**
* Resolve the given crafting job ids into their crafting jobs, skipping the ids that are unknown.
* @param craftingJobIds Crafting job ids, may be null if no ids are stored.
* @return A new collection with the resolved crafting jobs.
*/
protected Collection<CraftingJob> getCraftingJobs(@Nullable IntCollection craftingJobIds) {
if (craftingJobIds == null || craftingJobIds.isEmpty()) {
return Collections.emptyList();
}
List<CraftingJob> resolved = Lists.newArrayListWithCapacity(craftingJobIds.size());
IntIterator it = craftingJobIds.iterator();
while (it.hasNext()) {
CraftingJob craftingJob = this.craftingJobs.get(it.nextInt());
if (craftingJob != null) {
resolved.add(craftingJob);
}
}
return resolved;
}

public void addCraftingJobId(CraftingJob craftingJob) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
* Helpers related to handling crafting jobs.
Expand Down Expand Up @@ -986,6 +985,45 @@ public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStora
extractionMemoryReusable, collectMissingIngredients, recipeOutputQuantity, false);
}

/**
* Check if the given storage has no capacity at all, and can therefore never contain anything.
*
* {@link IIngredientComponentStorage#getMaxQuantity()} is not a cheap call for a network channel:
* it aggregates the capacity of every storage position in the channel,
* which costs a capability lookup and a slot enumeration per position.
* A channel that is indexed and holds at least one instance is guaranteed to have capacity,
* so its index can answer this question without touching any position.
*
* @param storage A storage.
* @param <T> The instance type.
* @param <M> The matching condition parameter.
* @return If the storage has no capacity.
*/
protected static <T, M> boolean hasNoStorageCapacity(IIngredientComponentStorage<T, M> storage) {
if (storage instanceof IngredientChannelIndexed
&& !((IngredientChannelIndexed<T, M>) storage).getIndex().isEmpty()) {
return false;
}
return storage.getMaxQuantity() == 0;
}

/**
* @param recipe A recipe.
* @param ingredientComponent An ingredient component type.
* @param <T> The instance type.
* @param <M> The matching condition parameter.
* @return If at least one of the recipe's inputs of the given component type is reusable.
*/
protected static <T, M> boolean hasReusableInput(IRecipeDefinition recipe, IngredientComponent<T, M> ingredientComponent) {
int inputCount = recipe.getInputs(ingredientComponent).size();
for (int i = 0; i < inputCount; i++) {
if (recipe.isInputReusable(ingredientComponent, i)) {
return true;
}
}
return false;
}

public static <T, M> Pair<List<T>, MissingIngredients<T, M>>
getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage, IngredientComponent<T, M> ingredientComponent,
IRecipeDefinition recipe, boolean simulate,
Expand All @@ -997,10 +1035,9 @@ public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStora

// Quickly return if the storage is empty
// We can't take this shortcut if we have a reusable ingredient AND extractionMemoryReusable is not empty
if (storage.getMaxQuantity() == 0 &&
if (hasNoStorageCapacity(storage) &&
extractionMemoryReusable.isEmpty() &&
IntStream.range(0, recipe.getInputs(ingredientComponent).size())
.noneMatch(i -> recipe.isInputReusable(ingredientComponent, i))) {
!hasReusableInput(recipe, ingredientComponent)) {
if (collectMissingIngredients) {
List<IPrototypedIngredientAlternatives<T, M>> recipeInputs = recipe.getInputs(ingredientComponent);
MissingIngredients<T, M> missing = new MissingIngredients<>(recipeInputs.stream().map(IPrototypedIngredientAlternatives::getAlternatives)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,10 @@ public void update(INetwork network, int channel, PartPos targetPos) {

// Notify the network of finalized crafting jobs
if (finishedCraftingJobs.size() > 0) {
ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
for (CraftingJob finishedCraftingJob : finishedCraftingJobs.values()) {
if (finishedCraftingJob.getAmount() == 0) {
// If the job is fully finished, remove it from the network
ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
craftingNetwork.onCraftingJobFinished(finishedCraftingJob);
allCraftingJobs.remove(finishedCraftingJob.getId());
nonBlockingJobsRunningAmount.remove(finishedCraftingJob.getId());
Expand Down Expand Up @@ -471,7 +471,9 @@ public void update(INetwork network, int channel, PartPos targetPos) {
}
}

if (processingJobs < this.maxProcessingJobs) {
// Only look for a job to start if this handler has room for one, and has something to start.
// Skipping this block for an idle handler avoids a crafting network lookup for every idle tick.
if (processingJobs < this.maxProcessingJobs && !this.pendingCraftingJobs.isEmpty()) {
// Handle crafting jobs
CraftingJob startingCraftingJob = null;
ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.cyclops.cyclopscore.datastructure.MultitransformIterator;
import org.cyclops.cyclopscore.ingredient.collection.IIngredientMapMutable;
import org.cyclops.cyclopscore.ingredient.collection.IngredientHashMap;
import org.cyclops.cyclopscore.ingredient.collection.IngredientMapSingleClassified;
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
import org.cyclops.integratedcrafting.api.recipe.ICraftingJobIndex;
import org.cyclops.integratedcrafting.api.recipe.ICraftingJobIndexModifiable;
Expand Down Expand Up @@ -57,7 +58,13 @@ public CraftingJob getCraftingJob(int craftingJobId) {

@Nullable
protected <T, M> IIngredientMapMutable<T, M, Collection<CraftingJob>> initializeIndex(IngredientComponent<T, M> recipeComponent) {
return new IngredientHashMap<>(recipeComponent);
// Classify by the component's primary category, just like RecipeIndexDefault does.
// Lookups in this index are done with quantity-less match conditions,
// which a plain hash map can only answer by filtering over every indexed crafting job.
if (recipeComponent.getCategoryTypes().size() == 1) {
return new IngredientHashMap<>(recipeComponent);
}
return new IngredientMapSingleClassified<>(recipeComponent, () -> new IngredientHashMap<>(recipeComponent), recipeComponent.getCategoryTypes().get(0));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,14 @@ public boolean craft(Function<IngredientComponent<?, ?>, PartPos> targetGetter,
CraftingInput gridInput = gridFull.asCraftInput();
Level level = target.getPos().getLevel(true);

return CraftingHelpers.findServerRecipe(RecipeType.CRAFTING, gridInput, level)
// Look the recipe up via the cache, as an uncached lookup is a linear scan over every crafting recipe.
// This method is called at least twice for every craft:
// once to simulate the craft, and once to actually perform it.
return CraftingHelpers.findRecipeCached(RecipeType.CRAFTING, gridInput, level, false)
.or(() -> {
try {
CraftingGrid gridSmall = new CraftingGrid(ingredients, 2, 2);
return CraftingHelpers.findServerRecipe(RecipeType.CRAFTING, gridSmall.asCraftInput(), level);
return CraftingHelpers.findRecipeCached(RecipeType.CRAFTING, gridSmall.asCraftInput(), level, false);
} catch (IllegalArgumentException e) {
// This can occur if the ingredients don't fit in a 2x2 grid.
return Optional.empty();
Expand Down
Loading