Skip to content

Commit

Permalink
Rework default field a bit
Browse files Browse the repository at this point in the history
Loadable#field is now Loadable#requiredField to make it clear its a required field, will be going through usages and making sure all want required.
No longer override getAndDeserialize (now called getIfPresent) as that messes with some of the loadable builders (and notably cannot be built int). Plus, you might want to requrie a field that has its own default
Instead, going to encourage creation of custom defaultField impls without a default parameter for such usages
Loadable also now has a getOrDefault helper, same semantics as getIfPresent. Simplifies some field implementions
  • Loading branch information
KnightMiner committed Mar 24, 2024
1 parent db7dfa9 commit a32ab47
Show file tree
Hide file tree
Showing 19 changed files with 73 additions and 95 deletions.
58 changes: 42 additions & 16 deletions src/main/java/slimeknights/mantle/data/loadable/Loadable.java
Expand Up @@ -11,14 +11,16 @@
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import net.minecraft.network.FriendlyByteBuf;
import org.jetbrains.annotations.Contract;
import slimeknights.mantle.data.loadable.field.DefaultingField;
import slimeknights.mantle.data.loadable.field.LoadableField;
import slimeknights.mantle.data.loadable.field.NamedField;
import slimeknights.mantle.data.loadable.field.NullableField;
import slimeknights.mantle.data.loadable.field.RequiredField;
import slimeknights.mantle.data.loadable.mapping.ListLoadable;
import slimeknights.mantle.data.loadable.mapping.MappedLoadable;
import slimeknights.mantle.data.loadable.mapping.SetLoadable;

import javax.annotation.Nullable;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;
Expand All @@ -31,19 +33,6 @@ public interface Loadable<T> extends JsonDeserializer<T>, JsonSerializer<T> {
/** Deserializes the object from json */
T convert(JsonElement element, String key) throws JsonSyntaxException;

/**
* Gets then deserializes the given field
* @param parent Parent to fetch field from
* @param key Field to get
* @return Value
*/
default T getAndDeserialize(JsonObject parent, String key) {
if (parent.has(key)) {
return convert(parent.get(key), key);
}
throw new JsonSyntaxException("Missing JSON field " + key + "");
}

/** Writes this object to json */
JsonElement serialize(T object) throws RuntimeException;

Expand All @@ -67,11 +56,48 @@ default JsonElement serialize(T object, Type type, JsonSerializationContext cont
}


/* Helpers for raw loadable use */

/**
* Gets then deserializes the given fieldm throwing if it is missing
* You should not override this method as we wish to leave that handling missing up to the RecordLoadable.
* Instead, consider a custom implementation of defaultField if you have a standard default.
* @param parent Parent to fetch field from
* @param key Field to get
* @return Value, or throws if missing
*/
default T getIfPresent(JsonObject parent, String key) {
if (parent.has(key)) {
return convert(parent.get(key), key);
}
throw new JsonSyntaxException("Missing JSON field " + key + "");
}

/**
* Gets then deserializes the given field, or returns a default value if its missing.
* You should not override this method as we wish to leave that handling missing up to the RecordLoadable.
* Instead, consider a custom implementation of defaultField if you have a standard default.
* @param parent Parent to fetch field from
* @param key Field to get
* @param defaultValue Default value to fetch
* @return Value or default.
*/
@Nullable
@Contract("_,_,!null->!null")
default T getOrDefault(JsonObject parent, String key, @Nullable T defaultValue) {
JsonElement element = parent.get(key);
if (element != null && !element.isJsonNull()) {
return convert(element, key);
}
return defaultValue;
}


/* Fields */

/** Creates a required field from this loadable */
default <P> LoadableField<T,P> field(String key, Function<P,T> getter) {
return new NamedField<>(this, key, false, getter);
default <P> LoadableField<T,P> requiredField(String key, Function<P,T> getter) {
return new RequiredField<>(this, key, false, getter);
}

/** Creates an optional field that falls back to null */
Expand Down
Expand Up @@ -65,7 +65,7 @@ private static <T extends Comparable<T>> BlockState setValue(BlockState state, P

@Override
public BlockState deserialize(JsonObject json) {
Block block = Loadables.BLOCK.getAndDeserialize(json, "block");
Block block = Loadables.BLOCK.getIfPresent(json, "block");
BlockState state = block.defaultBlockState();
if (json.has("properties")) {
StateDefinition<Block,BlockState> definition = block.getStateDefinition();
Expand Down
Expand Up @@ -39,7 +39,7 @@ private FluidStackLoadable() {}
/** Field for an optional fluid */
private static final LoadableField<Fluid,FluidStack> FLUID = Loadables.FLUID.defaultField("fluid", Fluids.EMPTY, false, FLUID_GETTER);
/** Field for fluid stack count that allows empty */
private static final LoadableField<Integer,FluidStack> AMOUNT = IntLoadable.FROM_ZERO.field("amount", FluidStack::getAmount);
private static final LoadableField<Integer,FluidStack> AMOUNT = IntLoadable.FROM_ZERO.requiredField("amount", FluidStack::getAmount);
/** Field for fluid stack count */
private static final LoadableField<CompoundTag,FluidStack> NBT = NBTLoadable.ALLOW_STRING.nullableField("nbt", FluidStack::getTag);

Expand Down
Expand Up @@ -2,7 +2,6 @@

import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
Expand All @@ -15,14 +14,6 @@ public enum IngredientLoadable implements Loadable<Ingredient> {
ALLOW_EMPTY,
DISALLOW_EMPTY;

@Override
public Ingredient getAndDeserialize(JsonObject parent, String key) {
if (this == ALLOW_EMPTY && !parent.has(key)) {
return Ingredient.EMPTY;
}
return Loadable.super.getAndDeserialize(parent, key);
}

@Override
public Ingredient convert(JsonElement element, String key) throws JsonSyntaxException {
Ingredient ingredient = Ingredient.fromJson(element);
Expand Down
Expand Up @@ -78,11 +78,7 @@ private record NullableNBTField<P>(Loadable<CompoundTag> loadable, String key, F
@Nullable
@Override
public CompoundTag get(JsonObject json) throws JsonSyntaxException {
JsonElement element = json.get(key);
if (element != null) {
return loadable.convert(element, key);
}
return null;
return loadable.getOrDefault(json, key, null);
}

@Override
Expand Down
@@ -1,6 +1,5 @@
package slimeknights.mantle.data.loadable.field;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import slimeknights.mantle.data.loadable.Loadable;
Expand All @@ -15,13 +14,7 @@
public record DefaultingField<T,P>(Loadable<T> loadable, String key, T defaultValue, boolean serializeDefault, Function<P,T> getter) implements AlwaysPresentLoadableField<T,P> {
@Override
public T get(JsonObject json) throws JsonSyntaxException {
if (json.has(key)) {
JsonElement element = json.get(key);
if (!element.isJsonNull()) {
return loadable.convert(element, key);
}
}
return defaultValue;
return loadable.getOrDefault(json, key, defaultValue);
}

@Override
Expand Down
@@ -1,6 +1,5 @@
package slimeknights.mantle.data.loadable.field;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import net.minecraft.network.FriendlyByteBuf;
Expand All @@ -16,13 +15,7 @@
public record NullableField<T,P>(Loadable<T> loadable, String key, Function<P,T> getter) implements LoadableField<T,P> {
@Override
public T get(JsonObject json) throws JsonSyntaxException {
if (json.has(key)) {
JsonElement element = json.get(key);
if (!element.isJsonNull()) {
return loadable.convert(element, key);
}
}
return null;
return loadable.getOrDefault(json, key, null);
}

@Override
Expand Down
Expand Up @@ -13,10 +13,10 @@
* @param <P> Parent object
* @param <T> Loadable type
*/
public record NamedField<T,P>(Loadable<T> loadable, String key, boolean serializeNull, Function<P,T> getter) implements AlwaysPresentLoadableField<T,P> {
public record RequiredField<T,P>(Loadable<T> loadable, String key, boolean serializeNull, Function<P,T> getter) implements AlwaysPresentLoadableField<T,P> {
@Override
public T get(JsonObject json) throws JsonSyntaxException {
return loadable.getAndDeserialize(json, key);
return loadable.getIfPresent(json, key);
}

@Override
Expand Down
Expand Up @@ -3,7 +3,6 @@
import com.google.common.collect.ImmutableCollection;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import lombok.RequiredArgsConstructor;
import net.minecraft.network.FriendlyByteBuf;
Expand Down Expand Up @@ -39,14 +38,6 @@ public C convert(JsonElement element, String key) throws JsonSyntaxException {
return build(builder);
}

@Override
public C getAndDeserialize(JsonObject parent, String key) {
if (minSize == 0 && !parent.has(key)) {
return build(makeBuilder());
}
return Loadable.super.getAndDeserialize(parent, key);
}

@Override
public JsonArray serialize(C collection) {
JsonArray array = new JsonArray();
Expand Down
Expand Up @@ -27,8 +27,8 @@ public PredicateRegistry(String name, IJsonPredicate<T> defaultInstance) {
// create common types
Loadable<List<IJsonPredicate<T>>> list = this.list(2);
invertedLoader = RecordLoadable.create(directField("inverted_type", p -> p.predicate), InvertedJsonPredicate::new);
andLoader = RecordLoadable.create(list.field("predicates", p -> p.children), AndJsonPredicate::new);
orLoader = RecordLoadable.create(list.field("predicates", p -> p.children), OrJsonPredicate::new);
andLoader = RecordLoadable.create(list.requiredField("predicates", p -> p.children), AndJsonPredicate::new);
orLoader = RecordLoadable.create(list.requiredField("predicates", p -> p.children), OrJsonPredicate::new);
// register common types
this.register(Mantle.getResource("any"), defaultInstance.getLoader());
this.register(Mantle.getResource("inverted"), invertedLoader);
Expand Down
Expand Up @@ -32,8 +32,8 @@ public RegistryPredicateRegistry(String name, IJsonPredicate<T> defaultInstance,
this.getter = getter;
this.tagMatcher = tagMatcher;
// create loaders
this.setLoader = RecordLoadable.create(registry.set().field(setKey, p -> p.set), SetPredicate::new);
this.tagLoader = RecordLoadable.create(tagKey.field("tag", p -> p.tag), TagPredicate::new);
this.setLoader = RecordLoadable.create(registry.set().requiredField(setKey, p -> p.set), SetPredicate::new);
this.tagLoader = RecordLoadable.create(tagKey.requiredField("tag", p -> p.tag), TagPredicate::new);
// register loaders
this.register(Mantle.getResource("set"), setLoader);
this.register(Mantle.getResource("tag"), tagLoader);
Expand Down
Expand Up @@ -71,7 +71,7 @@ private static Property<?> parseProperty(Block block, String name, Function<Stri
public static final RecordLoadable<BlockPropertiesPredicate> LOADER = new RecordLoadable<>() {
@Override
public BlockPropertiesPredicate deserialize(JsonObject json) {
Block block = Loadables.BLOCK.getAndDeserialize(json, "block");
Block block = Loadables.BLOCK.getIfPresent(json, "block");
// TODO: this is a bit of a unique case for matcher, as its parsing from a map into a list, think about whether we can do something generic
ImmutableList.Builder<Matcher> builder = ImmutableList.builder();
for (Entry<String, JsonElement> entry : GsonHelper.getAsJsonObject(json, "properties").entrySet()) {
Expand Down
Expand Up @@ -7,7 +7,7 @@

/** Predicate that matches a named source */
public record SourceMessagePredicate(String message) implements DamageSourcePredicate {
public static final RecordLoadable<SourceMessagePredicate> LOADER = RecordLoadable.create(StringLoadable.DEFAULT.field("message", SourceMessagePredicate::message), SourceMessagePredicate::new);
public static final RecordLoadable<SourceMessagePredicate> LOADER = RecordLoadable.create(StringLoadable.DEFAULT.requiredField("message", SourceMessagePredicate::message), SourceMessagePredicate::new);

public SourceMessagePredicate(DamageSource source) {
this(source.getMsgId());
Expand Down
Expand Up @@ -11,7 +11,7 @@
* Predicate that checks if the given entity has the given enchantment on any of their equipment
*/
public record HasEnchantmentEntityPredicate(Enchantment enchantment) implements LivingEntityPredicate {
public static final RecordLoadable<HasEnchantmentEntityPredicate> LOADER = RecordLoadable.create(Loadables.ENCHANTMENT.field("enchantment", HasEnchantmentEntityPredicate::enchantment), HasEnchantmentEntityPredicate::new);
public static final RecordLoadable<HasEnchantmentEntityPredicate> LOADER = RecordLoadable.create(Loadables.ENCHANTMENT.requiredField("enchantment", HasEnchantmentEntityPredicate::enchantment), HasEnchantmentEntityPredicate::new);

@Override
public boolean matches(LivingEntity entity) {
Expand Down
Expand Up @@ -14,7 +14,7 @@ public record MobTypePredicate(MobType type) implements LivingEntityPredicate {
*/
public static final NamedComponentRegistry<MobType> MOB_TYPES = new NamedComponentRegistry<>("Unknown mob type");
/** Loader for a mob type predicate */
public static RecordLoadable<MobTypePredicate> LOADER = RecordLoadable.create(MOB_TYPES.field("mobs", MobTypePredicate::type), MobTypePredicate::new);
public static RecordLoadable<MobTypePredicate> LOADER = RecordLoadable.create(MOB_TYPES.requiredField("mobs", MobTypePredicate::type), MobTypePredicate::new);

@Override
public boolean matches(LivingEntity input) {
Expand Down
Expand Up @@ -65,7 +65,7 @@ public T convert(JsonElement element, String key) throws JsonSyntaxException {
// first try object
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
return loaders.getAndDeserialize(object, "type").deserialize(object);
return loaders.getIfPresent(object, "type").deserialize(object);
}
// try primitive if allowed
if (compact && element.isJsonPrimitive()) {
Expand All @@ -85,14 +85,6 @@ public T deserialize(JsonElement element) {
return convert(element, "[unknown]");
}

@Override
public T getAndDeserialize(JsonObject parent, String key) {
if (defaultInstance != null && !parent.has(key)) {
return defaultInstance;
}
return Loadable.super.getAndDeserialize(parent, key);
}

/** Serializes the object to json, fighting generics */
@SuppressWarnings("unchecked")
private <L extends IHaveLoader> JsonElement serialize(IGenericLoader<L> loader, T src) {
Expand Down Expand Up @@ -177,6 +169,11 @@ public <P> LoadableField<T,P> defaultField(String key, boolean serializeDefault,
throw new IllegalStateException(name + " registry has no default instance, cannot make a default field");
}

/** Creates a defaulting field that does not serialize */
public <P> LoadableField<T,P> defaultField(String key, Function<P,T> getter) {
return defaultField(key, false, getter);
}

/** Creates a field that loads this object directly into the parent JSON object */
public <P> LoadableField<T,P> directField(String typeKey, Function<P,T> getter) {
return new DirectRegistryField<>(this, typeKey, getter);
Expand Down
19 changes: 9 additions & 10 deletions src/main/java/slimeknights/mantle/recipe/helper/ItemOutput.java
Expand Up @@ -2,13 +2,11 @@

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.mojang.serialization.Codec;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import lombok.RequiredArgsConstructor;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
Expand All @@ -17,8 +15,9 @@
import net.minecraft.world.level.ItemLike;
import slimeknights.mantle.data.JsonCodec;
import slimeknights.mantle.data.loadable.Loadable;
import slimeknights.mantle.data.loadable.Loadables;
import slimeknights.mantle.data.loadable.common.ItemStackLoadable;
import slimeknights.mantle.util.JsonHelper;
import slimeknights.mantle.data.loadable.primitive.IntLoadable;

import java.util.function.Supplier;

Expand All @@ -43,7 +42,7 @@ public String toString() {
return "ItemOutput";
}
};
/** Loadable instance for an item output. Custom to handle the eithering behavior */
/** Loadable instance for an item output. Not using EitherLoadable as we just always sync items to keep thing simplier. */
public static Loadable<ItemOutput> LOADABLE = new Loadable<>() {
@Override
public ItemOutput convert(JsonElement element, String key) throws JsonSyntaxException {
Expand Down Expand Up @@ -132,13 +131,13 @@ public static ItemOutput fromJson(JsonElement element) {
// if it has a tag, parse as tag
JsonObject json = element.getAsJsonObject();
if (json.has("tag")) {
TagKey<Item> tag = TagKey.create(Registry.ITEM_REGISTRY, JsonHelper.getResourceLocation(json, "tag"));
int count = GsonHelper.getAsInt(json, "count", 1);
TagKey<Item> tag = Loadables.ITEM_TAG.getIfPresent(json, "tag");
int count = IntLoadable.FROM_ONE.getOrDefault(json, "count", 1);
return fromTag(tag, count);
}

// default: parse as item stack using loadables
return fromStack(ItemStackLoadable.REQUIRED_STACK_NBT.convert(json, "item"));
return fromStack(ItemStackLoadable.REQUIRED_STACK_NBT.deserialize(json));
}

/**
Expand Down Expand Up @@ -175,14 +174,14 @@ public ItemStack get() {

@Override
public JsonElement serialize() {
String itemName = Registry.ITEM.getKey(item).toString();
JsonElement item = Loadables.ITEM.serialize(this.item);
if (count > 1) {
JsonObject json = new JsonObject();
json.addProperty("item", itemName);
json.add("item", item);
json.addProperty("count", count);
return json;
} else {
return new JsonPrimitive(itemName);
return item;
}
}
}
Expand Down

0 comments on commit a32ab47

Please sign in to comment.