From 2ff7efd68fcb0aaf59a208af2e95c13908d2289e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 17:48:41 +0000 Subject: [PATCH] Fix DeferredHolderCommon delegates not being references on Forge Registry#safeCastToReference only accepts Holder.Reference, which is why DeferredHolderCommon exposes its bound holder as delegate. On Forge, the IForgeRegistry bind override ran before the regular registry lookup and bound to Holder.direct, so the delegate was never a reference and serialization kept failing there. The regular registry is now consulted first, and the Forge override prefers the registry's own delegate. Covered by game tests on all loaders. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WyxzX1DGvXj5nV4zew3V8u --- .../config/DeferredHolderCommon.java | 19 +++++----- .../gametest/DeferredHolderCommonTest.java | 36 +++++++++++++++++++ .../cyclopscore/CyclopsCoreFabric.java | 3 +- .../cyclops/cyclopscore/CyclopsCoreForge.java | 10 +++++- .../cyclopscore/CyclopsCoreNeoForge.java | 3 +- 5 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 loader-common/src/main/java/org/cyclops/cyclopscore/gametest/DeferredHolderCommonTest.java diff --git a/loader-common/src/main/java/org/cyclops/cyclopscore/config/DeferredHolderCommon.java b/loader-common/src/main/java/org/cyclops/cyclopscore/config/DeferredHolderCommon.java index a493287fd53..7f11a9f0f73 100644 --- a/loader-common/src/main/java/org/cyclops/cyclopscore/config/DeferredHolderCommon.java +++ b/loader-common/src/main/java/org/cyclops/cyclopscore/config/DeferredHolderCommon.java @@ -144,18 +144,19 @@ protected Registry getRegistry() { protected final void bind(boolean throwOnMissingRegistry) { if (this.holder != null) return; - // Check if we have an override for specific mod loaders - if (DeferredHolderCommon.BIND_OVERRIDE != null) { - this.holder = (Holder) DeferredHolderCommon.BIND_OVERRIDE.apply(this.key); - if (this.holder != null) { - return; - } - } - + // Bind via the registry first, as that gives us a Holder.Reference, which is required by delegates. Registry registry = getRegistry(); if (registry != null) { this.holder = registry.get(this.key).orElse(null); - } else if (throwOnMissingRegistry) { + } + + // Check if we have an override for specific mod loaders, + // for entries that are not present in a regular registry. + if (this.holder == null && DeferredHolderCommon.BIND_OVERRIDE != null) { + this.holder = (Holder) DeferredHolderCommon.BIND_OVERRIDE.apply(this.key); + } + + if (this.holder == null && registry == null && throwOnMissingRegistry) { throw new IllegalStateException("Registry not present for " + this + ": " + this.key.registry()); } } diff --git a/loader-common/src/main/java/org/cyclops/cyclopscore/gametest/DeferredHolderCommonTest.java b/loader-common/src/main/java/org/cyclops/cyclopscore/gametest/DeferredHolderCommonTest.java new file mode 100644 index 00000000000..19fb2a4353f --- /dev/null +++ b/loader-common/src/main/java/org/cyclops/cyclopscore/gametest/DeferredHolderCommonTest.java @@ -0,0 +1,36 @@ +package org.cyclops.cyclopscore.gametest; + +import com.google.gson.JsonElement; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import net.minecraft.core.Holder; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.gametest.framework.GameTestHelper; +import org.cyclops.cyclopscore.RegistryEntriesCommon; +import org.cyclops.cyclopscore.config.DeferredHolderCommon; + +/** + * Tests for {@link DeferredHolderCommon}. + * @author rubensworks + */ +public class DeferredHolderCommonTest { + + // Registry#safeCastToReference only accepts Holder.Reference, so delegates must resolve to one. + @GameTest + public void testDelegateIsReference(GameTestHelper helper) { + Holder> delegate = RegistryEntriesCommon.PARTICLE_BLUR.getDelegate(); + helper.assertTrue(delegate instanceof Holder.Reference, + "Expected a Holder.Reference delegate, but got " + delegate); + helper.succeed(); + } + + @GameTest + public void testHolderCodecEncoding(GameTestHelper helper) { + DataResult result = BuiltInRegistries.PARTICLE_TYPE.holderByNameCodec() + .encodeStart(JsonOps.INSTANCE, RegistryEntriesCommon.PARTICLE_BLUR); + helper.assertTrue(result.result().isPresent(), "Expected a successful encoding, but got " + result); + helper.succeed(); + } + +} diff --git a/loader-fabric/src/main/java/org/cyclops/cyclopscore/CyclopsCoreFabric.java b/loader-fabric/src/main/java/org/cyclops/cyclopscore/CyclopsCoreFabric.java index b92806fdcfb..dc0f8886ea3 100644 --- a/loader-fabric/src/main/java/org/cyclops/cyclopscore/CyclopsCoreFabric.java +++ b/loader-fabric/src/main/java/org/cyclops/cyclopscore/CyclopsCoreFabric.java @@ -17,6 +17,7 @@ import org.cyclops.cyclopscore.component.DataComponentCapacityConfig; import org.cyclops.cyclopscore.component.DataComponentEnergyStorageConfig; import org.cyclops.cyclopscore.config.ConfigHandlerCommon; +import org.cyclops.cyclopscore.gametest.DeferredHolderCommonTest; import org.cyclops.cyclopscore.gametest.MethodGameTestInstanceConfig; import org.cyclops.cyclopscore.gametest.StartupTestFabric; import org.cyclops.cyclopscore.helper.CyclopsCoreInstance; @@ -112,6 +113,6 @@ protected void onConfigsRegister(ConfigHandlerCommon configHandler) { @Override public Class[] getGameTestClasses() { - return new Class[]{ StartupTestFabric.class }; + return new Class[]{ StartupTestFabric.class, DeferredHolderCommonTest.class }; } } diff --git a/loader-forge/src/main/java/org/cyclops/cyclopscore/CyclopsCoreForge.java b/loader-forge/src/main/java/org/cyclops/cyclopscore/CyclopsCoreForge.java index 319be0b7fa7..98c99af3cc8 100644 --- a/loader-forge/src/main/java/org/cyclops/cyclopscore/CyclopsCoreForge.java +++ b/loader-forge/src/main/java/org/cyclops/cyclopscore/CyclopsCoreForge.java @@ -25,6 +25,7 @@ import org.cyclops.cyclopscore.config.ConfigHandlerCommon; import org.cyclops.cyclopscore.config.ConfigurableTypesForge; import org.cyclops.cyclopscore.config.DeferredHolderCommon; +import org.cyclops.cyclopscore.gametest.DeferredHolderCommonTest; import org.cyclops.cyclopscore.gametest.MethodGameTestInstanceConfig; import org.cyclops.cyclopscore.gametest.StartupTestForge; import org.cyclops.cyclopscore.helper.CyclopsCoreInstance; @@ -37,6 +38,8 @@ import org.cyclops.cyclopscore.proxy.ICommonProxyCommon; import org.cyclops.cyclopscore.tracking.ImportantUsers; +import java.util.Optional; + /** * The main mod class of CyclopsCore. * @author rubensworks @@ -62,6 +65,11 @@ public CyclopsCoreForge(FMLJavaModLoadingContext context) { DeferredHolderCommon.BIND_OVERRIDE = (key) -> { ForgeRegistry registry = RegistryManager.ACTIVE.getRegistry(key.registry()); if (registry != null) { + // Prefer the registry's delegate, as only Holder.Reference can be serialized. + Optional> delegate = registry.getDelegate(key.identifier()); + if (delegate.isPresent()) { + return delegate.get(); + } Object value = registry.getValue(key.identifier()); if (value != null) { return Holder.direct(value); @@ -141,6 +149,6 @@ protected void onConfigsRegister(ConfigHandlerCommon configHandler) { @Override public Class[] getGameTestClasses() { - return new Class[]{ StartupTestForge.class }; + return new Class[]{ StartupTestForge.class, DeferredHolderCommonTest.class }; } } diff --git a/loader-neoforge/src/main/java/org/cyclops/cyclopscore/CyclopsCoreNeoForge.java b/loader-neoforge/src/main/java/org/cyclops/cyclopscore/CyclopsCoreNeoForge.java index c588a30c4ac..b841c1bc0a3 100644 --- a/loader-neoforge/src/main/java/org/cyclops/cyclopscore/CyclopsCoreNeoForge.java +++ b/loader-neoforge/src/main/java/org/cyclops/cyclopscore/CyclopsCoreNeoForge.java @@ -22,6 +22,7 @@ import org.cyclops.cyclopscore.component.DataComponentFluidContentConfig; import org.cyclops.cyclopscore.component.DataComponentInventoryConfig; import org.cyclops.cyclopscore.config.ConfigHandlerCommon; +import org.cyclops.cyclopscore.gametest.DeferredHolderCommonTest; import org.cyclops.cyclopscore.gametest.MethodGameTestInstanceConfig; import org.cyclops.cyclopscore.gametest.StartupTestNeoForge; import org.cyclops.cyclopscore.helper.CyclopsCoreInstance; @@ -177,7 +178,7 @@ public void onConfigsRegister(ConfigHandlerCommon configHandler) { @Override public Class[] getGameTestClasses() { - return new Class[] { StartupTestNeoForge.class }; + return new Class[] { StartupTestNeoForge.class, DeferredHolderCommonTest.class }; } private void loadComplete(FMLLoadCompleteEvent event) {