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

Easily sync config values between server & client #102

Merged
merged 3 commits into from
Jan 19, 2025
Merged
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
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