From a2d51836c6b22932b9f4b2d36f4732ee86650ece Mon Sep 17 00:00:00 2001 From: LLytho <29131229+LLytho@users.noreply.github.com> Date: Wed, 22 Jun 2022 17:03:31 +0200 Subject: [PATCH] Implement RecipeTransformations --- .../unified/AlmostUnifiedRuntime.java | 29 ++++--- .../unified/RecipeContextImpl.java | 72 ++++++++++------- .../unified/RecipeTransformationsImpl.java | 78 +++++++++++++++++++ .../unified/api/RecipeContext.java | 6 +- .../unified/api/RecipeHandler.java | 10 +-- .../unified/api/RecipeTransformations.java | 18 +++++ .../unified/handler/GenericRecipeHandler.java | 40 +--------- .../unified/handler/RecipeHandlerFactory.java | 31 +++----- .../handler/ShapedRecipeKeyHandler.java | 21 +++-- .../unified/compat/ie/IERecipeHandler.java | 50 ++++++++---- 10 files changed, 217 insertions(+), 138 deletions(-) create mode 100644 Common/src/main/java/com/almostreliable/unified/RecipeTransformationsImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/RecipeTransformations.java diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntime.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntime.java index e63ad81..fa640b7 100644 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntime.java +++ b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntime.java @@ -1,7 +1,8 @@ package com.almostreliable.unified; -import com.almostreliable.unified.api.RecipeHandler; import com.almostreliable.unified.handler.RecipeHandlerFactory; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import net.minecraft.resources.ResourceLocation; @@ -34,7 +35,7 @@ public abstract class AlmostUnifiedRuntime { } public void transformRecipes(Map recipes, ReplacementMap replacementMap) { - Map typeCount = new HashMap<>(); + Multimap typeCount = HashMultimap.create(); int transformedRecipes = 0; long start = System.nanoTime(); @@ -47,7 +48,7 @@ public abstract class AlmostUnifiedRuntime { // TODO for debugging remove this later ResourceLocation recipeType = getRecipeType(json); - typeCount.compute(recipeType, (rl, old) -> old == null ? 1 : old + 1); + typeCount.put(recipeType, entry.getKey()); } } } @@ -57,8 +58,12 @@ public abstract class AlmostUnifiedRuntime { transformedRecipes, recipes.size(), timeElapsed / 1000_000D); - typeCount.entrySet().stream().sorted(Comparator.comparing(o -> o.getKey().toString())).forEach(entry -> { - AlmostUnified.LOG.info("{}: {}", StringUtils.leftPad(entry.getKey().toString(), 50), entry.getValue()); + // TODO Pls remove this on release + typeCount.asMap().entrySet().stream().sorted(Comparator.comparing(o -> o.getKey().toString())).forEach((e) -> { + AlmostUnified.LOG.info("{}: {} | {}", + StringUtils.leftPad(e.getKey().toString(), 40), + StringUtils.leftPad(String.valueOf(e.getValue().size()), 4), + e.getValue().toString()); }); } @@ -69,16 +74,12 @@ public abstract class AlmostUnifiedRuntime { return null; } - RecipeContextImpl ctx = new RecipeContextImpl(recipeType, id, json, replacementMap); - try { - RecipeHandler recipeHandler = recipeHandlerFactory.create(ctx); - if (recipeHandler == null) { - return null; - } - + RecipeContextImpl ctx = new RecipeContextImpl(recipeType, id, json, replacementMap); + RecipeTransformationsImpl builder = new RecipeTransformationsImpl(); + recipeHandlerFactory.create(builder, ctx); JsonObject copy = json.deepCopy(); - recipeHandler.transformRecipe(copy, ctx); + builder.transform(copy, ctx); if (!json.equals(copy)) { return copy; } @@ -92,7 +93,6 @@ public abstract class AlmostUnifiedRuntime { return null; } - @Nullable protected ResourceLocation getRecipeType(JsonObject recipeJson) { String type = recipeJson.get("type").getAsString(); @@ -108,7 +108,6 @@ public abstract class AlmostUnifiedRuntime { throw new IllegalStateException("Internal error. TagManager was not updated correctly"); } - TagMap tagMap = TagMap.create(tagManager); Map> itemToTagMapping = new HashMap<>(allowedTags.size()); diff --git a/Common/src/main/java/com/almostreliable/unified/RecipeContextImpl.java b/Common/src/main/java/com/almostreliable/unified/RecipeContextImpl.java index 1b2afd8..9351a72 100644 --- a/Common/src/main/java/com/almostreliable/unified/RecipeContextImpl.java +++ b/Common/src/main/java/com/almostreliable/unified/RecipeContextImpl.java @@ -1,7 +1,8 @@ package com.almostreliable.unified; import com.almostreliable.unified.api.RecipeContext; -import com.almostreliable.unified.utils.JsonUtils; +import com.almostreliable.unified.handler.RecipeConstants; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @@ -10,11 +11,10 @@ import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; import javax.annotation.Nullable; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.UnaryOperator; +@SuppressWarnings("SameParameterValue") public class RecipeContextImpl implements RecipeContext { - public static final String TAG = "tag"; - public static final String ITEM = "item"; private final ResourceLocation type; private final ResourceLocation id; private final JsonObject currentRecipe; @@ -53,41 +53,55 @@ public class RecipeContextImpl implements RecipeContext { return replacementMap.getPreferredTag(item); } - @Override - public boolean replaceIngredient(JsonElement element) { - AtomicBoolean changed = new AtomicBoolean(false); + @Nullable + protected JsonElement depthReplace(JsonElement element, String potentialFrom, String potentialTo, UnaryOperator primitiveCallback) { + if (element instanceof JsonPrimitive primitive) { + return primitiveCallback.apply(primitive); + } - JsonUtils.arrayForEach(element, JsonObject.class, json -> { - if (json.get(ITEM) instanceof JsonPrimitive primitive) { - ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); - TagKey tag = getPreferredTagByItem(item); - if (tag != null) { - json.remove(ITEM); - json.addProperty(TAG, tag.location().toString()); - changed.set(true); + if (element instanceof JsonObject object) { + JsonElement replace = depthReplace(object.get(potentialFrom), potentialFrom, potentialTo, primitiveCallback); + if (replace != null) { + object.remove(potentialFrom); + object.add(potentialTo, replace); + } + } + + if (element instanceof JsonArray array) { + for (int i = 0; i < array.size(); i++) { + JsonElement replace = depthReplace(array.get(i), potentialFrom, potentialTo, primitiveCallback); + if (replace != null) { + array.set(i, replace); } } - }); + } - return changed.get(); + return null; } @Override - public boolean replaceResult(JsonElement element) { - AtomicBoolean changed = new AtomicBoolean(false); - - JsonUtils.arrayForEach(element, JsonObject.class, json -> { - if (json.get(ITEM) instanceof JsonPrimitive primitive) { - ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); - ResourceLocation replacement = getReplacementForItem(item); - if (replacement != null) { - json.addProperty(ITEM, replacement.toString()); - changed.set(true); - } + @Nullable + public JsonElement replaceIngredient(JsonElement element) { + return depthReplace(element, RecipeConstants.ITEM, RecipeConstants.TAG, primitive -> { + ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); + TagKey tag = getPreferredTagByItem(item); + if (tag != null) { + return new JsonPrimitive(tag.location().toString()); } + return null; }); + } - return changed.get(); + @Override + public JsonElement replaceResult(JsonElement element) { + return depthReplace(element, RecipeConstants.ITEM, RecipeConstants.ITEM, primitive -> { + ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); + ResourceLocation replacement = getReplacementForItem(item); + if (replacement != null) { + return new JsonPrimitive(replacement.toString()); + } + return null; + }); } @Override diff --git a/Common/src/main/java/com/almostreliable/unified/RecipeTransformationsImpl.java b/Common/src/main/java/com/almostreliable/unified/RecipeTransformationsImpl.java new file mode 100644 index 0000000..b9589e1 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/RecipeTransformationsImpl.java @@ -0,0 +1,78 @@ +package com.almostreliable.unified; + +import com.almostreliable.unified.api.RecipeContext; +import com.almostreliable.unified.api.RecipeTransformations; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; + +public class RecipeTransformationsImpl implements RecipeTransformations { + private final Map> consumers = new HashMap<>(); + + @Override + public void forEachObject(String property, BiFunction consumer) { + BiFunction arrayConsumer = (array, ctx) -> { + for (int i = 0; i < array.size(); i++) { + JsonElement element = array.get(i); + if (element instanceof JsonObject obj) { + JsonObject result = consumer.apply(obj, ctx); + if (result != null) { + array.set(i, result); + } + } + } + return array; + }; + + put(property, JsonArray.class, arrayConsumer); + } + + @Override + public void replaceIngredient(String property) { + put(property, JsonElement.class, (jsonElement, context) -> context.replaceIngredient(jsonElement)); + } + + @Override + public void replaceResult(String property) { + put(property, JsonElement.class, (jsonElement, context) -> context.replaceResult(jsonElement)); + } + + @Override + public void put(String property, BiFunction consumer) { + consumers.put(property, new Entry<>(JsonElement.class, consumer)); + } + + @Override + public void put(String property, Class type, BiFunction consumer) { + consumers.put(property, new Entry<>(type, consumer)); + } + + public void transform(JsonObject json, RecipeContext context) { + for (var e : json.entrySet()) { + Entry consumer = consumers.get(e.getKey()); + if (consumer != null) { + JsonElement result = consumer.apply(e.getValue(), context); + if (result != null) { + e.setValue(result); + } + } + } + } + + private record Entry(Class expectedType, + BiFunction func) { + @Nullable + T apply(JsonElement json, RecipeContext context) { + if (expectedType.isInstance(json)) { + return func.apply(expectedType.cast(json), context); + } + + return null; + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/RecipeContext.java b/Common/src/main/java/com/almostreliable/unified/api/RecipeContext.java index f5ef57a..99ec704 100644 --- a/Common/src/main/java/com/almostreliable/unified/api/RecipeContext.java +++ b/Common/src/main/java/com/almostreliable/unified/api/RecipeContext.java @@ -18,16 +18,14 @@ public interface RecipeContext { @Nullable TagKey getPreferredTagByItem(@Nullable ResourceLocation item); - boolean replaceIngredient(JsonElement element); + JsonElement replaceIngredient(JsonElement element); - boolean replaceResult(JsonElement element); + JsonElement replaceResult(JsonElement element); ResourceLocation getType(); ResourceLocation getId(); -// String getIterateProperty(); - boolean hasProperty(String property); default String getModId() { diff --git a/Common/src/main/java/com/almostreliable/unified/api/RecipeHandler.java b/Common/src/main/java/com/almostreliable/unified/api/RecipeHandler.java index 0cb7b21..2225194 100644 --- a/Common/src/main/java/com/almostreliable/unified/api/RecipeHandler.java +++ b/Common/src/main/java/com/almostreliable/unified/api/RecipeHandler.java @@ -1,13 +1,5 @@ package com.almostreliable.unified.api; -import com.google.gson.JsonObject; - -@FunctionalInterface public interface RecipeHandler { - - void transformRecipe(JsonObject json, RecipeContext context); - - default String getName() { - return getClass().getSimpleName(); - } + void collectTransformations(RecipeTransformations builder); } diff --git a/Common/src/main/java/com/almostreliable/unified/api/RecipeTransformations.java b/Common/src/main/java/com/almostreliable/unified/api/RecipeTransformations.java new file mode 100644 index 0000000..4fa5f05 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/RecipeTransformations.java @@ -0,0 +1,18 @@ +package com.almostreliable.unified.api; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.function.BiFunction; + +public interface RecipeTransformations { + void forEachObject(String property, BiFunction consumer); + + void replaceIngredient(String property); + + void replaceResult(String property); + + void put(String property, BiFunction consumer); + + void put(String property, Class type, BiFunction consumer); +} diff --git a/Common/src/main/java/com/almostreliable/unified/handler/GenericRecipeHandler.java b/Common/src/main/java/com/almostreliable/unified/handler/GenericRecipeHandler.java index 5568b1f..be77e36 100644 --- a/Common/src/main/java/com/almostreliable/unified/handler/GenericRecipeHandler.java +++ b/Common/src/main/java/com/almostreliable/unified/handler/GenericRecipeHandler.java @@ -1,11 +1,7 @@ package com.almostreliable.unified.handler; -import com.almostreliable.unified.api.RecipeContext; import com.almostreliable.unified.api.RecipeHandler; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import net.minecraft.resources.ResourceLocation; +import com.almostreliable.unified.api.RecipeTransformations; import java.util.Set; @@ -18,42 +14,14 @@ public class GenericRecipeHandler implements RecipeHandler { RecipeConstants.RESULT, RecipeConstants.RESULTS); - public boolean hasInputOrOutputProperty(RecipeContext property) { - for (String inputKey : inputKeys) { - if (property.hasProperty(inputKey)) { - return true; - } - } - - for (String outputKey : outputKeys) { - if (property.hasProperty(outputKey)) { - return true; - } - } - - return false; - } - @Override - public void transformRecipe(JsonObject json, RecipeContext context) { + public void collectTransformations(RecipeTransformations builder) { for (String inputKey : inputKeys) { - if (json.has(inputKey)) { - context.replaceIngredient(json.get(inputKey)); - } + builder.replaceIngredient(inputKey); } for (String outputKey : outputKeys) { - JsonElement jsonElement = json.get(outputKey); - if (jsonElement instanceof JsonPrimitive) { - ResourceLocation item = context.getReplacementForItem(ResourceLocation.tryParse(jsonElement.getAsString())); - if (item != null) { - json.addProperty(outputKey, item.toString()); - } - } else if (jsonElement != null) { - // Forge patched recipe results to also allow JsonObjects. See SimpleCookingSerializer::fromJson as an example. - context.replaceResult(jsonElement); - } - + builder.replaceResult(outputKey); } } } diff --git a/Common/src/main/java/com/almostreliable/unified/handler/RecipeHandlerFactory.java b/Common/src/main/java/com/almostreliable/unified/handler/RecipeHandlerFactory.java index d632e71..69c63bc 100644 --- a/Common/src/main/java/com/almostreliable/unified/handler/RecipeHandlerFactory.java +++ b/Common/src/main/java/com/almostreliable/unified/handler/RecipeHandlerFactory.java @@ -2,9 +2,9 @@ package com.almostreliable.unified.handler; import com.almostreliable.unified.api.RecipeContext; import com.almostreliable.unified.api.RecipeHandler; +import com.almostreliable.unified.api.RecipeTransformations; import net.minecraft.resources.ResourceLocation; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; @@ -12,32 +12,23 @@ public class RecipeHandlerFactory { private final Map transformersByType = new HashMap<>(); private final Map transformersByModId = new HashMap<>(); - @Nullable - public RecipeHandler create(RecipeContext context) { + public void create(RecipeTransformations builder, RecipeContext context) { + GenericRecipeHandler.INSTANCE.collectTransformations(builder); + + if (context.hasProperty(ShapedRecipeKeyHandler.PATTERN_PROPERTY) && + context.hasProperty(ShapedRecipeKeyHandler.KEY_PROPERTY)) { + ShapedRecipeKeyHandler.INSTANCE.collectTransformations(builder); + } + RecipeHandler byType = transformersByType.get(context.getType()); if (byType != null) { - return byType; + byType.collectTransformations(builder); } RecipeHandler byMod = transformersByModId.get(context.getModId()); if (byMod != null) { - return byMod; + byMod.collectTransformations(builder); } - - if (context.hasProperty(ShapedRecipeKeyHandler.PATTERN_PROPERTY) && - context.hasProperty(ShapedRecipeKeyHandler.KEY_PROPERTY)) { - return ShapedRecipeKeyHandler.INSTANCE; - } - - if (GenericRecipeHandler.INSTANCE.hasInputOrOutputProperty(context)) { - return GenericRecipeHandler.INSTANCE; - } - - return null; - } - - public void registerForType(String type, RecipeHandler transformer) { - transformersByType.put(new ResourceLocation(type), transformer); } public void registerForType(ResourceLocation type, RecipeHandler transformer) { diff --git a/Common/src/main/java/com/almostreliable/unified/handler/ShapedRecipeKeyHandler.java b/Common/src/main/java/com/almostreliable/unified/handler/ShapedRecipeKeyHandler.java index f998ade..baec7b6 100644 --- a/Common/src/main/java/com/almostreliable/unified/handler/ShapedRecipeKeyHandler.java +++ b/Common/src/main/java/com/almostreliable/unified/handler/ShapedRecipeKeyHandler.java @@ -1,7 +1,7 @@ package com.almostreliable.unified.handler; -import com.almostreliable.unified.api.RecipeContext; import com.almostreliable.unified.api.RecipeHandler; +import com.almostreliable.unified.api.RecipeTransformations; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -11,16 +11,15 @@ public class ShapedRecipeKeyHandler implements RecipeHandler { public static final String KEY_PROPERTY = "key"; @Override - public void transformRecipe(JsonObject json, RecipeContext context) { - if (json.get(KEY_PROPERTY) instanceof JsonObject object) { - for (var entry : object.entrySet()) { - context.replaceIngredient(entry.getValue()); + public void collectTransformations(RecipeTransformations builder) { + builder.put(KEY_PROPERTY, JsonObject.class, (json, context) -> { + for (var entry : json.entrySet()) { + JsonElement result = context.replaceIngredient(entry.getValue()); + if (result != null) { + entry.setValue(result); + } } - } - - JsonElement result = json.get("result"); - if (result != null) { - context.replaceResult(result); - } + return json; + }); } } diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/ie/IERecipeHandler.java b/Forge/src/main/java/com/almostreliable/unified/compat/ie/IERecipeHandler.java index 85ee72d..ade9339 100644 --- a/Forge/src/main/java/com/almostreliable/unified/compat/ie/IERecipeHandler.java +++ b/Forge/src/main/java/com/almostreliable/unified/compat/ie/IERecipeHandler.java @@ -2,6 +2,7 @@ package com.almostreliable.unified.compat.ie; import com.almostreliable.unified.api.RecipeContext; import com.almostreliable.unified.api.RecipeHandler; +import com.almostreliable.unified.api.RecipeTransformations; import com.almostreliable.unified.handler.RecipeConstants; import com.almostreliable.unified.utils.JsonUtils; import com.almostreliable.unified.utils.Utils; @@ -16,24 +17,45 @@ public class IERecipeHandler implements RecipeHandler { // From IE protected static final String BASE_KEY = "base_ingredient"; + // TODO make it cleaner @Override - public void transformRecipe(JsonObject json, RecipeContext context) { + public void collectTransformations(RecipeTransformations builder) { + builder.put("input0", (json, context) -> { + replaceIEIngredient(json, context); + return json; + }); // alloy recipes, refinery + builder.put("input1", (json, context) -> { + replaceIEIngredient(json, context); + return json; + }); // alloy recipes, refinery + builder.put(RecipeConstants.INPUT, (json, context) -> { + replaceIEIngredient(json, context); + return json; + }); // arc furnace, squeezer, cloche, coke oven, fermenter, fertilizer, metal_press + builder.put("additives", (json, context) -> { + replaceIEIngredient(json, context); + return json; + }); // arc furnace + builder.put(RecipeConstants.INPUTS, (json, context) -> { + replaceIEIngredient(json, context); + return json; + }); // blueprint, mixer - replaceIEIngredient(json.get("input0"), context); // alloy recipes, refinery - replaceIEIngredient(json.get("input1"), context); // alloy recipes, refinery - replaceIEIngredient(json.get(RecipeConstants.INPUT), - context); // arc furnace, squeezer, cloche, coke oven, fermenter, fertilizer, metal_press - replaceIEIngredient(json.get("additives"), context); // arc furnace - replaceIEIngredient(json.get(RecipeConstants.INPUTS), context); // blueprint, mixer - - replaceIEResult(json.get("secondaries"), context); // alloy recipes, crusher - - JsonUtils.arrayForEach(json.get("secondaries"), JsonObject.class, secondary -> { - replaceIEResult(secondary.get(RecipeConstants.OUTPUT), context); + // alloy recipes, crusher + builder.forEachObject("secondaries", (jsonObject, context) -> { + replaceIEResult(jsonObject.get(RecipeConstants.OUTPUT), context); + return jsonObject; }); - replaceIEResult(json.get(RecipeConstants.RESULT), context); - replaceIEResult(json.get(RecipeConstants.RESULTS), context); + builder.put(RecipeConstants.RESULT, (json, context) -> { + replaceIEResult(json, context); + return json; + }); + + builder.put(RecipeConstants.RESULTS, (json, context) -> { + replaceIEResult(json, context); + return json; + }); } protected void replaceIEResult(@Nullable JsonElement element, RecipeContext context) {