diff --git a/Common/src/main/java/com/almostreliable/unified/config/DuplicateConfig.java b/Common/src/main/java/com/almostreliable/unified/config/DuplicateConfig.java index 5e06e1c..290a059 100644 --- a/Common/src/main/java/com/almostreliable/unified/config/DuplicateConfig.java +++ b/Common/src/main/java/com/almostreliable/unified/config/DuplicateConfig.java @@ -60,20 +60,35 @@ public final class DuplicateConfig extends Config { * @return True if the recipe type is ignored, false otherwise */ private boolean isRecipeTypeIgnored(RecipeLink recipe) { - return ignoredRecipeTypesCache.computeIfAbsent(recipe.getType(), type -> { - for (Pattern ignorePattern : ignoreRecipeTypes) { - if (ignorePattern.matcher(type.toString()).matches()) { - return true; - } + ResourceLocation type = recipe.getType(); + Boolean ignored = ignoredRecipeTypesCache.get(type); + if (ignored == null) { + ignored = computeIsRecipeTypeIgnored(recipe); + ignoredRecipeTypesCache.put(type, ignored); + } + return ignored; + } + + private boolean computeIsRecipeTypeIgnored(RecipeLink recipe) { + ResourceLocation type = recipe.getType(); + String typeString = type.toString(); + for (Pattern ignorePattern : ignoreRecipeTypes) { + if (ignorePattern.matcher(typeString).matches()) { + return true; } - return false; - }); + } + return false; } public JsonCompare.CompareSettings getCompareSettings(ResourceLocation type) { return overrideRules.getOrDefault(type, defaultRules); } + public JsonCompare.CompareContext getCompareContext(RecipeLink recipe) { + JsonCompare.CompareSettings compareSettings = getCompareSettings(recipe.getType()); + return JsonCompare.CompareContext.create(compareSettings, recipe); + } + public boolean shouldCompareAll() { return compareAll; } diff --git a/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeLink.java b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeLink.java index b22c144..3300248 100644 --- a/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeLink.java +++ b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeLink.java @@ -11,11 +11,19 @@ import com.google.gson.JsonObject; import org.jetbrains.annotations.Nullable; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; public class RecipeLink implements RecipeData { + /** + * This cache is an optimization to avoid creating many ResourceLocations for just a few different types. + * Having fewer ResourceLocation instances can greatly speed up equality checking when these are used as map keys. + */ + private static final Map PARSED_TYPE_CACHE = new HashMap<>(); + private final ResourceLocation id; private final ResourceLocation type; private final JsonObject originalRecipe; @@ -27,7 +35,8 @@ public class RecipeLink implements RecipeData { this.originalRecipe = originalRecipe; try { - this.type = ResourceLocation.tryParse(originalRecipe.get("type").getAsString()); + String typeString = originalRecipe.get("type").getAsString(); + this.type = PARSED_TYPE_CACHE.computeIfAbsent(typeString, ResourceLocation::parse); } catch (Exception e) { throw new IllegalArgumentException("could not detect recipe type"); } @@ -40,19 +49,19 @@ public class RecipeLink implements RecipeData { * * @param first first recipe to compare * @param second second recipe to compare - * @param compareSettings Settings to use for comparison. + * @param compareContext Settings and context to use for comparison. * @return the recipe where rules are applied and the recipes are compared for equality, or null if the recipes are not equal */ @Nullable - public static RecipeLink compare(RecipeLink first, RecipeLink second, JsonCompare.CompareSettings compareSettings) { + public static RecipeLink compare(RecipeLink first, RecipeLink second, JsonCompare.CompareContext compareContext) { JsonObject selfActual = first.getActual(); JsonObject toCompareActual = second.getActual(); JsonObject compare = null; if (first.getType().toString().equals("minecraft:crafting_shaped")) { - compare = JsonCompare.compareShaped(selfActual, toCompareActual, compareSettings); - } else if (JsonCompare.matches(selfActual, toCompareActual, compareSettings)) { - compare = JsonCompare.compare(compareSettings.getRules(), selfActual, toCompareActual); + compare = JsonCompare.compareShaped(selfActual, toCompareActual, compareContext); + } else if (JsonCompare.matches(selfActual, toCompareActual, compareContext)) { + compare = JsonCompare.compare(compareContext.settings().getRules(), selfActual, toCompareActual); } if (compare == null) return null; @@ -129,10 +138,10 @@ public class RecipeLink implements RecipeData { * the master from the link will be used. Otherwise, we will create a new link if needed. * * @param otherRecipe Recipe data to check for duplicate against. - * @param compareSettings Settings to use for comparison. + * @param compareContext Settings and context to use for comparison. * @return True if recipe is a duplicate, false otherwise. */ - public boolean handleDuplicate(RecipeLink otherRecipe, JsonCompare.CompareSettings compareSettings) { + public boolean handleDuplicate(RecipeLink otherRecipe, JsonCompare.CompareContext compareContext) { DuplicateLink selfDuplicate = getDuplicateLink(); DuplicateLink otherDuplicate = otherRecipe.getDuplicateLink(); @@ -141,7 +150,7 @@ public class RecipeLink implements RecipeData { } if (selfDuplicate == null && otherDuplicate == null) { - RecipeLink compare = compare(this, otherRecipe, compareSettings); + RecipeLink compare = compare(this, otherRecipe, compareContext); if (compare == null) { return false; } @@ -153,7 +162,7 @@ public class RecipeLink implements RecipeData { } if (otherDuplicate != null) { - RecipeLink compare = compare(this, otherDuplicate.getMaster(), compareSettings); + RecipeLink compare = compare(this, otherDuplicate.getMaster(), compareContext); if (compare == null) { return false; } @@ -163,7 +172,7 @@ public class RecipeLink implements RecipeData { } // selfDuplicate != null - RecipeLink compare = compare(selfDuplicate.getMaster(), otherRecipe, compareSettings); + RecipeLink compare = compare(selfDuplicate.getMaster(), otherRecipe, compareContext); if (compare == null) { return false; } diff --git a/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeTransformer.java b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeTransformer.java index 3b9b4af..3579082 100644 --- a/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeTransformer.java +++ b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeTransformer.java @@ -138,7 +138,8 @@ public class RecipeTransformer { return false; } - JsonCompare.CompareSettings compareSettings = duplicateConfig.getCompareSettings(curRecipe.getType()); + JsonCompare.CompareContext compareContext = duplicateConfig.getCompareContext(curRecipe); + boolean foundDuplicate = false; for (RecipeLink recipeLink : recipes) { if (!curRecipe.getType().equals(recipeLink.getType())) { @@ -150,7 +151,7 @@ public class RecipeTransformer { continue; } - foundDuplicate |= curRecipe.handleDuplicate(recipeLink, compareSettings); + foundDuplicate |= curRecipe.handleDuplicate(recipeLink, compareContext); } return foundDuplicate; diff --git a/Common/src/main/java/com/almostreliable/unified/utils/JsonCompare.java b/Common/src/main/java/com/almostreliable/unified/utils/JsonCompare.java index a70f4e8..2ae5730 100644 --- a/Common/src/main/java/com/almostreliable/unified/utils/JsonCompare.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/JsonCompare.java @@ -1,5 +1,7 @@ package com.almostreliable.unified.utils; +import com.almostreliable.unified.unification.recipe.RecipeLink; + import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -8,8 +10,6 @@ import com.google.gson.JsonPrimitive; import org.jetbrains.annotations.Nullable; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -47,8 +47,8 @@ public final class JsonCompare { } @Nullable - public static JsonObject compareShaped(JsonObject first, JsonObject second, CompareSettings compareSettings) { - if (!matches(first, second, compareSettings)) return null; + public static JsonObject compareShaped(JsonObject first, JsonObject second, CompareContext compareContext) { + if (!matches(first, second, compareContext)) return null; JsonArray firstPattern = JsonUtils.arrayOrSelf(first.get("pattern")); JsonArray secondPattern = JsonUtils.arrayOrSelf(second.get("pattern")); @@ -97,20 +97,18 @@ public final class JsonCompare { return keyMap; } - public static boolean matches(JsonObject first, JsonObject second, CompareSettings compareSettings) { - Collection ignoredFields = compareSettings.getIgnoredFields(); - if (ignoredFields.isEmpty() && first.size() != second.size()) { + public static boolean matches(JsonObject first, JsonObject second, CompareContext compareContext) { + CompareSettings compareSettings = compareContext.settings; + if (!compareSettings.hasIgnoredFields() && first.size() != second.size()) { return false; } - for (Map.Entry firstEntry : first.entrySet()) { - if (ignoredFields.contains(firstEntry.getKey())) continue; - - JsonElement firstElem = firstEntry.getValue(); - JsonElement secondElem = second.get(firstEntry.getKey()); - + for (String field : compareContext.compareFields()) { + JsonElement secondElem = second.get(field); if (secondElem == null) return false; + JsonElement firstElem = first.get(field); + // sanitize elements for implicit counts of 1 if (compareSettings.handleImplicitCounts && needsSanitizing(firstElem, secondElem)) { firstElem = sanitize(firstElem); @@ -270,6 +268,17 @@ public final class JsonCompare { } } + public record CompareContext(CompareSettings settings, List compareFields) { + public static CompareContext create(CompareSettings settings, RecipeLink curRecipe) { + Set compareFields = curRecipe.getActual().keySet(); + if (!settings.ignoredFields.isEmpty()) { + compareFields = new HashSet<>(compareFields); + compareFields.removeAll(settings.ignoredFields); + } + return new CompareContext(settings, List.copyOf(compareFields)); + } + } + public static class CompareSettings { public static final String IGNORED_FIELDS = "ignored_fields"; @@ -292,8 +301,8 @@ public final class JsonCompare { } } - public Set getIgnoredFields() { - return Collections.unmodifiableSet(ignoredFields); + public boolean hasIgnoredFields() { + return !ignoredFields.isEmpty(); } public JsonObject serialize() {