Skip to content

Commit

Permalink
Easily sync config values between server & client (#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lyfts authored Jan 19, 2025
1 parent adfb839 commit 2752dc6
Show file tree
Hide file tree
Showing 7 changed files with 396 additions and 1 deletion.
17 changes: 17 additions & 0 deletions src/main/java/com/gtnewhorizon/gtnhlib/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,21 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ExcludeFromAutoGui {}

/**
* Fields or classes annotated with this will automatically be synced from server -> client. If applied to a class,
* all fields (including subcategories) in the class will be synced. All fields are restored to their original value
* when the player disconnects.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.TYPE })
@interface Sync {

/**
* Can be used to overwrite the sync behavior for fields in classes annotated with {@link Sync}.
*
* @return Whether the field should be synced. Defaults to true.
*/
boolean value() default true;
}
}
179 changes: 179 additions & 0 deletions src/main/java/com/gtnewhorizon/gtnhlib/config/ConfigFieldParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,39 @@ private static boolean isModDetected(Config.ModDetectedDefault modDefault) {
});
}

static String getValueAsString(@Nullable Object instance, Field field) throws ConfigException {
try {
val parser = getParser(field);
return parser.getAsString(instance, field);
} catch (Exception e) {
throw new ConfigException(
"Failed to get value as string for field " + field.getName()
+ " of type "
+ field.getType().getSimpleName()
+ " in class "
+ field.getDeclaringClass().getName()
+ ". Caused by: "
+ e);
}
}

static void setValueFromString(@Nullable Object instance, Field field, String value) throws ConfigException {
try {
val parser = getParser(field);
parser.setFromString(instance, value, field);
} catch (Exception e) {
throw new ConfigException(
"Failed to set value from string for field " + field.getName()
+ " of type "
+ field.getType().getSimpleName()
+ " in class "
+ field.getDeclaringClass().getName()
+ ". Caused by: "
+ e);
}

}

@SneakyThrows
private static Field extractField(Class<?> clazz, String field) {
return clazz.getDeclaredField(field);
Expand All @@ -158,6 +191,10 @@ void load(@Nullable Object instance, @Nullable String defValueString, Field fiel
String category, String name, String comment, String langKey);

void save(@Nullable Object instance, Field field, Configuration config, String category, String name);

void setFromString(@Nullable Object instance, String value, Field field);

String getAsString(@Nullable Object instance, Field field);
}

private static class BooleanParser implements Parser {
Expand Down Expand Up @@ -194,6 +231,25 @@ private boolean fromStringOrDefault(@Nullable Object instance, @Nullable String

return Boolean.parseBoolean(defValueString);
}

@Override
@SneakyThrows
public void setFromString(@Nullable Object instance, String value, Field field) {
val boxed = field.getType().equals(Boolean.class);
if (boxed) {
field.set(instance, Boolean.parseBoolean(value));
return;
}

field.setBoolean(instance, Boolean.parseBoolean(value));
}

@Override
@SneakyThrows
public String getAsString(@Nullable Object instance, Field field) {
val boxed = field.getType().equals(Boolean.class);
return Boolean.toString(boxed ? (Boolean) field.get(instance) : field.getBoolean(instance));
}
}

private static class IntParser implements Parser {
Expand Down Expand Up @@ -228,6 +284,25 @@ private int fromStringOrDefault(@Nullable Object instance, @Nullable String defV

return Integer.parseInt(defValueString);
}

@Override
@SneakyThrows
public void setFromString(@Nullable Object instance, String value, Field field) {
val boxed = field.getType().equals(Integer.class);
if (boxed) {
field.set(instance, Integer.parseInt(value));
return;
}

field.setInt(instance, Integer.parseInt(value));
}

@Override
@SneakyThrows
public String getAsString(@Nullable Object instance, Field field) {
val boxed = field.getType().equals(Integer.class);
return Integer.toString(boxed ? (Integer) field.get(instance) : field.getInt(instance));
}
}

private static class FloatParser implements Parser {
Expand Down Expand Up @@ -262,6 +337,25 @@ private float fromStringOrDefault(@Nullable Object instance, @Nullable String de

return Float.parseFloat(defValueString);
}

@Override
@SneakyThrows
public void setFromString(@org.jetbrains.annotations.Nullable Object instance, String value, Field field) {
val boxed = field.getType().equals(Float.class);
if (boxed) {
field.set(instance, Float.parseFloat(value));
return;
}

field.setFloat(instance, Float.parseFloat(value));
}

@Override
@SneakyThrows
public String getAsString(@Nullable Object instance, Field field) {
val boxed = field.getType().equals(Float.class);
return Float.toString(boxed ? (Float) field.get(instance) : field.getFloat(instance));
}
}

private static class DoubleParser implements Parser {
Expand Down Expand Up @@ -301,6 +395,25 @@ private double fromStringOrDefault(@Nullable Object instance, @Nullable String d

return Double.parseDouble(defValueString);
}

@Override
@SneakyThrows
public void setFromString(@Nullable Object instance, String value, Field field) {
val boxed = field.getType().equals(Double.class);
if (boxed) {
field.set(instance, Double.parseDouble(value));
return;
}

field.setDouble(instance, Double.parseDouble(value));
}

@Override
@SneakyThrows
public String getAsString(@Nullable Object instance, Field field) {
val boxed = field.getType().equals(Double.class);
return Double.toString(boxed ? (Double) field.get(instance) : field.getDouble(instance));
}
}

private static class StringParser implements Parser {
Expand Down Expand Up @@ -331,6 +444,18 @@ private String fromStringOrDefault(@Nullable Object instance, @Nullable String d

return defValueString;
}

@Override
@SneakyThrows
public void setFromString(@Nullable Object instance, String value, Field field) {
field.set(instance, value);
}

@Override
@SneakyThrows
public String getAsString(@Nullable Object instance, Field field) {
return (String) field.get(instance);
}
}

private static class EnumParser implements Parser {
Expand Down Expand Up @@ -415,6 +540,22 @@ private Enum<?> fromStringOrDefault(@Nullable Object instance, @Nullable String
}
return null;
}

@Override
@SneakyThrows
public void setFromString(@Nullable Object instance, String value, Field field) {
Field enumField = field.getType().getDeclaredField(value);
if (!enumField.isEnumConstant()) {
throw new NoSuchFieldException();
}
field.set(instance, enumField.get(instance));
}

@Override
@SneakyThrows
public String getAsString(@org.jetbrains.annotations.Nullable Object instance, Field field) {
return ((Enum<?>) field.get(instance)).name();
}
}

private static class StringArrayParser implements Parser {
Expand Down Expand Up @@ -446,6 +587,18 @@ private String[] fromStringOrDefault(@Nullable Object instance, @Nullable String
}
return value == null ? new String[0] : value;
}

@Override
@SneakyThrows
public void setFromString(@Nullable Object instance, String value, Field field) {
field.set(instance, value.split("|||"));
}

@Override
@SneakyThrows
public String getAsString(@Nullable Object instance, Field field) {
return String.join("|||", (String[]) field.get(instance));
}
}

private static class DoubleArrayParser implements Parser {
Expand Down Expand Up @@ -486,6 +639,19 @@ private double[] fromStringOrDefault(@Nullable Object instance, @Nullable String

return value == null ? new double[0] : value;
}

@Override
@SneakyThrows
public void setFromString(@Nullable Object instance, String value, Field field) {
field.set(instance, Arrays.stream(value.split(",")).mapToDouble(Double::parseDouble).toArray());
}

@Override
@SneakyThrows
public String getAsString(@Nullable Object instance, Field field) {
return Arrays.stream((double[]) field.get(instance)).mapToObj(Double::toString)
.collect(Collectors.joining(","));
}
}

private static class IntArrayParser implements Parser {
Expand Down Expand Up @@ -524,5 +690,18 @@ private int[] fromStringOrDefault(@Nullable Object instance, @Nullable String de

return value == null ? new int[0] : value;
}

@Override
@SneakyThrows
public void setFromString(@Nullable Object instance, String value, Field field) {
field.set(instance, Arrays.stream(value.split(",")).mapToInt(Integer::parseInt).toArray());
}

@Override
@SneakyThrows
public String getAsString(@Nullable Object instance, Field field) {
return Arrays.stream((int[]) field.get(instance)).mapToObj(Integer::toString)
.collect(Collectors.joining(","));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.gtnewhorizon.gtnhlib.config;

import static com.gtnewhorizon.gtnhlib.config.ConfigurationManager.LOGGER;

import java.util.Map;

import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.integrated.IntegratedServer;

import com.gtnewhorizon.gtnhlib.network.NetworkHandler;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.PlayerEvent;
import cpw.mods.fml.common.network.FMLNetworkEvent;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;

@SuppressWarnings("unused")
public final class ConfigSyncHandler {

static final Map<String, SyncedConfigElement> syncedElements = new Object2ObjectOpenHashMap<>();
private static boolean hasSyncedValues = false;

static {
FMLCommonHandler.instance().bus().register(new ConfigSyncHandler());
}

@SubscribeEvent
public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
if (!(event.player instanceof EntityPlayerMP playerMP)) return;
MinecraftServer server = MinecraftServer.getServer();
// no point in syncing in from client -> client.
if (server.isSinglePlayer() && !((IntegratedServer) server).getPublic()) {
return;
}
NetworkHandler.instance.sendTo(new PacketSyncConfig(syncedElements.values()), playerMP);
}

@SubscribeEvent
public void onClientDisconnect(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) {
if (!hasSyncedValues) return;
hasSyncedValues = false;
for (SyncedConfigElement element : syncedElements.values()) {
element.restoreValue();
}
}

static void onSync(PacketSyncConfig packet) {
for (Object2ObjectMap.Entry<String, String> entry : packet.syncedElements.object2ObjectEntrySet()) {
SyncedConfigElement element = syncedElements.get(entry.getKey());
if (element != null) {
try {
hasSyncedValues = true;
element.setSyncValue(entry.getValue());
} catch (ConfigException e) {
LOGGER.error("Failed to sync element {}", element, e);
}
}
}
}

static boolean hasSyncedValues() {
return hasSyncedValues;
}
}
Loading

0 comments on commit 2752dc6

Please sign in to comment.