From 7a02ccc3a81a199646c957a9fbfd88835893c3b3 Mon Sep 17 00:00:00 2001 From: cpw Date: Mon, 28 Jan 2019 22:42:37 -0500 Subject: [PATCH] ConfigEvents - the beginning. Signed-off-by: cpw --- .../minecraftforge/common/ForgeConfig.java | 20 ++- .../common/ForgeConfigSpec.java | 11 -- .../net/minecraftforge/common/ForgeMod.java | 5 +- .../net/minecraftforge/fml/ModContainer.java | 19 ++- .../net/minecraftforge/fml/ModLoader.java | 4 + .../fml/config/ConfigFileTypeHandler.java | 55 ++++++++ .../fml/config/ConfigTracker.java | 52 +++++++ .../minecraftforge/fml/config/ModConfig.java | 132 ++++++++++++++++++ .../fml/javafmlmod/FMLModLoadingContext.java | 10 ++ .../fml/server/ServerLifecycleHooks.java | 9 ++ 10 files changed, 288 insertions(+), 29 deletions(-) create mode 100644 src/main/java/net/minecraftforge/fml/config/ConfigFileTypeHandler.java create mode 100644 src/main/java/net/minecraftforge/fml/config/ConfigTracker.java create mode 100644 src/main/java/net/minecraftforge/fml/config/ModConfig.java diff --git a/src/main/java/net/minecraftforge/common/ForgeConfig.java b/src/main/java/net/minecraftforge/common/ForgeConfig.java index 876d00bf7..a8dc7a3f6 100644 --- a/src/main/java/net/minecraftforge/common/ForgeConfig.java +++ b/src/main/java/net/minecraftforge/common/ForgeConfig.java @@ -28,6 +28,8 @@ import java.util.List; import javax.annotation.Nullable; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.config.ModConfig; import org.apache.logging.log4j.LogManager; import com.electronwill.nightconfig.core.CommentedConfig; @@ -130,7 +132,6 @@ public class ForgeConfig } public static class Client { - public final BooleanValue zoomInMissingModelTextInGui; public final BooleanValue forgeCloudsEnabled; @@ -183,7 +184,7 @@ public class ForgeConfig } } - private static final ForgeConfigSpec spec = BUILDER.build(); + static final ForgeConfigSpec spec = BUILDER.build(); private static final ForgeConfigSpec.Builder CHUNK_BUILDER = new ForgeConfigSpec.Builder(); @@ -279,18 +280,15 @@ public class ForgeConfig public static final ForgeConfigSpec chunk_spec = CHUNK_BUILDER.build(); - private static void loadFrom(final Path configRoot) { - Path configFile = configRoot.resolve("forge.toml"); - spec.setConfigFile(configFile); - LogManager.getLogger().debug(FORGEMOD, "Loaded Forge config from {}", configFile); - configFile = configRoot.resolve("forge_chunks.toml"); - chunk_spec.setConfigFile(configFile); - LogManager.getLogger().debug(FORGEMOD, "Loaded Forge Chunk config from {}", configFile); + @SubscribeEvent + public static void onLoad(final ModConfig.Loading configEvent) { + LogManager.getLogger().debug(FORGEMOD, "Loaded forge config file {}", configEvent.getConfig().getFileName()); } - public static void load() { - loadFrom(FMLPaths.CONFIGDIR.get()); + @SubscribeEvent + public static void onFileChange(final ModConfig.ConfigReloading configEvent) { + LogManager.getLogger().fatal(CORE, "Forge config just got changed on the file system!"); } //General diff --git a/src/main/java/net/minecraftforge/common/ForgeConfigSpec.java b/src/main/java/net/minecraftforge/common/ForgeConfigSpec.java index b9b9e7554..0a9325dc8 100644 --- a/src/main/java/net/minecraftforge/common/ForgeConfigSpec.java +++ b/src/main/java/net/minecraftforge/common/ForgeConfigSpec.java @@ -73,19 +73,8 @@ public class ForgeConfigSpec extends UnmodifiableConfigWrapper this.levelComments = levelComments; } - public void setConfigFile(Path path) { - setConfig(CommentedFileConfig.builder(path).sync() - .autosave() - //.autoreload() - .writingMode(WritingMode.REPLACE) - .build()); - } - public void setConfig(CommentedConfig config) { this.childConfig = config; - if (config instanceof FileConfig) { - ((FileConfig) config).load(); - } if (!isCorrect(config)) { String configName = config instanceof FileConfig ? ((FileConfig) config).getNioPath().toString() : config.toString(); LogManager.getLogger().warn(CORE, "Configuration file {} is not correct. Correcting", configName); diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index 4231e480b..4bc5a95f0 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -22,6 +22,7 @@ package net.minecraftforge.common; import net.minecraftforge.fml.FMLWorldPersistenceHook; import net.minecraftforge.fml.VersionChecker; import net.minecraftforge.fml.WorldPersistenceHooks; +import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent; import net.minecraftforge.fml.event.lifecycle.FMLModIdMappingEvent; @@ -84,7 +85,9 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook MinecraftForge.EVENT_BUS.addListener(this::serverStarting); MinecraftForge.EVENT_BUS.addListener(this::playerLogin); MinecraftForge.EVENT_BUS.addListener(this::serverStopping); - ForgeConfig.load(); + FMLModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ForgeConfig.spec); + FMLModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ForgeConfig.chunk_spec); + FMLModLoadingContext.get().getModEventBus().register(ForgeConfig.class); } /* diff --git a/src/main/java/net/minecraftforge/fml/ModContainer.java b/src/main/java/net/minecraftforge/fml/ModContainer.java index 128107c86..01e646716 100644 --- a/src/main/java/net/minecraftforge/fml/ModContainer.java +++ b/src/main/java/net/minecraftforge/fml/ModContainer.java @@ -19,14 +19,10 @@ package net.minecraftforge.fml; +import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.forgespi.language.IModInfo; -import java.util.Collections; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; @@ -51,6 +47,9 @@ public abstract class ModContainer protected ModLoadingStage modLoadingStage; protected final Map> triggerMap; protected final Map> extensionPoints = new IdentityHashMap<>(); + protected final EnumMap configs = new EnumMap<>(ModConfig.Type.class); + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + protected Optional> configHandler = Optional.empty(); public ModContainer(IModInfo info) { @@ -125,6 +124,14 @@ public abstract class ModContainer extensionPoints.put(point, extension); } + public void addConfig(final ModConfig modConfig) { + configs.put(modConfig.getType(), modConfig); + } + + public void dispatchConfigEvent(ModConfig.ModConfigEvent event) { + configHandler.ifPresent(configHandler->configHandler.accept(event)); + } + /** * Does this mod match the supplied mod? * diff --git a/src/main/java/net/minecraftforge/fml/ModLoader.java b/src/main/java/net/minecraftforge/fml/ModLoader.java index 8818b1a98..1c329087b 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -24,6 +24,9 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ModelRegistryEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.fml.config.ConfigTracker; +import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.loading.FMLPaths; import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.LoadingModList; @@ -127,6 +130,7 @@ public class ModLoader ObjectHolderRegistry.findObjectHolders(); CapabilityManager.INSTANCE.injectCapabilities(modList.getAllScanData()); GameData.fireRegistryEvents(rl->true, LifecycleEventProvider.LOAD_REGISTRIES, this::dispatchAndHandleError); + DistExecutor.runWhenOn(Dist.CLIENT, ()->()-> ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get())); dispatchAndHandleError(LifecycleEventProvider.SETUP); DistExecutor.runWhenOn(Dist.CLIENT, ModLoader::fireClientEvents); dispatchAndHandleError(LifecycleEventProvider.SIDED_SETUP); diff --git a/src/main/java/net/minecraftforge/fml/config/ConfigFileTypeHandler.java b/src/main/java/net/minecraftforge/fml/config/ConfigFileTypeHandler.java new file mode 100644 index 000000000..39931e96b --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/config/ConfigFileTypeHandler.java @@ -0,0 +1,55 @@ +package net.minecraftforge.fml.config; + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import com.electronwill.nightconfig.core.file.FileWatcher; +import com.electronwill.nightconfig.core.io.WritingMode; +import net.minecraftforge.fml.loading.FMLPaths; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Function; + +import static net.minecraftforge.fml.config.ConfigTracker.CONFIG; + +public class ConfigFileTypeHandler { + private static final Logger LOGGER = LogManager.getLogger(); + static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler(); + + public Function reader(Path configBasePath) { + return (c) -> { + final Path configPath = configBasePath.resolve(c.getFileName()); + final CommentedFileConfig configData = CommentedFileConfig.builder(configPath).sync(). + autosave(). + writingMode(WritingMode.REPLACE). + build(); + LOGGER.debug(CONFIG, "Built TOML config for {}", configPath.toString()); + try { + FileWatcher.defaultInstance().addWatch(configPath, new ConfigWatcher(c, configData)); + LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath.toString()); + } catch (IOException e) { + throw new RuntimeException("Couldn't watch config file", e); + } + configData.load(); + LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath.toString()); + return configData; + }; + } + + private static class ConfigWatcher implements Runnable { + private final ModConfig modConfig; + private final CommentedFileConfig commentedFileConfig; + + ConfigWatcher(final ModConfig modConfig, final CommentedFileConfig commentedFileConfig) { + this.modConfig = modConfig; + this.commentedFileConfig = commentedFileConfig; + } + + @Override + public void run() { + LOGGER.debug(CONFIG, "Config file {} changed, sending notifies", this.modConfig.getFileName()); + this.modConfig.fireEvent(new ModConfig.ConfigReloading(this.modConfig)); + } + } +} diff --git a/src/main/java/net/minecraftforge/fml/config/ConfigTracker.java b/src/main/java/net/minecraftforge/fml/config/ConfigTracker.java new file mode 100644 index 000000000..912e22e79 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/config/ConfigTracker.java @@ -0,0 +1,52 @@ +package net.minecraftforge.fml.config; + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; + +import java.nio.file.Path; +import java.util.EnumMap; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; + +public class ConfigTracker { + private static final Logger LOGGER = LogManager.getLogger(); + static final Marker CONFIG = MarkerManager.getMarker("CONFIG"); + public static final ConfigTracker INSTANCE = new ConfigTracker(); + private final ConcurrentHashMap fileMap; + private final EnumMap> configSets; + + private ConfigTracker() { + this.fileMap = new ConcurrentHashMap<>(); + this.configSets = new EnumMap<>(ModConfig.Type.class); + this.configSets.put(ModConfig.Type.CLIENT, new ConcurrentSkipListSet<>()); +// this.configSets.put(ModConfig.Type.PLAYER, new ConcurrentSkipListSet<>()); + this.configSets.put(ModConfig.Type.SERVER, new ConcurrentSkipListSet<>()); + } + + void trackConfig(final ModConfig config) { + if (this.fileMap.containsKey(config.getFileName())) { + LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), this.fileMap.get(config.getFileName()).getModId(), config.getModId()); + throw new RuntimeException("Config conflict detected!"); + } + this.fileMap.put(config.getFileName(), config); + this.configSets.get(config.getType()).add(config); + LOGGER.debug(CONFIG, "Config file {} for {} tracking", config.getFileName(), config.getModId()); + } + + public void loadConfigs(ModConfig.Type type, Path configBasePath) { + LOGGER.debug(CONFIG, "Loading configs type {}", type); + this.configSets.get(type).forEach(config -> openConfig(config, configBasePath)); + } + + private void openConfig(final ModConfig config, final Path configBasePath) { + LOGGER.debug(CONFIG, "Loading config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId()); + final CommentedFileConfig configData = config.getHandler().reader(configBasePath).apply(config); + config.setConfigData(configData); + config.fireEvent(new ModConfig.Loading(config)); + config.getConfigData().save(); + } +} diff --git a/src/main/java/net/minecraftforge/fml/config/ModConfig.java b/src/main/java/net/minecraftforge/fml/config/ModConfig.java new file mode 100644 index 000000000..617483a59 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/config/ModConfig.java @@ -0,0 +1,132 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.fml.config; + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.loading.StringUtils; + +public class ModConfig +{ + private final Type type; + private final ForgeConfigSpec spec; + private final String fileName; + private final ModContainer container; + private final ConfigFileTypeHandler configHandler; + private CommentedFileConfig configData; + + public ModConfig(final Type type, final ForgeConfigSpec spec, final ModContainer container, final String fileName) { + this.type = type; + this.spec = spec; + this.fileName = fileName; + this.container = container; + this.configHandler = ConfigFileTypeHandler.TOML; + ConfigTracker.INSTANCE.trackConfig(this); + } + + public ModConfig(final Type type, final ForgeConfigSpec spec, final ModContainer activeContainer) { + this(type, spec, activeContainer, defaultConfigName(type, activeContainer.getModId())); + } + + private static String defaultConfigName(Type type, String modId) { + // config file name would be "forge-client.toml" and "forge-server.toml" + return String.format("%s-%s.toml", modId, type.extension()); + } + public Type getType() { + return type; + } + + public String getFileName() { + return fileName; + } + + public ConfigFileTypeHandler getHandler() { + return configHandler; + } + + public ForgeConfigSpec getSpec() { + return spec; + } + + public String getModId() { + return container.getModId(); + } + + public CommentedFileConfig getConfigData() { + return this.configData; + } + + void setConfigData(final CommentedFileConfig configData) { + this.configData = configData; + this.spec.setConfig(this.configData); + } + + void fireEvent(final ModConfigEvent configEvent) { + this.container.dispatchConfigEvent(configEvent); + } + + public enum Type { + /** + * Client type config is exclusively for configuration affecting the client state. + * Graphical options, for example. + */ + CLIENT, +// /** +// * Player type config is configuration that is associated with a player. +// * Preferences around machine states, for example. +// */ +// PLAYER, + /** + * Server type config is configuration that is associated with a server instance. + * It will be synced from a server to the client on connection. + */ + SERVER; + + public String extension() { + return StringUtils.toLowerCase(name()); + } + } + + public static class ModConfigEvent extends Event { + private final ModConfig config; + + ModConfigEvent(final ModConfig config) { + this.config = config; + } + + public ModConfig getConfig() { + return config; + } + } + + public static class Loading extends ModConfigEvent { + Loading(final ModConfig config) { + super(config); + } + } + + public static class ConfigReloading extends ModConfigEvent { + ConfigReloading(final ModConfig config) { + super(config); + } + } +} diff --git a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModLoadingContext.java b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModLoadingContext.java index a43c0df3f..6bf99bc16 100644 --- a/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModLoadingContext.java +++ b/src/main/java/net/minecraftforge/fml/javafmlmod/FMLModLoadingContext.java @@ -19,8 +19,10 @@ package net.minecraftforge.fml.javafmlmod; +import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.ExtensionPoint; +import net.minecraftforge.fml.config.ModConfig; import java.util.function.Supplier; @@ -36,6 +38,14 @@ public class FMLModLoadingContext getActiveContainer().registerExtensionPoint(point, extension); } + public void registerConfig(ModConfig.Type type, ForgeConfigSpec spec) { + activeContainer.addConfig(new ModConfig(type, spec, activeContainer)); + } + + public void registerConfig(ModConfig.Type type, ForgeConfigSpec spec, String fileName) { + activeContainer.addConfig(new ModConfig(type, spec, activeContainer, fileName)); + } + public IEventBus getModEventBus() { return getActiveContainer().getEventBus(); diff --git a/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java b/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java index c95826527..b0155a9d6 100644 --- a/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java +++ b/src/main/java/net/minecraftforge/fml/server/ServerLifecycleHooks.java @@ -29,11 +29,15 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.LogicalSidedProvider; +import net.minecraftforge.fml.config.ConfigTracker; +import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent; import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStartedEvent; import net.minecraftforge.fml.event.server.FMLServerStoppedEvent; import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; +import net.minecraftforge.fml.loading.FMLPaths; +import net.minecraftforge.fml.loading.FileUtils; import net.minecraftforge.fml.network.NetworkHooks; import net.minecraftforge.fml.packs.ResourcePackLoader; import org.apache.logging.log4j.LogManager; @@ -41,6 +45,8 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -56,6 +62,9 @@ public class ServerLifecycleHooks { currentServer = server; LogicalSidedProvider.setServer(()->server); + final Path serverConfig = server.getActiveAnvilConverter().getFile(server.getFolderName(), "serverconfig").toPath(); + FileUtils.getOrCreateDirectory(serverConfig, "serverconfig"); + ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.SERVER, serverConfig); ResourcePackLoader.loadResourcePacks(currentServer.getResourcePacks()); return !MinecraftForge.EVENT_BUS.post(new FMLServerAboutToStartEvent(server)); }