diff options
12 files changed, 110 insertions, 54 deletions
diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 7d5ece7..ba6dd04 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { modImplementation(libs.fabric.loader) implementation(libs.bundles.twelvemonkeys.imageio) + implementation(libs.bundles.quilt.parsers) } java { diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/GsonConfigSerializerBuilder.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/GsonConfigSerializerBuilder.java index 8d8d6c7..e871b7c 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/GsonConfigSerializerBuilder.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/GsonConfigSerializerBuilder.java @@ -64,5 +64,7 @@ public interface GsonConfigSerializerBuilder<T> { */ GsonConfigSerializerBuilder<T> appendGsonBuilder(UnaryOperator<GsonBuilder> gsonBuilder); + GsonConfigSerializerBuilder<T> setJson5(boolean json5); + ConfigSerializer<T> build(); } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java index 5c6894e..67b1d87 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/SimpleOptionFactory.java @@ -26,6 +26,7 @@ public abstract class SimpleOptionFactory<A extends Annotation, T> implements Op .controller(opt -> this.createController(annotation, field, storage, opt)) .available(this.available(annotation, field, storage)) .flags(this.flags(annotation, field, storage)) + .listener((opt, v) -> this.listener(annotation, field, storage, opt, v)) .build(); postInit(annotation, field, storage, option); @@ -70,6 +71,10 @@ public abstract class SimpleOptionFactory<A extends Annotation, T> implements Op return Set.of(); } + protected void listener(A annotation, ConfigField<T> field, OptionStorage storage, Option<T> option, T value) { + + } + protected void postInit(A annotation, ConfigField<T> field, OptionStorage storage, Option<T> option) { } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java index 65433a3..fa2b7b5 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/MasterTickBoxImpl.java @@ -15,13 +15,11 @@ public class MasterTickBoxImpl extends SimpleOptionFactory<MasterTickBox, Boolea } @Override - protected void postInit(MasterTickBox annotation, ConfigField<Boolean> field, OptionStorage storage, Option<Boolean> option) { - option.addListener((opt, val) -> { - for (String child : annotation.value()) { - storage.scheduleOptionOperation(child, childOpt -> { - childOpt.setAvailable(annotation.invert() != val); - }); - } - }); + protected void listener(MasterTickBox annotation, ConfigField<Boolean> field, OptionStorage storage, Option<Boolean> option, Boolean value) { + for (String child : annotation.value()) { + storage.scheduleOptionOperation(child, childOpt -> { + childOpt.setAvailable(annotation.invert() != value); + }); + } } } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java index 712a459..c59d20b 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/serializer/GsonConfigSerializer.java @@ -6,52 +6,71 @@ import dev.isxander.yacl3.impl.utils.YACLConstants; import dev.isxander.yacl3.platform.YACLPlatform; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; +import org.quiltmc.parsers.json.JsonReader; +import org.quiltmc.parsers.json.JsonWriter; +import org.quiltmc.parsers.json.gson.GsonReader; +import org.quiltmc.parsers.json.gson.GsonWriter; import java.awt.*; +import java.io.IOException; +import java.io.StringWriter; import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.function.UnaryOperator; +import java.util.stream.Collectors; public class GsonConfigSerializer<T> extends ConfigSerializer<T> { private final Gson gson; private final Path path; + private final boolean json5; - private GsonConfigSerializer(ConfigClassHandler<T> config, Path path, Gson gson) { + private GsonConfigSerializer(ConfigClassHandler<T> config, Path path, Gson gson, boolean json5) { super(config); this.gson = gson; this.path = path; + this.json5 = json5; } @Override public void serialize() { - JsonObject root = new JsonObject(); + YACLConstants.LOGGER.info("Serializing {} to '{}'", config.configClass(), path); - for (ConfigField<?> field : config.fields()) { - SerialField serial = field.serial().orElse(null); - if (serial == null) { - continue; - } + try (StringWriter stringWriter = new StringWriter()) { + JsonWriter jsonWriter = json5 ? JsonWriter.json5(stringWriter) : JsonWriter.json(stringWriter); + GsonWriter gsonWriter = new GsonWriter(jsonWriter); + jsonWriter.beginObject(); + for (ConfigField<?> field : config.fields()) { + SerialField serial = field.serial().orElse(null); + if (serial == null) { + continue; + } - if (YACLPlatform.isDevelopmentEnv() && serial.comment().isPresent()) { - YACLConstants.LOGGER.error("Config field '{}' has a comment, but comments are not supported by Gson. Please remove the comment or switch to a different serializer. This log will not be shown in production.", serial.serialName()); - } + if (!json5 && serial.comment().isPresent() && YACLPlatform.isDevelopmentEnv()) { + YACLConstants.LOGGER.warn("Found comment in config field '{}', but json5 is not enabled. Enable it with `.setJson5(true)` on the `GsonConfigSerializerBuilder`. Comments will not be serialized. This warning is only visible in development environments.", serial.serialName()); + } + jsonWriter.comment(serial.comment().orElse(null)); - try { - root.add(serial.serialName(), gson.toJsonTree(field.access().get())); - } catch (Exception e) { - YACLConstants.LOGGER.error("Failed to serialize config field '{}'.", serial.serialName(), e); + jsonWriter.name(serial.serialName()); + try { + gson.toJson(field.access().get(), field.access().type(), gsonWriter); + } catch (Exception e) { + YACLConstants.LOGGER.error("Failed to serialize config field '{}'.", serial.serialName(), e); + jsonWriter.nullValue(); + } } - } + jsonWriter.endObject(); + jsonWriter.flush(); - YACLConstants.LOGGER.info("Serializing {} to '{}'", config.configClass(), path); - try { - Files.writeString(path, gson.toJson(root), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); - } catch (Exception e) { + Files.createDirectories(path.getParent()); + Files.writeString(path, stringWriter.toString(), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + } catch (IOException e) { YACLConstants.LOGGER.error("Failed to serialize config class '{}'.", config.configClass().getSimpleName(), e); } } @@ -66,38 +85,37 @@ public class GsonConfigSerializer<T> extends ConfigSerializer<T> { YACLConstants.LOGGER.info("Deserializing {} from '{}'", config.configClass().getSimpleName(), path); - String json; - try { - json = Files.readString(path); - } catch (Exception e) { - throw new IllegalStateException("Failed to read '%s' for deserialization.".formatted(path), e); - } + try (JsonReader jsonReader = json5 ? JsonReader.json5(path) : JsonReader.json(path)) { + GsonReader gsonReader = new GsonReader(jsonReader); - Map<String, JsonElement> root = gson.fromJson(json, JsonObject.class).asMap(); - List<String> unconsumedKeys = new ArrayList<>(root.keySet()); + Map<String, ConfigField<?>> fieldMap = Arrays.stream(config.fields()) + .filter(field -> field.serial().isPresent()) + .collect(Collectors.toMap(f -> f.serial().orElseThrow().serialName(), Function.identity())); - for (ConfigField<?> field : config.fields()) { - SerialField serial = field.serial().orElse(null); - if (serial == null) { - continue; - } + jsonReader.beginObject(); + + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + ConfigField<?> field = fieldMap.get(name); + if (field == null) { + YACLConstants.LOGGER.warn("Found unknown config field '{}' in '{}'.", name, path); + jsonReader.skipValue(); + continue; + } - if (root.containsKey(serial.serialName())) { try { - field.access().set(gson.fromJson(root.get(serial.serialName()), field.access().type())); + field.access().set(gson.fromJson(gsonReader, field.access().type())); } catch (Exception e) { - YACLConstants.LOGGER.error("Failed to deserialize config field '{}'.", serial.serialName(), e); + YACLConstants.LOGGER.error("Failed to deserialize config field '{}'.", name, e); + jsonReader.skipValue(); } - } else { - YACLConstants.LOGGER.warn("Config field '{}' was not found in the config file. Skipping.", serial.serialName()); } - unconsumedKeys.remove(serial.serialName()); + jsonReader.endObject(); + } catch (IOException e) { + YACLConstants.LOGGER.error("Failed to deserialize config class '{}'.", config.configClass().getSimpleName(), e); } - if (!unconsumedKeys.isEmpty()) { - YACLConstants.LOGGER.warn("The following keys were not consumed by the config class: {}", String.join(", ", unconsumedKeys)); - } } public static class ColorTypeAdapter implements JsonSerializer<Color>, JsonDeserializer<Color> { @@ -115,6 +133,7 @@ public class GsonConfigSerializer<T> extends ConfigSerializer<T> { public static class Builder<T> implements GsonConfigSerializerBuilder<T> { private final ConfigClassHandler<T> config; private Path path; + private boolean json5; private UnaryOperator<GsonBuilder> gsonBuilder = builder -> builder .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .serializeNulls() @@ -152,8 +171,14 @@ public class GsonConfigSerializer<T> extends ConfigSerializer<T> { } @Override + public Builder<T> setJson5(boolean json5) { + this.json5 = json5; + return this; + } + + @Override public GsonConfigSerializer<T> build() { - return new GsonConfigSerializer<>(config, path, gsonBuilder.apply(new GsonBuilder()).create()); + return new GsonConfigSerializer<>(config, path, gsonBuilder.apply(new GsonBuilder()).create(), json5); } } } diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index bee77d4..de256bd 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -44,6 +44,10 @@ dependencies { implementation(it) include(it) } + libs.bundles.quilt.parsers.let { + implementation(it) + include(it) + } "common"(project(path = ":common", configuration = "namedElements")) { isTransitive = false } "shadowCommon"(project(path = ":common", configuration = "transformProductionFabric")) { isTransitive = false } diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index 16cd72d..73e2ccb 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -50,6 +50,11 @@ dependencies { include(it) forgeRuntimeLibrary(it) } + libs.bundles.quilt.parsers.let { + implementation(it) + include(it) + forgeRuntimeLibrary(it) + } "common"(project(path = ":common", configuration = "namedElements")) { isTransitive = false } "shadowCommon"(project(path = ":common", configuration = "transformProductionForge")) { isTransitive = false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fdbf9df..77242e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ fabric_loader = "0.14.21" # Common Dependencies mixin_extras = "0.2.0-beta.8" twelvemonkeys_imageio = "3.10.0-SNAPSHOT" +quilt_parsers = "0.2.1" # Fabric-like Dependencies fabric_api = "0.86.0+1.20.1" @@ -37,6 +38,8 @@ twelvemonkeys_imageio_metadata = { module = "com.twelvemonkeys.imageio:imageio-m twelvemonkeys_common_lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys_imageio" } twelvemonkeys_common_io = { module = "com.twelvemonkeys.common:common-io", version.ref = "twelvemonkeys_imageio" } twelvemonkeys_common_image = { module = "com.twelvemonkeys.common:common-image", version.ref = "twelvemonkeys_imageio" } +quilt_parsers_json = { module = "org.quiltmc.parsers:json", version.ref = "quilt_parsers" } +quilt_parsers_gson = { module = "org.quiltmc.parsers:gson", version.ref = "quilt_parsers" } # Fabric-like Dependencies fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric_api" } @@ -54,6 +57,10 @@ twelvemonkeys_imageio = [ "twelvemonkeys_common_io", "twelvemonkeys_common_image" ] +quilt_parsers = [ + "quilt_parsers_json", + "quilt_parsers_gson" +] [plugins] architectury_loom = { id = "dev.architectury.loom", version.ref = "architectury_loom" } diff --git a/test-common/src/main/java/dev/isxander/yacl3/test/ConfigV2Test.java b/test-common/src/main/java/dev/isxander/yacl3/test/ConfigV2Test.java index 6084e23..d287604 100644 --- a/test-common/src/main/java/dev/isxander/yacl3/test/ConfigV2Test.java +++ b/test-common/src/main/java/dev/isxander/yacl3/test/ConfigV2Test.java @@ -12,18 +12,21 @@ public class ConfigV2Test { public static ConfigClassHandler<ConfigV2Test> INSTANCE = ConfigClassHandler.createBuilder(ConfigV2Test.class) .id(new ResourceLocation("yacl3", "config")) .serializer(config -> GsonConfigSerializerBuilder.create(config) - .setPath(YACLPlatform.getConfigDir().resolve("yacl-test-v2.json")) + .setPath(YACLPlatform.getConfigDir().resolve("yacl-test-v2.json5")) + .setJson5(true) .build()) .autoGen(true) .build(); @AutoGen(category = "test", group = "master_test") @MasterTickBox({ "testTickBox", "testInt" }) - @SerialEntry public boolean masterOption = true; + @SerialEntry(comment = "This option does this and that which is cool because this...") + public boolean masterOption = true; @AutoGen(category = "test", group = "master_test") @TickBox - @SerialEntry public boolean testTickBox = true; + @SerialEntry(comment = "This is a cool comment omg this is amazing") + public boolean testTickBox = true; @AutoGen(category = "test", group = "master_test") @IntSlider(min = 0, max = 10, step = 2) diff --git a/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java b/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java index 2eadb2a..4953d0c 100644 --- a/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java +++ b/test-common/src/main/java/dev/isxander/yacl3/test/GuiTest.java @@ -44,7 +44,10 @@ public class GuiTest { .build()) .option(ButtonOption.createBuilder() .name(Component.literal("Auto-gen test")) - .action((screen, opt) -> Minecraft.getInstance().setScreen(ConfigV2Test.INSTANCE.generateGui().generateScreen(screen))) + .action((screen, opt) -> { + ConfigV2Test.INSTANCE.serializer().deserialize(); + Minecraft.getInstance().setScreen(ConfigV2Test.INSTANCE.generateGui().generateScreen(screen)); + }) .build()) .group(OptionGroup.createBuilder() .name(Component.literal("Wiki")) diff --git a/test-fabric/build.gradle.kts b/test-fabric/build.gradle.kts index 5786b4a..f2145d9 100644 --- a/test-fabric/build.gradle.kts +++ b/test-fabric/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(libs.twelvemonkeys.imageio.core) implementation(libs.twelvemonkeys.imageio.webp) + implementation(libs.bundles.quilt.parsers) "common"(project(path = ":test-common", configuration = "namedElements")) { isTransitive = false } implementation(project(path = ":fabric", configuration = "namedElements")) diff --git a/test-forge/build.gradle.kts b/test-forge/build.gradle.kts index 6fd938f..b43c7eb 100644 --- a/test-forge/build.gradle.kts +++ b/test-forge/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { forgeRuntimeLibrary(libs.twelvemonkeys.imageio.core) implementation(libs.twelvemonkeys.imageio.webp) forgeRuntimeLibrary(libs.twelvemonkeys.imageio.webp) + implementation(libs.bundles.quilt.parsers) + forgeRuntimeLibrary(libs.bundles.quilt.parsers) "common"(project(path = ":test-common", configuration = "namedElements")) { isTransitive = false } implementation(project(path = ":forge", configuration = "namedElements")) |