diff --git a/gradle.properties b/gradle.properties index 8022687928..2772cabaae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,22 +15,23 @@ neoform_version=20240808.144430 neoforge_snapshot_next_stable=22.0 mergetool_version=2.0.0 +# TODO: does this get bumped because of FML? accesstransformers_version=10.0.1 coremods_version=6.0.4 eventbus_version=8.0.1 modlauncher_version=11.0.4 securejarhandler_version=3.0.8 bootstraplauncher_version=2.0.2 -asm_version=9.5 +asm_version=9.7 installer_version=2.1.+ mixin_version=0.14.0+mixin.0.8.6 terminalconsoleappender_version=1.3.0 nightconfig_version=3.8.0 -jetbrains_annotations_version=24.0.1 +jetbrains_annotations_version=24.1.0 slf4j_api_version=2.0.7 apache_maven_artifact_version=3.8.5 jarjar_version=0.4.1 -fancy_mod_loader_version=4.0.24 +fancy_mod_loader_version=4.0.26-sync-config-changes mojang_logging_version=1.1.1 log4j_version=2.22.1 guava_version=31.1.2-jre diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java index 2e2e14ab5f..1cdb73eca9 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeEventHandler.java @@ -35,6 +35,7 @@ import net.neoforged.neoforge.event.level.ChunkEvent; import net.neoforged.neoforge.event.level.LevelEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; +import net.neoforged.neoforge.network.ConfigSync; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.payload.RegistryDataMapSyncPayload; import net.neoforged.neoforge.registries.DataMapLoader; @@ -77,6 +78,7 @@ public void preServerTick(ServerTickEvent.Pre event) { @SubscribeEvent public void postServerTick(ServerTickEvent.Post event) { WorldWorkerManager.tick(false); + ConfigSync.syncPendingConfigs(event.getServer()); } @SubscribeEvent diff --git a/src/main/java/net/neoforged/neoforge/internal/NeoForgeBindings.java b/src/main/java/net/neoforged/neoforge/internal/NeoForgeBindings.java index 9404cd2fba..87a76cdb96 100644 --- a/src/main/java/net/neoforged/neoforge/internal/NeoForgeBindings.java +++ b/src/main/java/net/neoforged/neoforge/internal/NeoForgeBindings.java @@ -7,11 +7,24 @@ import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.IBindingsProvider; +import net.neoforged.fml.config.IConfigSpec; +import net.neoforged.fml.config.ModConfig; import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.network.ConfigSync; +import org.jetbrains.annotations.Nullable; public class NeoForgeBindings implements IBindingsProvider { @Override public IEventBus getGameBus() { return NeoForge.EVENT_BUS; } + + @Override + public void onConfigChanged(ModConfig modConfig, @Nullable IConfigSpec.ILoadedConfig loadedConfig) { + if (modConfig.getType() != ModConfig.Type.SERVER || loadedConfig == null) { + return; + } + + ConfigSync.addPendingConfig(modConfig, loadedConfig); + } } diff --git a/src/main/java/net/neoforged/neoforge/network/ConfigSync.java b/src/main/java/net/neoforged/neoforge/network/ConfigSync.java index 9c15298762..988520b397 100644 --- a/src/main/java/net/neoforged/neoforge/network/ConfigSync.java +++ b/src/main/java/net/neoforged/neoforge/network/ConfigSync.java @@ -6,23 +6,46 @@ package net.neoforged.neoforge.network; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.List; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; +import java.util.WeakHashMap; import java.util.stream.Collectors; import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.minecraft.server.MinecraftServer; import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.fml.config.IConfigSpec; import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.config.ModConfigs; +import net.neoforged.neoforge.network.configuration.SyncConfig; import net.neoforged.neoforge.network.payload.ConfigFilePayload; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal public final class ConfigSync { + private static final Object lock = new Object(); + /** + * Connection -> Config file name -> byte[] of the config serialized to TOML. + * + *

Pending config updates get sent to players in the PLAY phase only, + * but start being tracked as soon as the {@link SyncConfig} configuration task runs. + * This ensures that all updates during the configuration phase will eventually arrive to the clients. + * + *

Connections get removed when GC'ed thanks to the WeakHashMap. + */ + private static final Map> configsToSync = new WeakHashMap<>(); + private ConfigSync() {} - public static List syncConfigs() { + public static void syncAllConfigs(ServerConfigurationPacketListener listener) { + synchronized (lock) { + configsToSync.put(listener.getConnection(), new LinkedHashMap<>()); + } + final Map configData = ModConfigs.getConfigSet(ModConfig.Type.SERVER).stream().collect(Collectors.toMap(ModConfig::getFileName, mc -> { try { return Files.readAllBytes(mc.getFullPath()); @@ -31,9 +54,33 @@ public static List syncConfigs() { } })); - return configData.entrySet().stream() - .map(e -> new ConfigFilePayload(e.getKey(), e.getValue())) - .toList(); + for (var entry : configData.entrySet()) { + listener.send(new ConfigFilePayload(entry.getKey(), entry.getValue())); + } + } + + public static void addPendingConfig(ModConfig modConfig, IConfigSpec.ILoadedConfig loadedConfig) { + var bytes = loadedConfig.config().configFormat().createWriter().writeToString(loadedConfig.config()).getBytes(StandardCharsets.UTF_8); + synchronized (lock) { + for (var toSync : configsToSync.values()) { + toSync.put(modConfig.getFileName(), bytes); + } + } + } + + public static void syncPendingConfigs(MinecraftServer server) { + synchronized (lock) { + for (var player : server.getPlayerList().getPlayers()) { + var toSync = configsToSync.get(player.connection.getConnection()); + if (toSync == null) { + throw new IllegalStateException("configsToSync should contain an entry for player " + player.getName()); + } + toSync.forEach((fileName, data) -> { + PacketDistributor.sendToPlayer(player, new ConfigFilePayload(fileName, data)); + }); + toSync.clear(); + } + } } public static void receiveSyncedConfig(final byte[] contents, final String fileName) { diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java index a3e9c32c35..6e90c9302e 100644 --- a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java +++ b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java @@ -40,7 +40,7 @@ private static void register(final RegisterPayloadHandlersEvent event) { final PayloadRegistrar registrar = event.registrar("1") // Update this version if the payload semantics change. .optional(); registrar - .configurationToClient( + .commonToClient( ConfigFilePayload.TYPE, ConfigFilePayload.STREAM_CODEC, ClientPayloadHandler::handle) diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/SyncConfig.java b/src/main/java/net/neoforged/neoforge/network/configuration/SyncConfig.java index 9dbd7f8f7a..ab89dadc94 100644 --- a/src/main/java/net/neoforged/neoforge/network/configuration/SyncConfig.java +++ b/src/main/java/net/neoforged/neoforge/network/configuration/SyncConfig.java @@ -25,7 +25,7 @@ public record SyncConfig(ServerConfigurationPacketListener listener) implements @Override public void run(Consumer sender) { - ConfigSync.syncConfigs().forEach(sender); + ConfigSync.syncAllConfigs(listener); listener().finishCurrentTask(type()); }