aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/anthonyhilyard/iceberg/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/anthonyhilyard/iceberg/util')
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java266
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/DynamicSubconfig.java138
2 files changed, 266 insertions, 138 deletions
diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java b/src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java
new file mode 100644
index 0000000..ad4f8d5
--- /dev/null
+++ b/src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java
@@ -0,0 +1,266 @@
+package com.anthonyhilyard.iceberg.util;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import com.anthonyhilyard.iceberg.Loader;
+import com.electronwill.nightconfig.core.CommentedConfig;
+import com.electronwill.nightconfig.core.Config;
+import com.electronwill.nightconfig.core.UnmodifiableConfig;
+import com.google.common.collect.Maps;
+import com.google.common.base.Objects;
+import com.google.common.collect.Iterators;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+import fuzs.configmenusforge.client.gui.data.EntryData;
+import fuzs.configmenusforge.client.gui.data.IEntryData;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraftforge.common.ForgeConfigSpec;
+import net.minecraftforge.fml.unsafe.UnsafeHacks;
+
+public class ConfigMenusForgeHelper
+{
+ private final static Map<Class<?>, List<MethodHandle>> configSpecMethodHandles = Maps.newHashMap();
+ private final static Map<Class<?>, Boolean> cachedConfigSpecClasses = Maps.newHashMap();
+ private final static MethodType getValuesMethodType = MethodType.methodType(UnmodifiableConfig.class);
+ private final static MethodType isLoadedMethodType = MethodType.methodType(boolean.class);
+ private final static MethodType saveMethodType = MethodType.methodType(void.class);
+
+ private static Object callMethod(UnmodifiableConfig spec, int methodIndex)
+ {
+ Class<?> specClass = spec.getClass();
+ if (!cachedConfigSpecClasses.containsKey(specClass))
+ {
+ cacheClass(specClass);
+ }
+
+ if (configSpecMethodHandles.containsKey(specClass))
+ {
+ try
+ {
+ return configSpecMethodHandles.get(specClass).get(methodIndex).invoke(spec);
+ }
+ catch (Throwable e)
+ {
+ Loader.LOGGER.warn(ExceptionUtils.getStackTrace(e));
+ }
+ }
+ return null;
+ }
+
+ public static UnmodifiableConfig getValues(UnmodifiableConfig spec)
+ {
+ return (UnmodifiableConfig) callMethod(spec, 0);
+ }
+
+ public static boolean isLoaded(UnmodifiableConfig spec)
+ {
+ return (Boolean) callMethod(spec, 1);
+ }
+
+ public static void save(UnmodifiableConfig spec)
+ {
+ callMethod(spec, 2);
+ }
+
+ public static Boolean cachedValidity(Class<?> specClass)
+ {
+ return cachedConfigSpecClasses.getOrDefault(specClass, null);
+ }
+
+ public static void cacheClass(Class<?> specClass)
+ {
+ MethodHandle getValuesMethod = null;
+ MethodHandle isLoadedMethod = null;
+ MethodHandle saveMethod = null;
+ try
+ {
+ getValuesMethod = MethodHandles.lookup().findVirtual(specClass, "getValues", getValuesMethodType);
+ isLoadedMethod = MethodHandles.lookup().findVirtual(specClass, "isLoaded", isLoadedMethodType);
+ saveMethod = MethodHandles.lookup().findVirtual(specClass, "save", saveMethodType);
+ }
+ catch (Throwable e)
+ {
+ Loader.LOGGER.warn(ExceptionUtils.getStackTrace(e));
+ }
+
+ // If we found valid getValues, isLoaded and save methods, add them to the cache.
+ if (getValuesMethod != null && isLoadedMethod != null && saveMethod != null)
+ {
+ cachedConfigSpecClasses.put(specClass, true);
+ configSpecMethodHandles.put(specClass, List.of(getValuesMethod, isLoadedMethod, saveMethod));
+ }
+ else
+ {
+ cachedConfigSpecClasses.put(specClass, false);
+ }
+ }
+
+ /**
+ * Changed spec from a ForgeConfigSpec to an UnmodifiableConfig.
+ */
+ public static void makeValueToDataMap(UnmodifiableConfig spec, UnmodifiableConfig values, CommentedConfig comments, Map<Object, IEntryData> allData, String basePath)
+ {
+ for (String path : values.valueMap().keySet())
+ {
+ String currentPath = basePath.isEmpty() ? path : basePath + "." + path;
+ Object value = values.valueMap().get(path);
+ if (value instanceof UnmodifiableConfig category)
+ {
+ final EntryData.CategoryEntryData data = new EntryData.CategoryEntryData(path, category, comments.getComment(path));
+ allData.put(category, data);
+ makeValueToDataMap(spec, category, (CommentedConfig) comments.valueMap().get(path), allData, currentPath);
+ }
+ else if (value instanceof ForgeConfigSpec.ConfigValue<?> configValue && configValue.get() instanceof UnmodifiableConfig category)
+ {
+ final EntryData.CategoryEntryData data = new DynamicCategoryEntryData(path, category, comments.getComment(path));
+ allData.put(category, data);
+ makeValueToDataMap(spec, category, (CommentedConfig) comments.valueMap().get(path), allData, currentPath);
+ }
+ else if (value instanceof ForgeConfigSpec.ConfigValue<?> configValue)
+ {
+ final EntryData.ConfigEntryData<?> data = new EntryData.ConfigEntryData<>(path, configValue, spec.getRaw(configValue.getPath()));
+ allData.put(configValue, data);
+ }
+ // Allow non-configvalue values if the parent is a dynamic subconfig.
+ else if (!(value instanceof ForgeConfigSpec.ConfigValue<?>) && allData.containsKey(values) && allData.get(values) instanceof DynamicCategoryEntryData)
+ {
+ final EntryData.ConfigEntryData<?> data = new DynamicConfigEntryData<>(List.of(currentPath.split("\\.")), value, spec.getRaw(currentPath), spec);
+ allData.put(value, data);
+ }
+ }
+ }
+
+ public static class DynamicCategoryEntryData extends EntryData.CategoryEntryData
+ {
+ public DynamicCategoryEntryData(String path, UnmodifiableConfig config, String comment) {
+ super(path, config, comment);
+ }
+ }
+
+ public static class DynamicConfigEntryData<T> extends EntryData.ConfigEntryData<T>
+ {
+ private final ForgeConfigSpec.ValueSpec valueSpec;
+ private T currentValue;
+ private T configValue;
+ private final List<String> fullPath;
+ private final UnmodifiableConfig spec;
+
+ private final static ForgeConfigSpec.ConfigValue<?> dummyConfigValue;
+
+ private Component title;
+
+ static
+ {
+ dummyConfigValue = UnsafeHacks.newInstance(ForgeConfigSpec.ConfigValue.class);
+ try
+ {
+ Field specField = ForgeConfigSpec.ConfigValue.class.getDeclaredField("spec");
+ UnsafeHacks.setField(specField, dummyConfigValue, UnsafeHacks.newInstance(ForgeConfigSpec.class));
+ Field defaultSupplierField = ForgeConfigSpec.ConfigValue.class.getDeclaredField("defaultSupplier");
+ UnsafeHacks.setField(defaultSupplierField, dummyConfigValue, (Supplier<?>)(() -> null));
+ }
+ catch (Exception e) { }
+ }
+
+ @SuppressWarnings("unchecked")
+ public DynamicConfigEntryData(List<String> fullPath, T configValue, ForgeConfigSpec.ValueSpec valueSpec, UnmodifiableConfig spec)
+ {
+ super(fullPath.get(fullPath.size() - 1), (ForgeConfigSpec.ConfigValue<T>) dummyConfigValue, valueSpec);
+ this.configValue = configValue;
+ this.currentValue = configValue;
+ this.valueSpec = valueSpec;
+ this.fullPath = fullPath;
+ this.spec = spec;
+
+ // We will override the normal title functionality since we want it to be unformatted.
+ this.title = new TextComponent(getPath());
+ }
+
+ @Override
+ public Component getTitle()
+ {
+ return this.title;
+ }
+
+ @Override
+ public boolean mayResetValue()
+ {
+ return !listSafeEquals(currentValue, getDefaultValue());
+ }
+
+ @Override
+ public boolean mayDiscardChanges()
+ {
+ return listSafeEquals(configValue, currentValue);
+ }
+
+ private static <T> boolean listSafeEquals(T o1, T o2)
+ {
+ // Attempts to solve an issue where types of lists won't match when one is read from file
+ // (due to enum being converted to string, long to int)
+ if (o1 instanceof List<?> list1 && o2 instanceof List<?> list2)
+ {
+ final Stream<String> stream1 = list1.stream().map(o -> o instanceof Enum<?> e ? e.name() : o.toString());
+ final Stream<String> stream2 = list2.stream().map(o -> o instanceof Enum<?> e ? e.name() : o.toString());
+ return Iterators.elementsEqual(stream1.iterator(), stream2.iterator());
+ }
+ return Objects.equal(o1, o2);
+ }
+
+ @Override
+ public void resetCurrentValue()
+ {
+ currentValue = getDefaultValue();
+ }
+
+ @Override
+ public void discardCurrentValue()
+ {
+ currentValue = configValue;
+ }
+
+ @Override
+ public void saveConfigValue()
+ {
+ try
+ {
+ Field childConfigField = spec.getClass().getDeclaredField("childConfig");
+ Config childConfig = UnsafeHacks.getField(childConfigField, spec);
+ childConfig.set(fullPath, currentValue);
+ }
+ catch (Exception e) { }
+ configValue = currentValue;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getDefaultValue()
+ {
+ return (T) valueSpec.getDefault();
+ }
+
+ public T getCurrentValue()
+ {
+ return currentValue;
+ }
+
+ public void setCurrentValue(T newValue)
+ {
+ currentValue = newValue;
+ }
+
+ @Override
+ public List<String> getFullPath()
+ {
+ return fullPath;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/DynamicSubconfig.java b/src/main/java/com/anthonyhilyard/iceberg/util/DynamicSubconfig.java
deleted file mode 100644
index 3ed17f3..0000000
--- a/src/main/java/com/anthonyhilyard/iceberg/util/DynamicSubconfig.java
+++ /dev/null
@@ -1,138 +0,0 @@
-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<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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