diff options
| author | isXander <xander@isxander.dev> | 2024-06-11 23:13:49 +0100 |
|---|---|---|
| committer | isXander <xander@isxander.dev> | 2024-06-11 23:13:57 +0100 |
| commit | 305718e163f91802a4bc1c1ed6540febb2ce204e (patch) | |
| tree | d72fe8b95dab1ef89f67b13a19f8c06fdb582c28 /src/main/java/dev/isxander/yacl3/config | |
| parent | 65b4f7ba8374bbaebc6a431f8347ffc3e8afdced (diff) | |
| download | YetAnotherConfigLib-305718e163f91802a4bc1c1ed6540febb2ce204e.tar.gz YetAnotherConfigLib-305718e163f91802a4bc1c1ed6540febb2ce204e.tar.bz2 YetAnotherConfigLib-305718e163f91802a4bc1c1ed6540febb2ce204e.zip | |
codec config and rewritten kotlin dsl
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/config')
12 files changed, 486 insertions, 15 deletions
diff --git a/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java b/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java index 83a0b1c..a0f7ee4 100644 --- a/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java +++ b/src/main/java/dev/isxander/yacl3/config/GsonConfigInstance.java @@ -68,12 +68,12 @@ public class GsonConfigInstance<T> extends ConfigInstance<T> { this.path = path; this.gson = builder .setExclusionStrategies(new ConfigExclusionStrategy()) - /*? if >1.20.4 { */ + /*? if >1.20.4 {*/ .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter(RegistryAccess.EMPTY)) - /*? } elif =1.20.4 {*//* - .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter()) - *//*? } else {*//* - .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) + /*?} elif =1.20.4 {*/ + /*.registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter()) + *//*?} else {*/ + /*.registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) *//*?}*/ .registerTypeHierarchyAdapter(Style.class, /*? if >=1.20.4 {*/new GsonConfigSerializer.StyleTypeAdapter()/*?} else {*//*new Style.Serializer()*//*?}*/) .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) @@ -169,12 +169,12 @@ public class GsonConfigInstance<T> extends ConfigInstance<T> { private UnaryOperator<GsonBuilder> gsonBuilder = builder -> builder .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .serializeNulls() - /*? if >1.20.4 { */ + /*? if >1.20.4 {*/ .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter(RegistryAccess.EMPTY)) - /*? } elif =1.20.4 {*//* - .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter()) - *//*? } else {*//* - .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) + /*?} elif =1.20.4 {*/ + /*.registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter()) + *//*?} else {*/ + /*.registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) *//*?}*/ .registerTypeHierarchyAdapter(Style.class, /*? if >=1.20.4 {*/new GsonConfigSerializer.StyleTypeAdapter()/*?} else {*//*new Style.Serializer()*//*?}*/) .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java index 3492c55..70e0e0b 100644 --- a/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java @@ -221,12 +221,12 @@ public class GsonConfigSerializer<T> extends ConfigSerializer<T> { private UnaryOperator<GsonBuilder> gsonBuilder = builder -> builder .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .serializeNulls() - /*? if >1.20.4 { */ + /*? if >1.20.4 {*/ .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter(RegistryAccess.EMPTY)) - /*? } elif =1.20.4 {*//* - .registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter()) - *//*? } else {*//* - .registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) + /*?} elif =1.20.4 {*/ + /*.registerTypeHierarchyAdapter(Component.class, new Component.SerializerAdapter()) + *//*?} else {*/ + /*.registerTypeHierarchyAdapter(Component.class, new Component.Serializer()) *//*?}*/ .registerTypeHierarchyAdapter(Style.class, /*? if >=1.20.4 {*/new StyleTypeAdapter()/*?} else {*//*new Style.Serializer()*//*?}*/) .registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter()) diff --git a/src/main/java/dev/isxander/yacl3/config/v3/AbstractConfigEntry.java b/src/main/java/dev/isxander/yacl3/config/v3/AbstractConfigEntry.java new file mode 100644 index 0000000..8092f23 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/AbstractConfigEntry.java @@ -0,0 +1,48 @@ +package dev.isxander.yacl3.config.v3; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Function; +import java.util.function.UnaryOperator; + +@ApiStatus.Experimental +public abstract class AbstractConfigEntry<T> extends AbstractReadonlyConfigEntry<T> implements ConfigEntry<T> { + private T value; + private final T defaultValue; + + private Function<T, T> setModifier; + + public AbstractConfigEntry(String fieldName, T defaultValue) { + super(fieldName); + this.value = defaultValue; + this.defaultValue = defaultValue; + this.setModifier = UnaryOperator.identity(); + } + + @Override + protected T innerGet() { + return this.value; + } + + @Override + public void set(T value) { + this.value = this.setModifier.apply(value); + } + + @Override + public T defaultValue() { + return this.defaultValue; + } + + @Override + public ConfigEntry<T> modifyGet(UnaryOperator<T> modifier) { + super.modifyGet(modifier); + return this; + } + + @Override + public ConfigEntry<T> modifySet(UnaryOperator<T> modifier) { + this.setModifier = this.setModifier.andThen(modifier); + return this; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/AbstractReadonlyConfigEntry.java b/src/main/java/dev/isxander/yacl3/config/v3/AbstractReadonlyConfigEntry.java new file mode 100644 index 0000000..7f59853 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/AbstractReadonlyConfigEntry.java @@ -0,0 +1,37 @@ +package dev.isxander.yacl3.config.v3; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Function; +import java.util.function.UnaryOperator; + +@ApiStatus.Experimental +public abstract class AbstractReadonlyConfigEntry<T> implements ReadonlyConfigEntry<T> { + private final String fieldName; + + private Function<T, T> getModifier; + + public AbstractReadonlyConfigEntry(String fieldName) { + this.fieldName = fieldName; + this.getModifier = UnaryOperator.identity(); + } + + @Override + public String fieldName() { + return fieldName; + } + + @Override + public T get() { + return this.getModifier.apply(this.innerGet()); + } + + protected abstract T innerGet(); + + @Override + public ReadonlyConfigEntry<T> modifyGet(UnaryOperator<T> modifier) { + this.getModifier = this.getModifier.andThen(modifier); + return this; + } + +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/ChildConfigEntryImpl.java b/src/main/java/dev/isxander/yacl3/config/v3/ChildConfigEntryImpl.java new file mode 100644 index 0000000..5b608de --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/ChildConfigEntryImpl.java @@ -0,0 +1,49 @@ +package dev.isxander.yacl3.config.v3; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.RecordBuilder; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Optional; + +@ApiStatus.Experimental +public class ChildConfigEntryImpl<T extends CodecConfig<T>> extends AbstractReadonlyConfigEntry<T> { + private final T config; + private final MapCodec<T> mapCodec; + + public ChildConfigEntryImpl(String fieldName, T config) { + super(fieldName); + this.config = config; + this.mapCodec = config.fieldOf(this.fieldName()); + } + + @Override + protected T innerGet() { + return config; + } + + @Override + public <R> RecordBuilder<R> encode(DynamicOps<R> ops, RecordBuilder<R> recordBuilder) { + return mapCodec.encode(config, ops, recordBuilder); + } + + @Override + public <R> boolean decode(R encoded, DynamicOps<R> ops) { + DataResult<T> result = mapCodec.decoder().parse(ops, encoded); + + //? if >1.20.4 { + Optional<DataResult.Error<T>> error = result.error(); + //?} else { + /*Optional<DataResult.PartialResult<T>> error = result.error(); + *///?} + if (error.isPresent()) { + YACLConstants.LOGGER.error("Failed to decode entry {}: {}", this.fieldName(), error.get().message()); + return false; + } + + return true; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/CodecConfig.java b/src/main/java/dev/isxander/yacl3/config/v3/CodecConfig.java new file mode 100644 index 0000000..833a7ef --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/CodecConfig.java @@ -0,0 +1,77 @@ +package dev.isxander.yacl3.config.v3; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.*; +import org.jetbrains.annotations.ApiStatus; + +import java.util.ArrayList; +import java.util.List; + +@ApiStatus.Experimental +public abstract class CodecConfig<S extends CodecConfig<S>> implements EntryAddable, Codec<S> { + private final List<ReadonlyConfigEntry<?>> entries = new ArrayList<>(); + + public CodecConfig() { + // cast here to throw immediately on construction + var ignored = (S) this; + } + + @Override + public <T> ConfigEntry<T> register(String fieldName, T defaultValue, Codec<T> codec) { + ConfigEntry<T> entry = new CodecConfigEntryImpl<>(fieldName, defaultValue, codec); + entries.add(entry); + return entry; + } + + @Override + public <T extends CodecConfig<T>> ReadonlyConfigEntry<T> register(String fieldName, T configInstance) { + ReadonlyConfigEntry<T> entry = new ChildConfigEntryImpl<>(fieldName, configInstance); + entries.add(entry); + return entry; + } + + protected void onFinishedDecode(boolean successful) { + } + + @Override + public <R> DataResult<R> encode(S input, DynamicOps<R> ops, R prefix) { + if (input != null && input != this) { + throw new IllegalArgumentException("`input` is ignored. It must be null or equal to `this`."); + } + + return this.encode(ops, prefix); + } + + @Override + public <R> DataResult<Pair<S, R>> decode(DynamicOps<R> ops, R input) { + this.decode(input, ops); + return DataResult.success(Pair.of((S) this, input)); + } + + public final <R> DataResult<R> encode(DynamicOps<R> ops, R prefix) { + RecordBuilder<R> builder = ops.mapBuilder(); + for (ReadonlyConfigEntry<?> entry : entries) { + builder = entry.encode(ops, builder); + } + return builder.build(prefix); + } + + public final <R> DataResult<R> encodeStart(DynamicOps<R> ops) { + return this.encode(ops, ops.empty()); + } + + /** + * @return true if decoding of all entries was successful + */ + public final <R> boolean decode(R encoded, DynamicOps<R> ops) { + boolean success = true; + + for (ReadonlyConfigEntry<?> entry : entries) { + success &= entry.decode(encoded, ops); + } + + onFinishedDecode(success); + + return success; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/CodecConfigEntryImpl.java b/src/main/java/dev/isxander/yacl3/config/v3/CodecConfigEntryImpl.java new file mode 100644 index 0000000..855fd22 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/CodecConfigEntryImpl.java @@ -0,0 +1,42 @@ +package dev.isxander.yacl3.config.v3; + +import com.mojang.serialization.*; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Optional; + +@ApiStatus.Experimental +public class CodecConfigEntryImpl<T> extends AbstractConfigEntry<T> { + private final MapCodec<T> mapCodec; + + public CodecConfigEntryImpl(String fieldName, T defaultValue, Codec<T> codec) { + super(fieldName, defaultValue); + this.mapCodec = codec.fieldOf(this.fieldName()); + } + + @Override + public <R> RecordBuilder<R> encode(DynamicOps<R> ops, RecordBuilder<R> recordBuilder) { + return mapCodec.encode(get(), ops, recordBuilder); + } + + @Override + public <R> boolean decode(R encoded, DynamicOps<R> ops) { + DataResult<T> result = mapCodec.decoder().parse(ops, encoded); + + //? if >1.20.4 { + Optional<DataResult.Error<T>> error = result.error(); + //?} else { + /*Optional<DataResult.PartialResult<T>> error = result.error(); + *///?} + if (error.isPresent()) { + YACLConstants.LOGGER.error("Failed to decode entry {}: {}", this.fieldName(), error.get().message()); + return false; + } + + T value = result.result().orElseThrow(); + this.set(value); + + return true; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/ConfigEntry.java b/src/main/java/dev/isxander/yacl3/config/v3/ConfigEntry.java new file mode 100644 index 0000000..c22d796 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/ConfigEntry.java @@ -0,0 +1,38 @@ +package dev.isxander.yacl3.config.v3; + +import dev.isxander.yacl3.api.Binding; +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Consumer; +import java.util.function.UnaryOperator; + +@ApiStatus.Experimental +public interface ConfigEntry<T> extends ReadonlyConfigEntry<T> { + + void set(T value); + + T defaultValue(); + + @Override + ConfigEntry<T> modifyGet(UnaryOperator<T> modifier); + + @Override + default ConfigEntry<T> onGet(Consumer<T> consumer) { + return this.modifyGet(v -> { + consumer.accept(v); + return v; + }); + } + + ConfigEntry<T> modifySet(UnaryOperator<T> modifier); + default ConfigEntry<T> onSet(Consumer<T> consumer) { + return this.modifySet(v -> { + consumer.accept(v); + return v; + }); + } + + default Binding<T> asBinding() { + return Binding.generic(this.defaultValue(), this::get, this::set); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/EntryAddable.java b/src/main/java/dev/isxander/yacl3/config/v3/EntryAddable.java new file mode 100644 index 0000000..9ebd12d --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/EntryAddable.java @@ -0,0 +1,11 @@ +package dev.isxander.yacl3.config.v3; + +import com.mojang.serialization.Codec; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public interface EntryAddable { + <T> ConfigEntry<T> register(String fieldName, T defaultValue, Codec<T> codec); + + <T extends CodecConfig<T>> ReadonlyConfigEntry<T> register(String fieldName, T configInstance); +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/JsonFileCodecConfig.java b/src/main/java/dev/isxander/yacl3/config/v3/JsonFileCodecConfig.java new file mode 100644 index 0000000..49a0dac --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/JsonFileCodecConfig.java @@ -0,0 +1,90 @@ +package dev.isxander.yacl3.config.v3; + +import com.google.gson.*; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +@ApiStatus.Experimental +public abstract class JsonFileCodecConfig extends CodecConfig { + private final Path configPath; + private final Gson gson; + + public JsonFileCodecConfig(Path configPath) { + this.configPath = configPath; + this.gson = createGson(); + } + + public void saveToFile() { + DataResult<JsonElement> jsonTreeResult = this.encodeStart(JsonOps.INSTANCE); + if (jsonTreeResult.error().isPresent()) { + onSaveError( + SaveError.ENCODING, + new IllegalStateException("Failed to encode: " + jsonTreeResult.error().get().message()) + ); + return; + } + + JsonElement jsonTree = jsonTreeResult.result().orElseThrow(); + String json = gson.toJson(jsonTree); + + try { + Files.writeString(configPath, json, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + } catch (IOException e) { + onSaveError(SaveError.WRITING, e); + } + } + + public boolean loadFromFile() { + if (Files.notExists(configPath)) { + return false; + } + + String json; + try { + json = Files.readString(configPath); + } catch (IOException e) { + onLoadError(LoadError.READING, e); + return false; + } + + JsonElement jsonTree; + try { + jsonTree = JsonParser.parseString(json); + } catch (JsonParseException e) { + onLoadError(LoadError.JSON_PARSING, e); + return false; + } + + return this.decode(jsonTree, JsonOps.INSTANCE); + } + + protected Gson createGson() { + return new GsonBuilder().setPrettyPrinting().create(); + } + + protected void onSaveError(SaveError error, @Nullable Throwable e) { + throw new IllegalStateException("Error whilst " + error.name().toLowerCase(), e); + } + + protected void onLoadError(LoadError error, @Nullable Throwable e) { + throw new IllegalStateException("Error whilst " + error.name().toLowerCase(), e); + } + + protected enum SaveError { + WRITING, + ENCODING, + } + + protected enum LoadError { + READING, + JSON_PARSING, + DECODING, + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/KotlinExts.kt b/src/main/java/dev/isxander/yacl3/config/v3/KotlinExts.kt new file mode 100644 index 0000000..2c51656 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/KotlinExts.kt @@ -0,0 +1,53 @@ +package dev.isxander.yacl3.config.v3 + +import com.mojang.serialization.Codec +import dev.isxander.yacl3.dsl.OptionDsl +import dev.isxander.yacl3.dsl.OptionRegistrar +import org.jetbrains.annotations.ApiStatus +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +@ApiStatus.Experimental +fun <T> EntryAddable.register( + default: T, + codec: Codec<T> +) = PropertyDelegateProvider<EntryAddable, ReadOnlyProperty<EntryAddable, ConfigEntry<T>>> { thisRef, property -> + val entry = thisRef.register(property.name, default, codec) + ReadOnlyProperty { _, _ -> entry } +} + +fun <T : CodecConfig<T>> EntryAddable.register( + fieldName: String? = null, + configInstance: T +) = PropertyDelegateProvider<EntryAddable, T> { thisRef, property -> + thisRef.register(fieldName ?: property.name, configInstance) + configInstance +} + +operator fun <T : CodecConfig<T>> T.getValue(thisRef: CodecConfig<*>?, property: KProperty<*>): T { + return this +} + +@get:ApiStatus.Experimental +@set:ApiStatus.Experimental +var <T> ConfigEntry<T>.value: T + get() = this.get() + set(value) = this.set(value) + +@get:ApiStatus.Experimental +val <T> ConfigEntry<T>.default: T + get() = this.defaultValue() + +@get:ApiStatus.Experimental +val ConfigEntry<*>.fieldName: String + get() = this.fieldName() + +@ApiStatus.Experimental +fun <T : Any> OptionRegistrar.register( + configEntry: ConfigEntry<T>, + block: OptionDsl<T>.() -> Unit +) = register<T>(configEntry.fieldName) { + binding(configEntry.asBinding()) + block() +} diff --git a/src/main/java/dev/isxander/yacl3/config/v3/ReadonlyConfigEntry.java b/src/main/java/dev/isxander/yacl3/config/v3/ReadonlyConfigEntry.java new file mode 100644 index 0000000..c38853b --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v3/ReadonlyConfigEntry.java @@ -0,0 +1,26 @@ +package dev.isxander.yacl3.config.v3; + +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.RecordBuilder; +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Consumer; +import java.util.function.UnaryOperator; + +@ApiStatus.Experimental +public interface ReadonlyConfigEntry<T> { + String fieldName(); + + T get(); + + ReadonlyConfigEntry<T> modifyGet(UnaryOperator<T> modifier); + default ReadonlyConfigEntry<T> onGet(Consumer<T> consumer) { + return this.modifyGet(v -> { + consumer.accept(v); + return v; + }); + } + + <R> RecordBuilder<R> encode(DynamicOps<R> ops, RecordBuilder<R> recordBuilder); + <R> boolean decode(R encoded, DynamicOps<R> ops); +} |
