From 7a3df325b5bb294c2836082f58b719c70792ed61 Mon Sep 17 00:00:00 2001 From: Anthony Hilyard Date: Thu, 13 Jan 2022 15:13:04 -0800 Subject: Added subconfig capability to Forge's config system. --- .../iceberg/mixin/ForgeConfigSpecMixin.java | 160 +++++++++++++++++++++ .../iceberg/util/DynamicSubconfig.java | 138 ++++++++++++++++++ src/main/resources/iceberg.mixins.json | 5 +- 3 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/anthonyhilyard/iceberg/mixin/ForgeConfigSpecMixin.java create mode 100644 src/main/java/com/anthonyhilyard/iceberg/util/DynamicSubconfig.java (limited to 'src') diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/ForgeConfigSpecMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/ForgeConfigSpecMixin.java new file mode 100644 index 0000000..288af26 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/ForgeConfigSpecMixin.java @@ -0,0 +1,160 @@ +package com.anthonyhilyard.iceberg.mixin; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import com.anthonyhilyard.iceberg.util.DynamicSubconfig; +import com.electronwill.nightconfig.core.CommentedConfig; +import com.electronwill.nightconfig.core.UnmodifiableConfig; +import com.electronwill.nightconfig.core.ConfigSpec.CorrectionAction; +import com.electronwill.nightconfig.core.ConfigSpec.CorrectionListener; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.common.ForgeConfigSpec.ValueSpec; + + +@Mixin(value = ForgeConfigSpec.class, remap = false) +public class ForgeConfigSpecMixin +{ + @Shadow(remap = false) + private Map, String> levelComments; + + @Shadow(remap = false) + private boolean stringsMatchIgnoringNewlines(@Nullable Object obj1, @Nullable Object obj2) { return false; } + + /** + * @author iceberg + * @reason Overwrite the correct method to fix subconfigs not being handled properly. + */ + @Overwrite(remap = false) + private int correct(UnmodifiableConfig spec, CommentedConfig config, LinkedList parentPath, List parentPathUnmodifiable, CorrectionListener listener, CorrectionListener commentListener, boolean dryRun) + { + int count = 0; + + Map specMap = spec.valueMap(); + Map configMap = config.valueMap(); + + for (Map.Entry specEntry : specMap.entrySet()) + { + final String key = specEntry.getKey(); + Object specValue = specEntry.getValue(); + final Object configValue = configMap.get(key); + final CorrectionAction action = configValue == null ? CorrectionAction.ADD : CorrectionAction.REPLACE; + + parentPath.addLast(key); + + String subConfigComment = null; + + // If this value is a config, use that as the spec value to support subconfigs. + if (specValue instanceof ValueSpec valueSpec && valueSpec.getDefault() instanceof UnmodifiableConfig) + { + subConfigComment = valueSpec.getComment(); + specValue = valueSpec.getDefault(); + } + + if (specValue instanceof UnmodifiableConfig) + { + if (configValue instanceof CommentedConfig) + { + count += correct((UnmodifiableConfig)specValue, (CommentedConfig)configValue, parentPath, parentPathUnmodifiable, listener, commentListener, dryRun); + if (count > 0 && dryRun) + { + return count; + } + } + else if (dryRun) + { + return 1; + } + else + { + CommentedConfig newValue = config.createSubConfig(); + configMap.put(key, newValue); + listener.onCorrect(action, parentPathUnmodifiable, configValue, newValue); + count++; + count += correct((UnmodifiableConfig)specValue, newValue, parentPath, parentPathUnmodifiable, listener, commentListener, dryRun); + } + + String newComment = subConfigComment == null ? levelComments.get(parentPath) : subConfigComment; + String oldComment = config.getComment(key); + if (!stringsMatchIgnoringNewlines(oldComment, newComment)) + { + if (commentListener != null) + { + commentListener.onCorrect(action, parentPathUnmodifiable, oldComment, newComment); + } + + if (dryRun) + { + return 1; + } + + config.setComment(key, newComment); + } + } + else + { + ValueSpec valueSpec = (ValueSpec)specValue; + if (!valueSpec.test(configValue)) + { + if (dryRun) + { + return 1; + } + + Object newValue = valueSpec.correct(configValue); + configMap.put(key, newValue); + listener.onCorrect(action, parentPathUnmodifiable, configValue, newValue); + count++; + } + String oldComment = config.getComment(key); + if (!stringsMatchIgnoringNewlines(oldComment, valueSpec.getComment())) + { + if (commentListener != null) + { + commentListener.onCorrect(action, parentPathUnmodifiable, oldComment, valueSpec.getComment()); + } + + if (dryRun) + { + return 1; + } + + config.setComment(key, valueSpec.getComment()); + } + } + + parentPath.removeLast(); + } + + // Second step: removes the unspecified values + for (Iterator> ittr = configMap.entrySet().iterator(); ittr.hasNext();) + { + Map.Entry entry = ittr.next(); + + // If the spec is a dynamic subconfig, don't bother checking the spec since that's the point. + if (!(spec instanceof DynamicSubconfig) && !specMap.containsKey(entry.getKey())) + { + if (dryRun) + { + return 1; + } + + ittr.remove(); + parentPath.addLast(entry.getKey()); + listener.onCorrect(CorrectionAction.REMOVE, parentPathUnmodifiable, entry.getValue(), null); + parentPath.removeLast(); + count++; + } + } + return count; + } +} diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/DynamicSubconfig.java b/src/main/java/com/anthonyhilyard/iceberg/util/DynamicSubconfig.java new file mode 100644 index 0000000..3ed17f3 --- /dev/null +++ b/src/main/java/com/anthonyhilyard/iceberg/util/DynamicSubconfig.java @@ -0,0 +1,138 @@ +package com.anthonyhilyard.iceberg.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import com.electronwill.nightconfig.core.AbstractCommentedConfig; +import com.electronwill.nightconfig.core.ConfigFormat; +import com.electronwill.nightconfig.core.UnmodifiableCommentedConfig; +import com.electronwill.nightconfig.core.UnmodifiableConfig; + +/** + * An exact copy of SimpleCommentedConfig, but this class is specifically meant for subconfigs. + * That being said--the class of a config is checked during config validation, and subconfigs are allowed + * extra leniency in config keys. + */ +final public class DynamicSubconfig extends AbstractCommentedConfig +{ + private final ConfigFormat configFormat; + + /** + * Creates a Subconfig with the specified format. + * + * @param configFormat the config's format + */ + DynamicSubconfig(ConfigFormat configFormat, boolean concurrent) + { + super(concurrent ? new ConcurrentHashMap<>() : new HashMap<>()); + this.configFormat = configFormat; + } + + /** + * Creates a Subconfig with the specified data and format. The map is used as it is and + * isn't copied. + */ + DynamicSubconfig(Map valueMap, ConfigFormat configFormat) + { + super(valueMap); + this.configFormat = configFormat; + } + + /** + * Creates a Subconfig with the specified backing map supplier and format. + * + * @param mapCreator the supplier for backing maps + * @param configFormat the config's format + */ + DynamicSubconfig(Supplier> mapCreator, ConfigFormat configFormat) + { + super(mapCreator); + this.configFormat = configFormat; + } + + /** + * Creates a Subconfig by copying a config and with the specified format. + * + * @param toCopy the config to copy + * @param configFormat the config's format + */ + DynamicSubconfig(UnmodifiableConfig toCopy, ConfigFormat configFormat, + boolean concurrent) + { + super(toCopy, concurrent); + this.configFormat = configFormat; + } + + /** + * Creates a Subconfig by copying a config, with the specified backing map creator and format. + * + * @param toCopy the config to copy + * @param mapCreator the supplier for backing maps + * @param configFormat the config's format + */ + public DynamicSubconfig(UnmodifiableConfig toCopy, Supplier> mapCreator, + ConfigFormat configFormat) + { + super(toCopy, mapCreator); + this.configFormat = configFormat; + } + + /** + * Creates a Subconfig by copying a config and with the specified format. + * + * @param toCopy the config to copy + * @param configFormat the config's format + */ + DynamicSubconfig(UnmodifiableCommentedConfig toCopy, ConfigFormat configFormat, + boolean concurrent) + { + super(toCopy, concurrent); + this.configFormat = configFormat; + } + + /** + * Creates a Subconfig by copying a config, with the specified backing map creator and format. + * + * @param toCopy the config to copy + * @param mapCreator the supplier for backing maps + * @param configFormat the config's format + */ + public DynamicSubconfig(UnmodifiableCommentedConfig toCopy, Supplier> mapCreator, + ConfigFormat configFormat) + { + super(toCopy, mapCreator); + this.configFormat = configFormat; + } + + @Override + public ConfigFormat configFormat() + { + return configFormat; + } + + @Override + public DynamicSubconfig createSubConfig() + { + return new DynamicSubconfig(mapCreator, configFormat); + } + + @Override + public AbstractCommentedConfig clone() + { + return new DynamicSubconfig(this, mapCreator, configFormat); + } + + /** + * Creates a new Subconfig with the content of the given config. The returned config will have + * the same format as the copied config. + * + * @param config the config to copy + * @return a copy of the config + */ + static DynamicSubconfig copy(UnmodifiableConfig config) + { + return new DynamicSubconfig(config, config.configFormat(), false); + } +} \ No newline at end of file diff --git a/src/main/resources/iceberg.mixins.json b/src/main/resources/iceberg.mixins.json index 6ee1aca..f167048 100644 --- a/src/main/resources/iceberg.mixins.json +++ b/src/main/resources/iceberg.mixins.json @@ -1,11 +1,12 @@ { "required": true, "package": "com.anthonyhilyard.iceberg.mixin", - "compatibilityLevel": "JAVA_16", + "compatibilityLevel": "JAVA_17", "refmap": "iceberg.refmap.json", "mixins": [ "EntityMixin", - "PlayerAdvancementsMixin" + "PlayerAdvancementsMixin", + "ForgeConfigSpecMixin" ], "client": [ "ScreenMixin", -- cgit