Skip to content

Commit f1e92a5

Browse files
committed
Re-test recipe codec, serialization
1 parent 19b8f30 commit f1e92a5

File tree

12 files changed

+304
-58
lines changed

12 files changed

+304
-58
lines changed

src/api/java/dev/compactmods/crafting/api/recipe/IMiniaturizationRecipe.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.compactmods.crafting.api.recipe;
22

3+
import java.util.Collection;
34
import java.util.Optional;
45
import dev.compactmods.crafting.api.components.IRecipeComponents;
56
import dev.compactmods.crafting.api.recipe.layers.IRecipeLayer;
@@ -21,4 +22,6 @@ public interface IMiniaturizationRecipe {
2122
Optional<IRecipeLayer> getLayer(int layer);
2223

2324
IRecipeComponents getComponents();
25+
26+
void setOutputs(Collection<ItemStack> outputs);
2427
}

src/main/java/dev/compactmods/crafting/CompactCrafting.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class CompactCrafting
2121
{
2222
// Directly reference a log4j logger.
2323
public static final Logger LOGGER = LogManager.getLogger(CompactCrafting.MOD_ID);
24-
public static final Logger RECIPE_LOGGER = LogManager.getLogger("CCRecipeMatcher");
24+
public static final Logger RECIPE_LOGGER = LogManager.getLogger("CCRecipeLoader");
2525

2626
public static final String MOD_ID = "compactcrafting";
2727

src/main/java/dev/compactmods/crafting/recipes/MiniaturizationRecipe.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ public class MiniaturizationRecipe extends RecipeBase implements IMiniaturizatio
4040
* Only used for recipe dimension calculation from loading phase.
4141
* Specifies the minimum field size required for fluid recipe layers.
4242
*/
43-
private final int recipeSize;
43+
private int recipeSize;
4444
private ResourceLocation id;
4545
private Map<Integer, IRecipeLayer> layers;
4646
private final ItemStack catalyst;
47-
private final ItemStack[] outputs;
47+
private ItemStack[] outputs;
4848
private AxisAlignedBB dimensions;
4949

5050
private Map<String, Integer> cachedComponentTotals;
@@ -117,7 +117,7 @@ void applyComponents(Map<String, IRecipeComponent> compMap) {
117117
}
118118
}
119119

120-
private void applyLayers(List<IRecipeLayer> layers) {
120+
public void applyLayers(List<IRecipeLayer> layers) {
121121
this.layers = new HashMap<>();
122122
ArrayList<IRecipeLayer> rev = new ArrayList<>(layers);
123123
Collections.reverse(rev);
@@ -329,6 +329,11 @@ public IRecipeComponents getComponents() {
329329
return this.components;
330330
}
331331

332+
@Override
333+
public void setOutputs(Collection<ItemStack> outputs) {
334+
this.outputs = outputs.toArray(new ItemStack[0]);
335+
}
336+
332337
public void setComponents(IRecipeComponents components) {
333338
this.components = components;
334339
}
@@ -386,7 +391,15 @@ public boolean hasSpecifiedSize() {
386391
return FieldProjectionSize.canFitDimensions(this.recipeSize);
387392
}
388393

389-
public int getSize() {
394+
public int getRecipeSize() {
390395
return this.recipeSize;
391396
}
397+
398+
public void setRecipeSize(int size) {
399+
if(!FieldProjectionSize.canFitDimensions(size))
400+
return;
401+
402+
this.recipeSize = size;
403+
this.recalculateDimensions();
404+
}
392405
}

src/main/java/dev/compactmods/crafting/recipes/MiniaturizationRecipeCodec.java

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
package dev.compactmods.crafting.recipes;
22

3+
import java.util.Collections;
34
import java.util.List;
45
import java.util.Map;
6+
import java.util.Optional;
57
import com.google.common.collect.ImmutableList;
68
import com.mojang.datafixers.util.Pair;
7-
import com.mojang.serialization.Codec;
8-
import com.mojang.serialization.DataResult;
9-
import com.mojang.serialization.DynamicOps;
10-
import com.mojang.serialization.RecordBuilder;
9+
import com.mojang.serialization.*;
1110
import dev.compactmods.crafting.CompactCrafting;
12-
import dev.compactmods.crafting.recipes.components.RecipeComponentTypeCodec;
13-
import dev.compactmods.crafting.recipes.layers.RecipeLayerTypeCodec;
14-
import dev.compactmods.crafting.server.ServerConfig;
1511
import dev.compactmods.crafting.api.components.IRecipeComponent;
1612
import dev.compactmods.crafting.api.components.RecipeComponentType;
1713
import dev.compactmods.crafting.api.field.FieldProjectionSize;
1814
import dev.compactmods.crafting.api.recipe.layers.IRecipeLayer;
1915
import dev.compactmods.crafting.api.recipe.layers.RecipeLayerType;
2016
import dev.compactmods.crafting.api.recipe.layers.dim.IFixedSizedRecipeLayer;
17+
import dev.compactmods.crafting.recipes.components.RecipeComponentTypeCodec;
18+
import dev.compactmods.crafting.recipes.layers.RecipeLayerTypeCodec;
19+
import dev.compactmods.crafting.server.ServerConfig;
2120
import net.minecraft.item.ItemStack;
2221

2322
public class MiniaturizationRecipeCodec implements Codec<MiniaturizationRecipe> {
@@ -38,53 +37,85 @@ public <T> DataResult<Pair<MiniaturizationRecipe, T>> decode(DynamicOps<T> ops,
3837
CompactCrafting.RECIPE_LOGGER.debug("Starting recipe decode: {}", input.toString());
3938
}
4039

40+
MiniaturizationRecipe recipe = new MiniaturizationRecipe();
41+
StringBuilder errorBuilder = new StringBuilder();
42+
4143
int recipeSize = Codec.INT.optionalFieldOf("recipeSize", -1)
4244
.codec()
4345
.parse(ops, input)
44-
.result().get();
46+
.result()
47+
.get();
4548

46-
ItemStack catalyst = ItemStack.CODEC.fieldOf("catalyst").codec()
47-
.parse(ops, input)
48-
.resultOrPartial(CompactCrafting.RECIPE_LOGGER::error)
49-
.orElse(ItemStack.EMPTY);
49+
recipe.setRecipeSize(recipeSize);
5050

51-
List<IRecipeLayer> layers = LAYER_CODEC.listOf().fieldOf("layers").codec()
52-
.parse(ops, input)
53-
.resultOrPartial(CompactCrafting.RECIPE_LOGGER::error)
54-
.get();
51+
final DataResult<List<IRecipeLayer>> layers = LAYER_CODEC.listOf()
52+
.fieldOf("layers").codec()
53+
.parse(ops, input);
5554

56-
List<ItemStack> outputs = ItemStack.CODEC.listOf().fieldOf("outputs").codec()
57-
.parse(ops, input)
58-
.resultOrPartial(CompactCrafting.RECIPE_LOGGER::error)
59-
.get();
55+
if (layers.error().isPresent()) {
56+
final Optional<List<IRecipeLayer>> partialLayers = layers.resultOrPartial(errorBuilder::append);
57+
partialLayers.ifPresent(recipe::applyLayers);
58+
return DataResult.error(errorBuilder.toString(), Pair.of(recipe, input), Lifecycle.stable());
59+
}
6060

61-
Map<String, IRecipeComponent> components = Codec.unboundedMap(Codec.STRING, COMPONENT_CODEC).fieldOf("components")
62-
.codec()
63-
.parse(ops, input)
64-
.resultOrPartial(CompactCrafting.RECIPE_LOGGER::error)
65-
.get();
61+
final List<IRecipeLayer> layerList = layers
62+
.resultOrPartial(errorBuilder::append)
63+
.orElse(Collections.emptyList());
64+
65+
recipe.applyLayers(layerList);
6666

67-
boolean hasFixedLayers = layers.stream().anyMatch(l -> l instanceof IFixedSizedRecipeLayer);
67+
boolean hasFixedLayers = layerList.stream().anyMatch(l -> l instanceof IFixedSizedRecipeLayer);
6868
if (debugOutput) {
69-
CompactCrafting.RECIPE_LOGGER.debug("Number of layers defined: {}", layers.size());
69+
CompactCrafting.RECIPE_LOGGER.debug("Number of layers defined: {}", layerList.size());
7070
CompactCrafting.RECIPE_LOGGER.debug("Is fixed size: {}", hasFixedLayers);
7171
}
7272

7373
// if we don't have a fixed size layer to base dimensions off of, and the recipe size won't fit in a field
7474
if (!hasFixedLayers && !FieldProjectionSize.canFitDimensions(recipeSize)) {
75-
MiniaturizationRecipe partial = new MiniaturizationRecipe(layers, catalyst, outputs, components);
76-
return DataResult.error(
77-
"Specified recipe size will not fit in a crafting field: " + recipeSize,
78-
Pair.of(partial, input));
75+
errorBuilder.append("Specified recipe size will not fit in a crafting field: ").append(recipeSize);
76+
return DataResult.error(errorBuilder.toString(), Pair.of(recipe, input), Lifecycle.stable());
7977
}
8078

81-
MiniaturizationRecipe recipe = new MiniaturizationRecipe(recipeSize, layers, catalyst, outputs, components);
8279
recipe.recalculateDimensions();
8380

81+
ItemStack catalyst = ItemStack.CODEC.fieldOf("catalyst").codec()
82+
.parse(ops, input)
83+
.resultOrPartial(errorBuilder::append)
84+
.orElse(ItemStack.EMPTY);
85+
86+
if (catalyst.isEmpty()) {
87+
CompactCrafting.LOGGER.warn("Warning: recipe has no catalyst; this may be unintentional.");
88+
}
89+
90+
Optional<List<ItemStack>> outputs = ItemStack.CODEC.listOf().fieldOf("outputs").codec()
91+
.parse(ops, input)
92+
.resultOrPartial(errorBuilder::append);
93+
94+
outputs.ifPresent(recipe::setOutputs);
95+
if (!outputs.isPresent()) {
96+
return DataResult.error(errorBuilder.toString(), Pair.of(recipe, input), Lifecycle.stable());
97+
}
98+
99+
if(recipe.getOutputs().length == 0) {
100+
errorBuilder.append("No outputs were defined.");
101+
return DataResult.error(errorBuilder.toString(), Pair.of(recipe, input), Lifecycle.stable());
102+
}
103+
104+
Optional<Map<String, IRecipeComponent>> components = Codec.unboundedMap(Codec.STRING, COMPONENT_CODEC)
105+
.optionalFieldOf("components", Collections.emptyMap())
106+
.codec()
107+
.parse(ops, input)
108+
.resultOrPartial(errorBuilder::append);
109+
110+
components.ifPresent(compNode -> {
111+
CompactCrafting.RECIPE_LOGGER.trace("Got components map; checking any exist and applying to recipe.");
112+
recipe.applyComponents(compNode);
113+
});
114+
84115
if (debugOutput)
85116
CompactCrafting.RECIPE_LOGGER.debug("Finishing recipe decode.");
86117

87-
return DataResult.success(Pair.of(recipe, input));
118+
return DataResult.success(Pair.of(recipe, input), Lifecycle.stable());
88119
}
89120

90121
@Override
@@ -110,7 +141,7 @@ public <T> DataResult<T> encode(MiniaturizationRecipe recipe, DynamicOps<T> ops,
110141
builder.add("type", Codec.STRING.encodeStart(ops, "compactcrafting:miniaturization"));
111142

112143
if (recipe.hasSpecifiedSize())
113-
builder.add("recipeSize", Codec.INT.encodeStart(ops, recipe.getSize()));
144+
builder.add("recipeSize", Codec.INT.encodeStart(ops, recipe.getRecipeSize()));
114145

115146
return builder.add("layers", layers)
116147
.add("components", components)

src/main/java/dev/compactmods/crafting/recipes/MiniaturizationRecipeSerializer.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public MiniaturizationRecipe fromNetwork(ResourceLocation recipeId, PacketBuffer
4040
boolean debugReg = ServerConfig.RECIPE_REGISTRATION.get();
4141
if (debugReg) CompactCrafting.LOGGER.debug("Starting recipe read: {}", recipeId);
4242

43+
if(!buffer.isReadable() || buffer.readableBytes() == 0) {
44+
CompactCrafting.LOGGER.error("Recipe not readable from buffer: {}", recipeId);
45+
return null;
46+
}
47+
4348
try {
4449
final MiniaturizationRecipe recipe = buffer.readWithCodec(MiniaturizationRecipe.CODEC);
4550
recipe.setId(recipeId);
@@ -49,9 +54,8 @@ public MiniaturizationRecipe fromNetwork(ResourceLocation recipeId, PacketBuffer
4954
return recipe;
5055
} catch (IOException e) {
5156
CompactCrafting.LOGGER.error(String.format("Miniaturization recipe failed to decode: %s", recipeId), e);
57+
return null;
5258
}
53-
54-
return null;
5559
}
5660

5761
@Override
@@ -63,9 +67,6 @@ public void toNetwork(PacketBuffer buffer, MiniaturizationRecipe recipe) {
6367
try {
6468
buffer.writeWithCodec(MiniaturizationRecipe.CODEC, recipe);
6569
} catch (IOException ioe) {
66-
if(recipe != null)
67-
CompactCrafting.LOGGER.error(String.format("Failed to encode recipe for network: %s", recipe.getRecipeIdentifier()), ioe);
68-
else
6970
CompactCrafting.LOGGER.error("Failed to encode recipe for network.", ioe);
7071
}
7172
}

src/test/java/dev/compactmods/crafting/tests/recipes/data/MiniaturizationRecipeCodecTests.java

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import java.util.Map;
44
import java.util.Optional;
55
import com.google.gson.JsonElement;
6+
import com.mojang.datafixers.util.Either;
67
import com.mojang.serialization.DataResult;
78
import com.mojang.serialization.JsonOps;
9+
import dev.compactmods.crafting.api.components.IRecipeComponents;
810
import dev.compactmods.crafting.api.recipe.layers.IRecipeLayer;
911
import dev.compactmods.crafting.recipes.MiniaturizationRecipe;
1012
import dev.compactmods.crafting.server.ServerConfig;
@@ -29,7 +31,7 @@ static void BeforeAllTests() {
2931
@Test
3032
@Tag("minecraft")
3133
void LoadsRecipeFromJson() {
32-
JsonElement json = FileHelper.INSTANCE.getJsonFromFile("recipes/layers.json");
34+
JsonElement json = FileHelper.INSTANCE.getJsonFromFile("recipes/basic.json");
3335

3436
MiniaturizationRecipe.CODEC.parse(JsonOps.INSTANCE, json)
3537
.resultOrPartial(Assertions::fail)
@@ -39,15 +41,75 @@ void LoadsRecipeFromJson() {
3941
@Test
4042
@Tag("minecraft")
4143
void RequiresRecipeSizeOnAllDynamicLayers() {
42-
MiniaturizationRecipe recipe = RecipeTestUtil.getRecipeFromFile("recipes/no_size_dynamic.json");
44+
JsonElement json = FileHelper.INSTANCE.getJsonFromFile("recipes/fail_no_size_dynamic.json");
4345

44-
// TODO
46+
Optional<DataResult.PartialResult<MiniaturizationRecipe>> loaded = MiniaturizationRecipe.CODEC
47+
.parse(JsonOps.INSTANCE, json)
48+
.error();
49+
50+
Assertions.assertTrue(loaded.isPresent());
51+
52+
String error = loaded.get().message();
53+
Assertions.assertNotNull(error);
54+
}
55+
56+
@Test
57+
@Tag("minecraft")
58+
void DoesNotFailIfNoComponentsDefined() {
59+
JsonElement json = FileHelper.INSTANCE.getJsonFromFile("recipes/warn_no_components.json");
60+
61+
Either<MiniaturizationRecipe, DataResult.PartialResult<MiniaturizationRecipe>> loaded = MiniaturizationRecipe.CODEC
62+
.parse(JsonOps.INSTANCE, json)
63+
.get();
64+
65+
Assertions.assertFalse(loaded.right().isPresent());
66+
loaded.ifLeft(result -> {
67+
final IRecipeComponents components = result.getComponents();
68+
Assertions.assertNotNull(components);
69+
70+
// Even though the recipe loaded, it should have remapped the missing component as an empty block
71+
Assertions.assertTrue(components.hasBlock("I"), "Expected components to have added an empty I block.");
72+
Assertions.assertTrue(components.isEmptyBlock("I"), "Expected components to have an empty I block.");
73+
});
74+
}
75+
76+
@Test
77+
@Tag("minecraft")
78+
void PartialResultIfNoOutputsEntryExists() {
79+
JsonElement json = FileHelper.INSTANCE.getJsonFromFile("recipes/fail_no_outputs_entry.json");
80+
81+
Either<MiniaturizationRecipe, DataResult.PartialResult<MiniaturizationRecipe>> loaded = MiniaturizationRecipe.CODEC
82+
.parse(JsonOps.INSTANCE, json)
83+
.get();
84+
85+
Assertions.assertTrue(loaded.right().isPresent());
86+
loaded.ifRight(partial -> {
87+
final String message = partial.message();
88+
Assertions.assertTrue(message.contains("outputs"));
89+
});
90+
}
91+
92+
@Test
93+
@Tag("minecraft")
94+
void PartialResultIfNoOutputsExist() {
95+
JsonElement json = FileHelper.INSTANCE.getJsonFromFile("recipes/fail_no_outputs.json");
96+
97+
Either<MiniaturizationRecipe, DataResult.PartialResult<MiniaturizationRecipe>> loaded = MiniaturizationRecipe.CODEC
98+
.parse(JsonOps.INSTANCE, json)
99+
.get();
100+
101+
Assertions.assertFalse(loaded.left().isPresent());
102+
Assertions.assertTrue(loaded.right().isPresent());
103+
loaded.ifRight(partial -> {
104+
final String message = partial.message();
105+
Assertions.assertTrue(message.contains("No outputs"));
106+
});
45107
}
46108

47109
@Test
48110
@Tag("minecraft")
49111
void LoadsRecipeLayersCorrectly() {
50-
MiniaturizationRecipe recipe = RecipeTestUtil.getRecipeFromFile("recipes/layers.json");
112+
MiniaturizationRecipe recipe = RecipeTestUtil.getRecipeFromFile("recipes/basic.json");
51113
if (recipe == null) {
52114
Assertions.fail("No recipe was loaded.");
53115
} else {
@@ -72,7 +134,7 @@ void LoadsRecipeLayersCorrectly() {
72134
@Test
73135
@Tag("minecraft")
74136
void MakesRoundTripThroughNbtCorrectly() {
75-
MiniaturizationRecipe recipe = RecipeTestUtil.getRecipeFromFile("recipes/layers.json");
137+
MiniaturizationRecipe recipe = RecipeTestUtil.getRecipeFromFile("recipes/basic.json");
76138
if (recipe == null) {
77139
Assertions.fail("No recipe was loaded.");
78140
} else {

0 commit comments

Comments
 (0)