Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync server config changes to the connected clients #1439

Draft
wants to merge 1 commit into
base: 1.21.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
57 changes: 52 additions & 5 deletions src/main/java/net/neoforged/neoforge/network/ConfigSync.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
*
* <p>Connections get removed when GC'ed thanks to the WeakHashMap.
*/
private static final Map<Connection, Map<String, byte[]>> configsToSync = new WeakHashMap<>();

private ConfigSync() {}

public static List<ConfigFilePayload> syncConfigs() {
public static void syncAllConfigs(ServerConfigurationPacketListener listener) {
synchronized (lock) {
configsToSync.put(listener.getConnection(), new LinkedHashMap<>());
}

final Map<String, byte[]> configData = ModConfigs.getConfigSet(ModConfig.Type.SERVER).stream().collect(Collectors.toMap(ModConfig::getFileName, mc -> {
try {
return Files.readAllBytes(mc.getFullPath());
Expand All @@ -31,9 +54,33 @@ public static List<ConfigFilePayload> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public record SyncConfig(ServerConfigurationPacketListener listener) implements

@Override
public void run(Consumer<CustomPacketPayload> sender) {
ConfigSync.syncConfigs().forEach(sender);
ConfigSync.syncAllConfigs(listener);
listener().finishCurrentTask(type());
}

Expand Down
Loading