From dbef25fedc8def0f1d51b564d8cc3edf350a38b0 Mon Sep 17 00:00:00 2001 From: Anastasia Chrysanthemum Date: Fri, 27 Feb 2026 04:48:08 +1100 Subject: [PATCH] feat: Allow Inherited Configs to use direct codecs. --- .../v3/GreenhouseInheritedConfigHolder.java | 20 +-- .../config/api/v3/InheritedConfigValue.java | 10 +- .../lang/GreenhouseConfigPolyamoryLang.java | 8 +- .../codec/InheritedConfigRecordCodec.java | 20 +-- ...ava => PolyamoryCommentedConfigCodec.java} | 10 +- ...ractGreenhouseConfigHolderBuilderImpl.java | 75 ++++++++-- .../GreenhouseConfigHolderBuilderImpl.java | 51 +++---- ...houseInheritedConfigHolderBuilderImpl.java | 140 ++++++++++-------- .../config/impl/util/InvokerUtils.java | 27 ++++ .../config/test/GreenhouseConfigTest.java | 25 +++- .../client/GreenhouseConfigTestClient.java | 39 ++++- .../config/test/config/SplitConfig.java | 4 +- 12 files changed, 281 insertions(+), 148 deletions(-) rename xplat/src/main/java/lgbt/greenhouse/config/impl/codec/{CommentedConfigCodec.java => PolyamoryCommentedConfigCodec.java} (96%) diff --git a/xplat/src/main/java/lgbt/greenhouse/config/api/v3/GreenhouseInheritedConfigHolder.java b/xplat/src/main/java/lgbt/greenhouse/config/api/v3/GreenhouseInheritedConfigHolder.java index 623e29b..ebc5290 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/api/v3/GreenhouseInheritedConfigHolder.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/api/v3/GreenhouseInheritedConfigHolder.java @@ -3,7 +3,6 @@ package lgbt.greenhouse.config.api.v3; import lgbt.greenhouse.config.api.v3.builder.GreenhouseConfigHolderBuilder; import lgbt.greenhouse.config.api.v3.builder.GreenhouseInheritedConfigHolderBuilder; import lgbt.greenhouse.config.api.v3.dfu.builder.GreenhouseConfigDataFixerBuilder; -import lgbt.greenhouse.config.api.v3.lang.GreenhouseConfigLang; import lgbt.greenhouse.config.impl.GreenhouseConfigImpl; import lgbt.greenhouse.config.impl.config.GreenhouseConfigHolderImpl; import lgbt.greenhouse.config.impl.config.builder.GreenhouseConfigHolderBuilderImpl; @@ -31,7 +30,6 @@ public interface GreenhouseInheritedConfigHolder extends Green * @param clazz The class representation of the unique values to this config. * @param inheritedConfig A config to inherit into this one. * @param dataVersion The data version for the config, used for data fixes. - * @param configLang The config lang to use for this config. Determines the format of the config file. * @param side The side to register the config on. * @param configBuilderConsumer A consumer used for configuring the config builder. * @param The class representation of the inherited config. @@ -41,10 +39,9 @@ public interface GreenhouseInheritedConfigHolder extends Green static GreenhouseInheritedConfigHolder register(Class clazz, GreenhouseConfigHolder inheritedConfig, int dataVersion, - GreenhouseConfigLang configLang, GreenhouseConfigSide side, Consumer> configBuilderConsumer) { - return register(clazz, inheritedConfig, dataVersion, configLang, side, configBuilderConsumer, fixerBuilderConsumer -> { + return register(clazz, inheritedConfig, dataVersion, side, configBuilderConsumer, fixerBuilderConsumer -> { }); } @@ -58,7 +55,6 @@ public interface GreenhouseInheritedConfigHolder extends Green * @param clazz The class representation of the unique values to this config. * @param inheritedConfig A config to inherit into this one. * @param dataVersion The data version for the config, used for data fixes. - * @param configLang The config lang to use for this config. Determines the format of the config file. * @param side The side to register the config on. * @param configBuilderConsumer A consumer used for configuring the config builder. * @param fixerBuilderConsumer A consumer used for configuring the data fixer builder. @@ -69,14 +65,13 @@ public interface GreenhouseInheritedConfigHolder extends Green static GreenhouseInheritedConfigHolder register(Class clazz, GreenhouseConfigHolder inheritedConfig, int dataVersion, - GreenhouseConfigLang configLang, GreenhouseConfigSide side, Consumer> configBuilderConsumer, Consumer fixerBuilderConsumer) { if (!(inheritedConfig instanceof GreenhouseConfigHolderImpl holderImpl)) throw new IllegalStateException("Unable to inherit config from config of class '" + inheritedConfig.getClass() + "'"); - return register(clazz, holderImpl.getBuilder(), dataVersion, configLang, side, configBuilderConsumer, fixerBuilderConsumer); + return register(clazz, holderImpl.getBuilder(), dataVersion, side, configBuilderConsumer, fixerBuilderConsumer); } /** @@ -89,7 +84,6 @@ public interface GreenhouseInheritedConfigHolder extends Green * @param clazz The class representation of the unique values to this config. * @param inheritedConfig A config to inherit into this one. * @param dataVersion The data version for the config, used for data fixes. - * @param configLang The config lang to use for this config. Determines the format of the config file. * @param side The side to register the config on. * @param configBuilderConsumer A consumer used for configuring the config builder. * @param The class representation of the inherited config. @@ -99,10 +93,9 @@ public interface GreenhouseInheritedConfigHolder extends Green static GreenhouseInheritedConfigHolder register(Class clazz, GreenhouseConfigHolderBuilder inheritedConfig, int dataVersion, - GreenhouseConfigLang configLang, GreenhouseConfigSide side, Consumer> configBuilderConsumer) { - return register(clazz, inheritedConfig, dataVersion, configLang, side, configBuilderConsumer, fixerBuilderConsumer -> { + return register(clazz, inheritedConfig, dataVersion, side, configBuilderConsumer, fixerBuilderConsumer -> { }); } @@ -116,7 +109,6 @@ public interface GreenhouseInheritedConfigHolder extends Green * @param clazz The class representation of the unique values to this config. * @param inheritedConfig A config to inherit into this one. * @param dataVersion The data version for the config, used for data fixes. - * @param configLang The config lang to use for this config. Determines the format of the config file. * @param side The side to register the config on. * @param configBuilderConsumer A consumer used for configuring the config builder. * @param fixerBuilderConsumer A consumer used for configuring the data fixer builder. @@ -127,14 +119,14 @@ public interface GreenhouseInheritedConfigHolder extends Green static GreenhouseInheritedConfigHolder register(Class clazz, GreenhouseConfigHolderBuilder inheritedConfig, int dataVersion, - GreenhouseConfigLang configLang, GreenhouseConfigSide side, Consumer> configBuilderConsumer, Consumer fixerBuilderConsumer) { - GreenhouseConfigDataFixerBuilderImpl fixerBuilder = ((GreenhouseConfigHolderBuilderImpl) inheritedConfig).getFixerBuilder().copy(); + GreenhouseConfigHolderBuilderImpl inheritedConfigBuilder = ((GreenhouseConfigHolderBuilderImpl) inheritedConfig); + GreenhouseConfigDataFixerBuilderImpl fixerBuilder = inheritedConfigBuilder.getFixerBuilder().copy(); fixerBuilderConsumer.accept(fixerBuilder); - GreenhouseInheritedConfigHolderBuilderImpl configBuilder = new GreenhouseInheritedConfigHolderBuilderImpl<>(inheritedConfig, clazz, configLang, fixerBuilder, dataVersion); + GreenhouseInheritedConfigHolderBuilderImpl configBuilder = new GreenhouseInheritedConfigHolderBuilderImpl<>(inheritedConfig, clazz, inheritedConfigBuilder.getConfigLang(), fixerBuilder, dataVersion); configBuilderConsumer.accept(configBuilder); return GreenhouseConfigImpl.registerConfig(configBuilder.build(), side); diff --git a/xplat/src/main/java/lgbt/greenhouse/config/api/v3/InheritedConfigValue.java b/xplat/src/main/java/lgbt/greenhouse/config/api/v3/InheritedConfigValue.java index 557d0be..c9bf59a 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/api/v3/InheritedConfigValue.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/api/v3/InheritedConfigValue.java @@ -3,11 +3,11 @@ package lgbt.greenhouse.config.api.v3; /** * A joined config value, used in inherited config holders. * - * @param inherited A inherited config object, may be modified by the server. - * @param extra A config object unique to this object. - * @param A inherited config class. - * @param An extra config class. + * @param inherited A inherited config object, may be modified by the server. + * @param extra A config object unique to this object. + * @param A inherited config class. + * @param An extra config class. * @see GreenhouseInheritedConfigHolder */ -public record InheritedConfigValue(Root inherited, Extra extra) { +public record InheritedConfigValue(Inherited inherited, Extra extra) { } diff --git a/xplat/src/main/java/lgbt/greenhouse/config/api/v3/lang/GreenhouseConfigPolyamoryLang.java b/xplat/src/main/java/lgbt/greenhouse/config/api/v3/lang/GreenhouseConfigPolyamoryLang.java index b189064..ea9ce98 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/api/v3/lang/GreenhouseConfigPolyamoryLang.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/api/v3/lang/GreenhouseConfigPolyamoryLang.java @@ -3,7 +3,7 @@ package lgbt.greenhouse.config.api.v3.lang; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.DynamicOps; -import lgbt.greenhouse.config.impl.codec.CommentedConfigCodec; +import lgbt.greenhouse.config.impl.codec.PolyamoryCommentedConfigCodec; import lgbt.greenhouse.polyamory.core.api.PolyamoryElement; import lgbt.greenhouse.polyamory.core.api.PolyamoryFormat; import lgbt.greenhouse.polyamory.dfu.api.PolyamoryOps; @@ -24,7 +24,7 @@ import java.util.Map; * @see GreenhouseConfigJsonCLang */ public abstract class GreenhouseConfigPolyamoryLang implements GreenhouseConfigLang> { - private final Map, CommentedConfigCodec> CODEC_TO_COMMENTED_CODEC = new HashMap<>(); + private final Map, PolyamoryCommentedConfigCodec> CODEC_TO_COMMENTED_CODEC = new HashMap<>(); private final PolyamoryOps ops; protected GreenhouseConfigPolyamoryLang(PolyamoryOps ops) { @@ -48,7 +48,7 @@ public abstract class GreenhouseConfigPolyamoryLang implements GreenhouseConfigL @Override public DataResult decode(Codec codec, PolyamoryElement polyamoryElement) { //noinspection unchecked - CommentedConfigCodec commentedCodec = (CommentedConfigCodec) CODEC_TO_COMMENTED_CODEC.computeIfAbsent(codec, CommentedConfigCodec::new); + PolyamoryCommentedConfigCodec commentedCodec = (PolyamoryCommentedConfigCodec) CODEC_TO_COMMENTED_CODEC.computeIfAbsent(codec, PolyamoryCommentedConfigCodec::new); return commentedCodec.parse(ops, polyamoryElement).map(CommentedValue::value); } @@ -56,7 +56,7 @@ public abstract class GreenhouseConfigPolyamoryLang implements GreenhouseConfigL @Override public DataResult> encode(Codec codec, Config value, @Nullable PolyamoryElement read) { //noinspection unchecked - CommentedConfigCodec commentedCodec = (CommentedConfigCodec) CODEC_TO_COMMENTED_CODEC.computeIfAbsent(codec, CommentedConfigCodec::new); + PolyamoryCommentedConfigCodec commentedCodec = (PolyamoryCommentedConfigCodec) CODEC_TO_COMMENTED_CODEC.computeIfAbsent(codec, PolyamoryCommentedConfigCodec::new); if (read == null) { return commentedCodec.encodeStart(ops, new CommentedValueWithoutInternal<>(Collections.emptyList(), value)); diff --git a/xplat/src/main/java/lgbt/greenhouse/config/impl/codec/InheritedConfigRecordCodec.java b/xplat/src/main/java/lgbt/greenhouse/config/impl/codec/InheritedConfigRecordCodec.java index 41d95b9..ba2e371 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/impl/codec/InheritedConfigRecordCodec.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/impl/codec/InheritedConfigRecordCodec.java @@ -13,11 +13,11 @@ import java.lang.invoke.MethodHandle; import java.util.*; import java.util.function.Supplier; -public class InheritedConfigRecordCodec extends ConfigRecordCodec> { +public class InheritedConfigRecordCodec extends ConfigRecordCodec> { - public final Map, FieldEntry>> inheritedEntries; + public final Map, FieldEntry>> inheritedEntries; - public final Map, FieldEntry>> extraEntries; + public final Map, FieldEntry>> extraEntries; @Nullable private MethodHandle inheritedConstructor = null; @@ -25,16 +25,16 @@ public class InheritedConfigRecordCodec extends ConfigRecordCodec> supplier, - Map> inheritedEntries, - Map> extraEntries) { + Supplier> supplier, + Map> inheritedEntries, + Map> extraEntries) { super(modId, supplier, Collections.emptyMap()); this.inheritedEntries = createCodecEntriesMapSingle(inheritedEntries, supplier.get().inherited()); this.extraEntries = createCodecEntriesMapSingle(extraEntries, supplier.get().extra()); } @Override - public DataResult, T>> decode(DynamicOps ops, T input) { + public DataResult, T>> decode(DynamicOps ops, T input) { DataResult inheritedResult = createParameters(ops, input, inheritedEntries); DataResult extraResult = createParameters(ops, input, extraEntries); @@ -53,13 +53,13 @@ public class InheritedConfigRecordCodec extends ConfigRecordCodec(inherited, extra), input)); } catch (Throwable e) { @@ -68,7 +68,7 @@ public class InheritedConfigRecordCodec extends ConfigRecordCodec DataResult encode(InheritedConfigValue input, DynamicOps ops, T prefix) { + public DataResult encode(InheritedConfigValue input, DynamicOps ops, T prefix) { Set operatedKeys = new HashSet<>(); HashMap toMerge = new HashMap<>(); var builder = ops.mapBuilder(); diff --git a/xplat/src/main/java/lgbt/greenhouse/config/impl/codec/CommentedConfigCodec.java b/xplat/src/main/java/lgbt/greenhouse/config/impl/codec/PolyamoryCommentedConfigCodec.java similarity index 96% rename from xplat/src/main/java/lgbt/greenhouse/config/impl/codec/CommentedConfigCodec.java rename to xplat/src/main/java/lgbt/greenhouse/config/impl/codec/PolyamoryCommentedConfigCodec.java index 60780ff..ddb0c35 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/impl/codec/CommentedConfigCodec.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/impl/codec/PolyamoryCommentedConfigCodec.java @@ -24,11 +24,11 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -public class CommentedConfigCodec implements Codec> { +public class PolyamoryCommentedConfigCodec implements Codec> { private final Codec> codec; private final List> entries; - public CommentedConfigCodec(Codec elementCodec) { + public PolyamoryCommentedConfigCodec(Codec elementCodec) { this.codec = CommentedValueCodec.create(elementCodec); this.entries = createCommentEntries(elementCodec); } @@ -258,6 +258,12 @@ public class CommentedConfigCodec implements Codec> { addToCommentedFieldEntriesFromMapDecoder(entries, fieldEntry, rootMapDecoder); } + // Check whether the internal MapCodec is an unsafe assumed map codec. + Codec unsafeCodec = InvokerUtils.mapAwayFromUnsafeMapCodec(mapCodec); + if (unsafeCodec != null) { + addToCommentedFieldEntriesFromCodec(entries, fieldEntry, unsafeCodec); + } + addToCommentedFieldEntriesFromMapDecoder(entries, fieldEntry, mapCodec); } } diff --git a/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/AbstractGreenhouseConfigHolderBuilderImpl.java b/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/AbstractGreenhouseConfigHolderBuilderImpl.java index 025fed8..2e60f72 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/AbstractGreenhouseConfigHolderBuilderImpl.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/AbstractGreenhouseConfigHolderBuilderImpl.java @@ -1,12 +1,14 @@ package lgbt.greenhouse.config.impl.config.builder; import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.MapDecoder; import lgbt.greenhouse.config.api.v3.GreenhouseConfigHolder; +import lgbt.greenhouse.config.api.v3.GreenhouseConfigValueCodec; import lgbt.greenhouse.config.api.v3.builder.AbstractGreenhouseConfigHolderBuilder; import lgbt.greenhouse.config.api.v3.event.GreenhouseConfigEvents; import lgbt.greenhouse.config.api.v3.lang.GreenhouseConfigLang; import lgbt.greenhouse.config.impl.codec.CommonFieldEntry; -import lgbt.greenhouse.config.impl.codec.ConfigRecordCodec; import lgbt.greenhouse.config.impl.codec.FieldEntry; import lgbt.greenhouse.config.impl.dfu.builder.GreenhouseConfigDataFixerBuilderImpl; import lgbt.greenhouse.config.impl.util.InvokerUtils; @@ -15,6 +17,7 @@ import org.jspecify.annotations.Nullable; import java.util.*; import java.util.function.Function; +import java.util.function.Supplier; @SuppressWarnings("unchecked") public abstract class AbstractGreenhouseConfigHolderBuilderImpl, Impl extends GreenhouseConfigHolder> implements AbstractGreenhouseConfigHolderBuilder { @@ -30,8 +33,14 @@ public abstract class AbstractGreenhouseConfigHolderBuilderImpl codec; + private Supplier defaultValue; + + @Nullable + protected Codec directCodec; + @Nullable + protected Codec finalCodec; protected final GreenhouseConfigDataFixerBuilderImpl fixerBuilder; @@ -65,6 +74,10 @@ public abstract class AbstractGreenhouseConfigHolderBuilderImpl getConfigLang() { + return configLang; + } + public abstract Function getter(); public abstract boolean allowNetworkSerialization(); @@ -75,7 +88,7 @@ public abstract class AbstractGreenhouseConfigHolderBuilderImpl codec) { - this.codec = codec; + this.directCodec = codec; return (Builder) this; } @@ -87,25 +100,47 @@ public abstract class AbstractGreenhouseConfigHolderBuilderImpl new ArrayList<>()).add(callback); } - public Codec getCodec() { + protected Supplier getOrCreateDefaultValue() { + if (defaultValue == null) { + defaultValue = createDefaultValue(); + } + return defaultValue; + } + + protected Supplier createLocalDataDefaultValue() { + return directCodec != null + ? InvokerUtils.createFromArrayOrThrow(modId, localDataClazz, getCodecDefaultValues(directCodec)) + : InvokerUtils.createFromFieldsOrThrow(modId, localDataClazz, fields.values() + .stream() + .map(CommonFieldEntry::castForList) + .toList() + ); + } + + protected abstract Supplier createDefaultValue(); + + public Codec getOrCreateCodec() { if (!createdCodec) { - if (this.codec == null) { + if (this.directCodec == null) { if (fields.isEmpty()) { throwNoCodecException(); } - this.codec = new ConfigRecordCodec<>(modId, InvokerUtils.createFromFieldsOrThrow(modId, localDataClazz, fields.values() - .stream() - .map(CommonFieldEntry::castForList) - .toList()), fields); + finalCodec = createCodecFromFields(); + } else { + finalCodec = mapDirectCodec(directCodec); } } this.createdCodec = true; - if (codec == null) { + if (finalCodec == null) { throwNoCodecException(); } - return codec; + return finalCodec; } + protected abstract Codec createCodecFromFields(); + + protected abstract Codec mapDirectCodec(Codec directCodec); + public GreenhouseConfigDataFixerBuilderImpl getFixerBuilder() { return fixerBuilder; } @@ -113,4 +148,22 @@ public abstract class AbstractGreenhouseConfigHolderBuilderImpl codec) { + List list = new ArrayList<>(); + if (codec instanceof MapCodec.MapCodecCodec(MapCodec mapCodec)) { + var builder = InvokerUtils.invokeInternalBuilderFromRecordCodec(mapCodec); + if (builder == null) { + throw new UnsupportedOperationException("Greenhouse Config must use a RecordCodecBuilder when using the direct method"); + } + MapDecoder rootDecoder = InvokerUtils.invokeDecoderFromRecordCodecBuilder(builder); + List> reflectedFields = InvokerUtils.invokeDecodersFromRecordCodecDecoder(rootDecoder); + for (MapDecoder decoder : reflectedFields) { + if (!(decoder instanceof GreenhouseConfigValueCodec valueCodec)) + throw new UnsupportedOperationException("Root values within direct method codecs must be ConfigValueCodecs"); + list.add(valueCodec.defaultValue.get()); + } + } + return list.toArray(); + } } diff --git a/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/GreenhouseConfigHolderBuilderImpl.java b/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/GreenhouseConfigHolderBuilderImpl.java index b42e6c1..167aed6 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/GreenhouseConfigHolderBuilderImpl.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/GreenhouseConfigHolderBuilderImpl.java @@ -1,14 +1,12 @@ package lgbt.greenhouse.config.impl.config.builder; import com.mojang.serialization.Codec; -import com.mojang.serialization.MapCodec; -import com.mojang.serialization.MapDecoder; import lgbt.greenhouse.config.api.v3.GreenhouseConfigHolder; -import lgbt.greenhouse.config.api.v3.GreenhouseConfigValueCodec; import lgbt.greenhouse.config.api.v3.builder.DefaultValueCommentSettings; import lgbt.greenhouse.config.api.v3.builder.GreenhouseConfigHolderBuilder; import lgbt.greenhouse.config.api.v3.lang.GreenhouseConfigLang; import lgbt.greenhouse.config.impl.codec.CommonFieldEntry; +import lgbt.greenhouse.config.impl.codec.ConfigRecordCodec; import lgbt.greenhouse.config.impl.codec.FieldEntry; import lgbt.greenhouse.config.impl.codec.network.ConfigRecordStreamCodec; import lgbt.greenhouse.config.impl.codec.network.StreamFieldEntry; @@ -22,6 +20,7 @@ import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import java.util.function.Supplier; public class GreenhouseConfigHolderBuilderImpl extends AbstractGreenhouseConfigHolderBuilderImpl, GreenhouseConfigHolder> implements GreenhouseConfigHolderBuilder { protected boolean createdStreamCodec; @@ -47,14 +46,8 @@ public class GreenhouseConfigHolderBuilderImpl extends AbstractGreenhous dataVersion, modId, configLang, - fields.isEmpty() - ? InvokerUtils.createFromArrayOrThrow(modId, localDataClazz, getCodecDefaultValues(getCodec())) - : InvokerUtils.createFromFieldsOrThrow(modId, localDataClazz, fields.values() - .stream() - .map(CommonFieldEntry::castForList) - .toList() - ), - getCodec(), + getOrCreateDefaultValue(), + getOrCreateCodec(), getStreamCodec(), getFixerBuilder().getOrBuild(), getFixerBuilder().getPreviousLangs(), @@ -112,24 +105,6 @@ public class GreenhouseConfigHolderBuilderImpl extends AbstractGreenhous return streamCodec; } - private Object[] getCodecDefaultValues(Codec codec) { - List list = new ArrayList<>(); - if (codec instanceof MapCodec.MapCodecCodec(MapCodec mapCodec)) { - var builder = InvokerUtils.invokeInternalBuilderFromRecordCodec(mapCodec); - if (builder == null) { - throw new UnsupportedOperationException("Greenhouse Config must use a RecordCodecBuilder when using the direct method"); - } - MapDecoder rootDecoder = InvokerUtils.invokeDecoderFromRecordCodecBuilder(builder); - List> reflectedFields = InvokerUtils.invokeDecodersFromRecordCodecDecoder(rootDecoder); - for (MapDecoder decoder : reflectedFields) { - if (!(decoder instanceof GreenhouseConfigValueCodec valueCodec)) - throw new UnsupportedOperationException("Root values within direct method codecs must be ConfigValueCodecs"); - list.add(valueCodec.defaultValue.get()); - } - } - return list.toArray(); - } - @Override public Function getter() { return Function.identity(); @@ -139,4 +114,22 @@ public class GreenhouseConfigHolderBuilderImpl extends AbstractGreenhous public boolean allowNetworkSerialization() { return true; } + + @Override + protected Supplier createDefaultValue() { + return createLocalDataDefaultValue(); + } + + @Override + public Codec createCodecFromFields() { + return new ConfigRecordCodec<>(modId, InvokerUtils.createFromFieldsOrThrow(modId, localDataClazz, fields.values() + .stream() + .map(CommonFieldEntry::castForList) + .toList()), fields); + } + + @Override + public Codec mapDirectCodec(Codec directCodec) { + return directCodec; + } } diff --git a/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/GreenhouseInheritedConfigHolderBuilderImpl.java b/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/GreenhouseInheritedConfigHolderBuilderImpl.java index 85fb945..f0ab5de 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/GreenhouseInheritedConfigHolderBuilderImpl.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/impl/config/builder/GreenhouseInheritedConfigHolderBuilderImpl.java @@ -1,6 +1,8 @@ package lgbt.greenhouse.config.impl.config.builder; import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import lgbt.greenhouse.config.api.v3.GreenhouseInheritedConfigHolder; import lgbt.greenhouse.config.api.v3.InheritedConfigValue; import lgbt.greenhouse.config.api.v3.builder.DefaultValueCommentSettings; @@ -9,13 +11,12 @@ import lgbt.greenhouse.config.api.v3.builder.GreenhouseInheritedConfigHolderBuil import lgbt.greenhouse.config.api.v3.lang.GreenhouseConfigLang; import lgbt.greenhouse.config.api.v3.registry.LateHolderSet; import lgbt.greenhouse.config.impl.GreenhouseConfigImpl; -import lgbt.greenhouse.config.impl.codec.CommonFieldEntry; +import lgbt.greenhouse.config.impl.codec.ConfigRecordCodec; import lgbt.greenhouse.config.impl.codec.FieldEntry; import lgbt.greenhouse.config.impl.codec.InheritedConfigRecordCodec; import lgbt.greenhouse.config.impl.config.GreenhouseConfigHolderImpl; import lgbt.greenhouse.config.impl.config.GreenhouseInheritedConfigHolderImpl; import lgbt.greenhouse.config.impl.dfu.builder.GreenhouseConfigDataFixerBuilderImpl; -import lgbt.greenhouse.config.impl.util.InvokerUtils; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; @@ -27,48 +28,37 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -public final class GreenhouseInheritedConfigHolderBuilderImpl extends AbstractGreenhouseConfigHolderBuilderImpl, GreenhouseInheritedConfigHolderBuilder, GreenhouseInheritedConfigHolder> implements GreenhouseInheritedConfigHolderBuilder { - private final GreenhouseConfigHolderBuilder rootConfig; +public final class GreenhouseInheritedConfigHolderBuilderImpl extends AbstractGreenhouseConfigHolderBuilderImpl, GreenhouseInheritedConfigHolderBuilder, GreenhouseInheritedConfigHolder> implements GreenhouseInheritedConfigHolderBuilder { + private final GreenhouseConfigHolderBuilder inheritedConfig; @Nullable - private GreenhouseInheritedConfigHolder built; + private GreenhouseInheritedConfigHolder built; - public GreenhouseInheritedConfigHolderBuilderImpl(GreenhouseConfigHolderBuilder inheritedConfig, + public GreenhouseInheritedConfigHolderBuilderImpl(GreenhouseConfigHolderBuilder inheritedConfig, Class extraClazz, GreenhouseConfigLang configLang, GreenhouseConfigDataFixerBuilderImpl fixerBuilder, int dataVersion) { - super(GreenhouseConfigImpl.getRecordBuilderData(inheritedConfig).modId(), extraClazz, ((GreenhouseConfigHolderBuilderImpl) inheritedConfig).localDataClazz, configLang, fixerBuilder, dataVersion); - this.rootConfig = inheritedConfig; + super(GreenhouseConfigImpl.getRecordBuilderData(inheritedConfig).modId(), extraClazz, ((GreenhouseConfigHolderBuilderImpl) inheritedConfig).localDataClazz, configLang, fixerBuilder, dataVersion); + this.inheritedConfig = inheritedConfig; - if (this.dataVersion < ((GreenhouseConfigHolderBuilderImpl) inheritedConfig).dataVersion) { + if (this.dataVersion < ((GreenhouseConfigHolderBuilderImpl) inheritedConfig).dataVersion) { throw new IllegalArgumentException("The data version for inherited config '" + GreenhouseConfigImpl.getRecordBuilderData(inheritedConfig).modId() + "' must not be less than the inherited config's"); } } - public GreenhouseInheritedConfigHolder build() { + public GreenhouseInheritedConfigHolder build() { if (built == null) { - Supplier defaultConfig = createDefaultValue(); - GreenhouseConfigHolderBuilderImpl inheritedConfigBuilder = (GreenhouseConfigHolderBuilderImpl) rootConfig; - GreenhouseConfigHolderImpl inheritedConfig = (GreenhouseConfigHolderImpl) inheritedConfigBuilder.build(); - - Supplier> defaultCombined = () -> new InheritedConfigValue<>(inheritedConfig.getDefaultValue().get(), defaultConfig.get()); - - // TODO: Allow InheritedConfigRecordCodec to use RecordCodecBuilders. - Codec> combinedCodec = new InheritedConfigRecordCodec<>( - modId, - defaultCombined, - ((GreenhouseConfigHolderBuilderImpl) rootConfig).fields, - fields - ); + GreenhouseConfigHolderBuilderImpl inheritedConfigBuilder = (GreenhouseConfigHolderBuilderImpl) inheritedConfig; + GreenhouseConfigHolderImpl inheritedConfig = (GreenhouseConfigHolderImpl) inheritedConfigBuilder.build(); //noinspection unchecked built = new GreenhouseInheritedConfigHolderImpl<>( - (Class>) (Object) InheritedConfigValue.class, + (Class>) (Object) InheritedConfigValue.class, dataVersion, modId, configLang, - defaultCombined, - combinedCodec, + getOrCreateDefaultValue(), + getOrCreateCodec(), inheritedConfig.getStreamCodec(), fixerBuilder.getOrBuild(), fixerBuilder.getPreviousLangs(), @@ -80,45 +70,45 @@ public final class GreenhouseInheritedConfigHolderBuilderImpl exten } @Override - public GreenhouseInheritedConfigHolderBuilderImpl withValue(String key, - @Nullable String comments, - Codec codec, - V defaultValue, - Function getter, - DefaultValueCommentSettings defaultValueCommentSettings) { + public GreenhouseInheritedConfigHolderBuilderImpl withValue(String key, + @Nullable String comments, + Codec codec, + V defaultValue, + Function getter, + DefaultValueCommentSettings defaultValueCommentSettings) { fields.put(key, new FieldEntry<>(key, comments, codec, defaultValue, getter, defaultValueCommentSettings)); return this; } @Override - public GreenhouseInheritedConfigHolderBuilderImpl withNetworkSynchronizedValue(String key, @Nullable String comments, - Codec codec, StreamCodec streamCodec, - V defaultValue, - Function getter, - DefaultValueCommentSettings defaultValueCommentSettings) { + public GreenhouseInheritedConfigHolderBuilderImpl withNetworkSynchronizedValue(String key, @Nullable String comments, + Codec codec, StreamCodec streamCodec, + V defaultValue, + Function getter, + DefaultValueCommentSettings defaultValueCommentSettings) { throw new UnsupportedOperationException("Unable to serialize extra values within an inherited config. Use the original config to serialize values over the network"); } @Override - public GreenhouseInheritedConfigHolderBuilderImpl withNetworkSynchronizedRegistryHolder(String key, @Nullable String comments, - ResourceKey> registry, - ResourceKey defaultValue, - Function> getter, - DefaultValueCommentSettings defaultValueCommentSettings) { + public GreenhouseInheritedConfigHolderBuilderImpl withNetworkSynchronizedRegistryHolder(String key, @Nullable String comments, + ResourceKey> registry, + ResourceKey defaultValue, + Function> getter, + DefaultValueCommentSettings defaultValueCommentSettings) { throw new UnsupportedOperationException("Unable to serialize extra values within an inherited config. Use the original config to serialize values over the network"); } @Override - public GreenhouseInheritedConfigHolderBuilderImpl withNetworkSerializedRegistryHolders(String key, @Nullable String comments, - ResourceKey> registry, - Function> getter, - Consumer> defaultValueConsumer, - DefaultValueCommentSettings defaultValueCommentSettings) { + public GreenhouseInheritedConfigHolderBuilderImpl withNetworkSerializedRegistryHolders(String key, @Nullable String comments, + ResourceKey> registry, + Function> getter, + Consumer> defaultValueConsumer, + DefaultValueCommentSettings defaultValueCommentSettings) { throw new UnsupportedOperationException("Unable to serialize extra values within an inherited config. Use the original config to serialize values over the network"); } @Override - public Function, Extra> getter() { + public Function, Extra> getter() { return InheritedConfigValue::extra; } @@ -127,19 +117,45 @@ public final class GreenhouseInheritedConfigHolderBuilderImpl exten return false; } - private Supplier createDefaultValue() { - return () -> { - try { - return InvokerUtils.createFromFields(localDataClazz, fields.values() - .stream() - .map(CommonFieldEntry::castForList) - .toList()); - } catch (Throwable e) { - throw new NullPointerException("Failed to find public constructor for config '" + modId + "' for parameter classes '" + fields.values() - .stream() - .map(entry -> entry.defaultValue().getClass()) - .toList() + "'"); - } - }; + @Override + protected Codec> createCodecFromFields() { + GreenhouseConfigHolderBuilderImpl inheritedConfigBuilder = (GreenhouseConfigHolderBuilderImpl) inheritedConfig; + + if (inheritedConfigBuilder.directCodec != null) { + return mapDirectCodec(new ConfigRecordCodec<>( + modId, + createLocalDataDefaultValue(), + fields + )); + } + + return new InheritedConfigRecordCodec<>( + modId, + getOrCreateDefaultValue(), + inheritedConfigBuilder.fields, + fields + ); + } + + @Override + protected Codec> mapDirectCodec(Codec directCodec) { + GreenhouseConfigHolderBuilderImpl inheritedConfigBuilder = (GreenhouseConfigHolderBuilderImpl) inheritedConfig; + GreenhouseConfigHolderImpl inheritedConfig = (GreenhouseConfigHolderImpl) inheritedConfigBuilder.build(); + + return RecordCodecBuilder.create(inst -> inst.group( + MapCodec.assumeMapUnsafe(inheritedConfig.getCodec()) + .forGetter(InheritedConfigValue::inherited), + MapCodec.assumeMapUnsafe(directCodec) + .forGetter(InheritedConfigValue::extra) + ).apply(inst, InheritedConfigValue::new)); + } + + @Override + protected Supplier> createDefaultValue() { + Supplier defaultConfig = createLocalDataDefaultValue(); + GreenhouseConfigHolderBuilderImpl inheritedConfigBuilder = (GreenhouseConfigHolderBuilderImpl) inheritedConfig; + GreenhouseConfigHolderImpl inheritedConfig = (GreenhouseConfigHolderImpl) inheritedConfigBuilder.build(); + + return () -> new InheritedConfigValue<>(inheritedConfig.getDefaultValue().get(), defaultConfig.get()); } } diff --git a/xplat/src/main/java/lgbt/greenhouse/config/impl/util/InvokerUtils.java b/xplat/src/main/java/lgbt/greenhouse/config/impl/util/InvokerUtils.java index 52430ad..172db64 100644 --- a/xplat/src/main/java/lgbt/greenhouse/config/impl/util/InvokerUtils.java +++ b/xplat/src/main/java/lgbt/greenhouse/config/impl/util/InvokerUtils.java @@ -142,6 +142,33 @@ public class InvokerUtils { throw new NullPointerException("Could not obtain 'wrapped' field within RecursiveCodec."); } + @Nullable + public static Codec mapAwayFromUnsafeMapCodec(MapCodec mapCodec) { + try { + Class clazz = mapCodec.getClass(); + Field field = clazz.getDeclaredField("val$codec"); + + Optional valueHandle = find(lookup -> + MethodHandles.privateLookupIn(clazz, lookup) + .findStaticGetter(clazz, "COMPRESSED_VALUE_KEY", String.class) + ); + Optional codecHandle = find(lookup -> + MethodHandles.privateLookupIn(clazz, lookup) + .unreflectGetter(field) + ); + + // Check for valueHandle to make sure that the MapCodec is from assumeMapUnsafe or similar. + if (valueHandle.isEmpty() || codecHandle.isEmpty()) + return null; + + if (codecHandle.get().invoke(mapCodec) instanceof Codec codec) + //noinspection unchecked + return (Codec) codec; + } catch (Throwable ignored) { + } + return null; + } + /** * Potentially invokes a {@link RecordCodecBuilder} from a {@link MapCodec}. * diff --git a/xplat/src/test/java/lgbt/greenhouse/config/test/GreenhouseConfigTest.java b/xplat/src/test/java/lgbt/greenhouse/config/test/GreenhouseConfigTest.java index a6002f8..7447e78 100644 --- a/xplat/src/test/java/lgbt/greenhouse/config/test/GreenhouseConfigTest.java +++ b/xplat/src/test/java/lgbt/greenhouse/config/test/GreenhouseConfigTest.java @@ -190,7 +190,8 @@ public class GreenhouseConfigTest { 1, GreenhouseConfigJsonCLang.INSTANCE, configBuilder -> configBuilder - .withNetworkSynchronizedValue("color", + .withNetworkSynchronizedValue( + "color", "A wonderful common color.", TextColor.CODEC, ByteBufCodecs.INT.map(TextColor::fromRgb, TextColor::getValue), @@ -202,7 +203,6 @@ public class GreenhouseConfigTest { ColorConfig.class, COLOR_CONFIG_BUILDER, 1, - GreenhouseConfigJsonCLang.INSTANCE, GreenhouseConfigSide.DEDICATED, builder -> builder .withValue("dedicated_color", @@ -216,13 +216,26 @@ public class GreenhouseConfigTest { public static GreenhouseConfigHolder RAW_CODEC_CONFIG = GreenhouseConfigHolder.register( RecordCodecBuilderConfig.class, GreenhouseConfigTest.MOD_ID + "_raw_codec", - 2, + 1, GreenhouseConfigJsonCLang.INSTANCE, GreenhouseConfigSide.COMMON, configBuilder -> - configBuilder.direct(RecordCodecBuilderConfig.CODEC), - fixerBuilder -> { - } + configBuilder.direct(RecordCodecBuilderConfig.CODEC) + ); + public static GreenhouseConfigHolderBuilder UNUSED_BUILDER = GreenhouseConfigHolder.builder( + ColorConfig.class, + MOD_ID + "_client_raw_codec", + 1, + GreenhouseConfigJsonLang.INSTANCE, + configBuilder -> configBuilder + .withNetworkSynchronizedValue( + "color", + "A wonderful color.", + TextColor.CODEC, + ByteBufCodecs.INT.map(TextColor::fromRgb, TextColor::getValue), + ColorConfig.COMMON_DEFAULT, + ColorConfig::color + ) ); public static void init() { diff --git a/xplat/src/test/java/lgbt/greenhouse/config/test/client/GreenhouseConfigTestClient.java b/xplat/src/test/java/lgbt/greenhouse/config/test/client/GreenhouseConfigTestClient.java index 49cbbda..4b11c29 100644 --- a/xplat/src/test/java/lgbt/greenhouse/config/test/client/GreenhouseConfigTestClient.java +++ b/xplat/src/test/java/lgbt/greenhouse/config/test/client/GreenhouseConfigTestClient.java @@ -21,6 +21,7 @@ import lgbt.greenhouse.config.test.client.config.GetterSetterConfig; import lgbt.greenhouse.config.test.client.config.SplitClientConfig; import lgbt.greenhouse.config.test.config.ColorConfig; import lgbt.greenhouse.config.test.config.CommonConfig; +import lgbt.greenhouse.config.test.config.RecordCodecBuilderConfig; import lgbt.greenhouse.config.test.config.SplitConfig; import net.minecraft.network.chat.TextColor; import org.apache.commons.lang3.mutable.MutableBoolean; @@ -34,8 +35,11 @@ public class GreenhouseConfigTestClient { public static boolean catNoisesEnabled = false; public static boolean getterSetterEnabled = false; - public static GreenhouseInheritedConfigHolder COLOR_CONFIG = GreenhouseInheritedConfigHolder.register(ColorConfig.class, - GreenhouseConfigTest.COLOR_CONFIG_BUILDER, 1, GreenhouseConfigJsonCLang.INSTANCE, GreenhouseConfigSide.CLIENT, + public static GreenhouseInheritedConfigHolder COLOR_CONFIG = GreenhouseInheritedConfigHolder.register( + ColorConfig.class, + GreenhouseConfigTest.COLOR_CONFIG_BUILDER, + 1, + GreenhouseConfigSide.CLIENT, configBuilder -> configBuilder .withValue( "client_color", @@ -48,8 +52,11 @@ public class GreenhouseConfigTestClient { fixerBuilder.withPreviousLang(0, GreenhouseConfigJsonLang.INSTANCE) ); - public static GreenhouseInheritedConfigHolder SPLIT_CONFIG = GreenhouseInheritedConfigHolder.register(SplitClientConfig.class, - GreenhouseConfigTest.SPLIT_CONFIG, 2, GreenhouseConfigJsonCLang.INSTANCE, GreenhouseConfigSide.CLIENT, + public static GreenhouseInheritedConfigHolder SPLIT_CONFIG = GreenhouseInheritedConfigHolder.register( + SplitClientConfig.class, + GreenhouseConfigTest.SPLIT_CONFIG, + 2, + GreenhouseConfigSide.CLIENT, configBuilder -> configBuilder .withValue( "play_cow_sounds_constantly", @@ -108,6 +115,30 @@ public class GreenhouseConfigTestClient { ) ); + public static GreenhouseInheritedConfigHolder INHERITED_RAW_BUILDER_CONFIG = GreenhouseInheritedConfigHolder.register( + ColorConfig.class, + GreenhouseConfigTest.RAW_CODEC_CONFIG, + 1, + GreenhouseConfigSide.CLIENT, + configBuilder -> + configBuilder.withValue( + "an_extra_color", + "A wonderful extra color.", + TextColor.CODEC, + ColorConfig.CLIENT_DEFAULT, + ColorConfig::color + ) + ); + + public static GreenhouseInheritedConfigHolder EXTRA_RAW_CONFIG = GreenhouseInheritedConfigHolder.register( + RecordCodecBuilderConfig.Cirno.class, + GreenhouseConfigTest.UNUSED_BUILDER, + 1, + GreenhouseConfigSide.CLIENT, + configBuilder -> configBuilder + .direct(RecordCodecBuilderConfig.Cirno.CODEC) + ); + public static void init() { GreenhouseConfigEvents.onRegistryPopulated(CONFIG, (holder, registries) -> onClientJoined(holder.get())); diff --git a/xplat/src/test/java/lgbt/greenhouse/config/test/config/SplitConfig.java b/xplat/src/test/java/lgbt/greenhouse/config/test/config/SplitConfig.java index 755b2f3..1e94d33 100644 --- a/xplat/src/test/java/lgbt/greenhouse/config/test/config/SplitConfig.java +++ b/xplat/src/test/java/lgbt/greenhouse/config/test/config/SplitConfig.java @@ -2,7 +2,9 @@ package lgbt.greenhouse.config.test.config; import net.minecraft.world.item.ItemStack; -public record SplitConfig(double volumeOfSylvsFan, Alphabet alphabet, ItemStack vanillaItemStack, +public record SplitConfig(double volumeOfSylvsFan, + Alphabet alphabet, + ItemStack vanillaItemStack, ItemStack moddedItemStack) { public record Alphabet(String a, String b, String c, String d, String e, String f, String h) { -- 2.51.2