Skip to content

Commit

Permalink
Add test for CooptCodec and adjust how error recover is handled with …
Browse files Browse the repository at this point in the history
…deserialization
  • Loading branch information
Dragon-Seeker committed Oct 27, 2023
1 parent cfdfc6b commit 2247a50
Show file tree
Hide file tree
Showing 17 changed files with 628 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.wispforest.owo.serialization;

import io.wispforest.owo.serialization.impl.SerializationAttribute;
import org.jetbrains.annotations.Nullable;

import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

public interface Deserializer<T> {

Expand Down Expand Up @@ -33,6 +36,8 @@ public interface Deserializer<T> {

long readVarLong();

<V> V tryRead(Function<Deserializer<T>, V> func);

<E> SequenceDeserializer<E> sequence(Endec<E> elementEndec);

<V> MapDeserializer<V> map(Endec<V> valueEndec);
Expand Down
49 changes: 48 additions & 1 deletion src/main/java/io/wispforest/owo/serialization/Endec.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.gson.*;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import io.wispforest.owo.serialization.impl.*;
import io.wispforest.owo.serialization.impl.nbt.NbtEndec;
import io.wispforest.owo.serialization.impl.json.JsonEndec;
Expand All @@ -10,8 +11,11 @@
import net.minecraft.nbt.*;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import net.minecraft.util.Uuids;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
Expand Down Expand Up @@ -139,6 +143,31 @@ static <T> Endec<T> ofRegistry(Registry<T> registry) {
return Endec.IDENTIFIER.then(registry::get, registry::getId);
}

static <T> Endec<TagKey<T>> unprefixedTagKey(RegistryKey<? extends Registry<T>> registry) {
return IDENTIFIER.then(id -> TagKey.of(registry, id), TagKey::id);
}

static <T> Endec<TagKey<T>> tagKey(RegistryKey<? extends Registry<T>> registry) {
return Endec.STRING
.validate(s -> {
if(!s.startsWith("#")) throw new IllegalStateException("Not a tag id");

var id = s.substring(1);

try {
if(!Identifier.isValid(id)) throw new IllegalStateException("Not a valid resource location: " + id);
} catch (InvalidIdentifierException var2) {
throw new IllegalStateException("Not a valid resource location: " + id + " " + var2.getMessage());
}

return s;
})
.then(
s -> TagKey.of(registry, new Identifier(s.substring(1))),
tag -> "#" + tag.id()
);
}

static <T, K> Endec<T> dispatchedOf(Function<K, Endec<? extends T>> keyToEndec, Function<T, K> keyGetter, Endec<K> keyEndec) {
return new StructEndec<T>() {
@Override
Expand Down Expand Up @@ -204,6 +233,10 @@ public <E> R decode(Deserializer<E> deserializer) {
};
}

default <R> StructField<R, T> field(String name, Function<R, T> getter){
return StructField.of(name, this, getter);
}

default Endec<Optional<T>> ofOptional(){
return new Endec<>() {
@Override
Expand All @@ -222,6 +255,20 @@ public <E> Optional<T> decode(Deserializer<E> deserializer) {
return ofOptional().then(o -> o.orElse(null), Optional::ofNullable);
}

default Endec<T> validate(Function<T, T> validator){
return new Endec<T>() {
@Override
public <E> void encode(Serializer<E> serializer, T value) {
Endec.this.encode(serializer, value);
}

@Override
public <E> T decode(Deserializer<E> deserializer) {
return validator.apply(Endec.this.decode(deserializer));
}
};
}

default Endec<T> onError(TriConsumer<Serializer, T, Exception> encode, BiFunction<Deserializer, Exception, T> decode){
return new Endec<>() {
@Override
Expand All @@ -236,7 +283,7 @@ public <E> void encode(Serializer<E> serializer, T value) {
@Override
public <E> T decode(Deserializer<E> deserializer) {
try {
return Endec.this.decode(deserializer);
return deserializer.tryRead(Endec.this::decode);
} catch (Exception e) {
return decode.apply(deserializer, e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.wispforest.owo.serialization.endecs;

import com.mojang.datafixers.util.Either;
import io.wispforest.owo.serialization.Deserializer;
import io.wispforest.owo.serialization.Endec;
import io.wispforest.owo.serialization.Serializer;
import io.wispforest.owo.serialization.impl.SerializationAttribute;

import java.util.Objects;
import java.util.Optional;

public record EitherEndec<F, S>(Endec<F> first, Endec<S> second) implements Endec<Either<F, S>> {

@Override
public <E> void encode(Serializer<E> serializer, Either<F, S> either) {
boolean selfDescribing = serializer.attributes().contains(SerializationAttribute.SELF_DESCRIBING);

if(!selfDescribing){
either.ifLeft(left -> {
try(var struct = serializer.struct()) {
struct.field("side", Endec.VAR_INT, 0)
.field("value", first, left);
}
}).ifRight(right -> {
try(var struct = serializer.struct()) {
struct.field("side", Endec.VAR_INT, 1)
.field("value", second, right);
}
});

return;
}

either.ifLeft(left -> first.encode(serializer, left))
.ifRight(right -> second.encode(serializer, right));
}

@Override
public <E> Either<F, S> decode(Deserializer<E> deserializer) {
boolean selfDescribing = deserializer.attributes().contains(SerializationAttribute.SELF_DESCRIBING);

if(!selfDescribing){
var struct = deserializer.struct();

return switch (struct.field("side", Endec.VAR_INT)){
case 0 -> Either.left(struct.field("value", first));
case 1 -> Either.right(struct.field("value", second));
default -> throw new IllegalStateException("Unknown Int value for given Either Endec");
};
}

Optional<Either<F, S>> result1 = Optional.empty();

try {
result1 = Optional.of(Either.left(deserializer.tryRead(first::decode)));
} catch (Exception ignore) {}

Optional<Either<F, S>> result2 = Optional.empty();

try {
result2 = Optional.of(Either.right(deserializer.tryRead(second::decode)));
} catch (Exception ignore) {}

if (result1.isPresent()) return result1.get();
if (result2.isPresent()) return result2.get();

throw new IllegalStateException("Neither alternatives read successfully!");
}

public boolean equals(Object o) {
if (this == o) return true;

if (o != null && this.getClass() == o.getClass()) {
EitherEndec<?, ?> either = (EitherEndec) o;
return Objects.equals(this.first, either.first) && Objects.equals(this.second, either.second);
}

return false;
}

public String toString() {
return "EitherCodec[" + this.first + ", " + this.second + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.wispforest.owo.serialization.endecs;

import io.wispforest.owo.serialization.Endec;
import io.wispforest.owo.serialization.impl.ReflectionEndecBuilder;
import net.minecraft.recipe.book.CraftingRecipeCategory;

import java.util.function.Function;

public class ExtraEndecs {

public static final Endec<Integer> NONNEGATIVE_INT = rangedInt(0, Integer.MAX_VALUE, v -> "Value must be non-negative: " + v);
public static final Endec<Integer> POSITIVE_INT = rangedInt(1, Integer.MAX_VALUE, v -> "Value must be positive: " + v);


private static Endec<Integer> rangedInt(int min, int max, Function<Integer, String> messageFactory) {
return Endec.INT.validate(value -> {
if(value.compareTo(min) >= 0 && value.compareTo(max) <= 0) return value;

throw new IllegalStateException(messageFactory.apply(value));
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.wispforest.owo.serialization.endecs;

import com.mojang.datafixers.util.Either;
import io.wispforest.owo.serialization.Endec;
import io.wispforest.owo.serialization.impl.StructEndecBuilder;
import net.minecraft.recipe.Ingredient;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.collection.DefaultedList;

import java.util.Arrays;
import java.util.List;

public class IngredientEndec {

private static final Endec<Ingredient.StackEntry> STACK_ENTRY_ENDEC = StructEndecBuilder.of(
RecipeEndecs.INGREDIENT.field("item", e -> e.stack),
Ingredient.StackEntry::new
);

private static final Endec<Ingredient.TagEntry> TAG_ENTRY_ENDEC = StructEndecBuilder.of(
Endec.unprefixedTagKey(RegistryKeys.ITEM).field("tag", e -> e.tag),
Ingredient.TagEntry::new
);

private static final Endec<Ingredient.Entry> INGREDIENT_ENTRY_ENDEC = new XorEndec<>(STACK_ENTRY_ENDEC, TAG_ENTRY_ENDEC)
.then(
either -> either.map(stackEntry -> stackEntry, tagEntry -> tagEntry),
entry -> {
if (entry instanceof Ingredient.TagEntry tagEntry) return Either.right(tagEntry);
if (entry instanceof Ingredient.StackEntry stackEntry) return Either.left(stackEntry);

throw new UnsupportedOperationException("This is neither an item value nor a tag value.");
}
);

public static final Endec<Ingredient> ALLOW_EMPTY_CODEC = createEndec(true);
public static final Endec<Ingredient> DISALLOW_EMPTY_CODEC = createEndec(false);

// public static final Endec<Ingredient> RAW_DATA = Endec.ITEM_STACK.list()
// .then(
// stackList -> Ingredient.ofEntries(stackList.stream().map(Ingredient.StackEntry::new)),
// ingredient -> Arrays.stream(ingredient.getMatchingStacks()).toList()
// );

private static Endec<Ingredient> createEndec(boolean allowEmpty) {
Endec<Ingredient.Entry[]> endec = INGREDIENT_ENTRY_ENDEC.list()
.validate(entries -> {
if(!allowEmpty && entries.size() < 1){
throw new IllegalStateException("Item array cannot be empty, at least one item must be defined");
}

return entries;
})
.then(entries -> entries.toArray(new Ingredient.Entry[0]), List::of);

return new EitherEndec<>(endec, INGREDIENT_ENTRY_ENDEC)
.then(
either -> either.map(Ingredient::new, entry -> new Ingredient(new Ingredient.Entry[]{entry})),
ingredient -> {
if(ingredient.entries.length == 0 && !allowEmpty){
throw new IllegalStateException("Item array cannot be empty, at least one item must be defined");
}

return (ingredient.entries.length == 1)
? Either.right(ingredient.entries[0])
: Either.left(ingredient.entries);
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.wispforest.owo.serialization.endecs;

import io.wispforest.owo.serialization.Endec;
import io.wispforest.owo.serialization.impl.ReflectionEndecBuilder;
import io.wispforest.owo.serialization.impl.StructEndecBuilder;
import io.wispforest.owo.serialization.impl.StructField;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.recipe.book.CraftingRecipeCategory;
import net.minecraft.registry.Registries;

public class RecipeEndecs {
private static final Endec<Item> CRAFTING_RESULT_ITEM = Endec.ofRegistry(Registries.ITEM)
.validate(item -> {
if(item == Items.AIR) throw new IllegalStateException("Crafting result must not be minecraft:air");

return item;
});

public static final Endec<ItemStack> CRAFTING_RESULT = StructEndecBuilder.of(
CRAFTING_RESULT_ITEM.field("item", ItemStack::getItem),
StructField.defaulted("count", ExtraEndecs.POSITIVE_INT, ItemStack::getCount, 1),
ItemStack::new
);

static final Endec<ItemStack> INGREDIENT = Endec.ofRegistry(Registries.ITEM)
.validate(item -> {
if(item == Items.AIR) throw new IllegalStateException("Empty ingredient not allowed here");

return item;
})
.then(ItemStack::new, ItemStack::getItem);

public static final Endec<CraftingRecipeCategory> CATEGORY_ENDEC = ReflectionEndecBuilder.createEnumSerializer(CraftingRecipeCategory.class);

}
Loading

0 comments on commit 2247a50

Please sign in to comment.