diff --git a/src/main/java/net/minecraftforge/registries/GameData.java b/src/main/java/net/minecraftforge/registries/GameData.java index d0ef606f7d..840af7cad6 100644 --- a/src/main/java/net/minecraftforge/registries/GameData.java +++ b/src/main/java/net/minecraftforge/registries/GameData.java @@ -53,7 +53,6 @@ import net.minecraft.world.level.levelgen.DebugLevelSource; import net.minecraft.world.level.material.Fluid; import net.minecraftforge.common.CreativeModeTabRegistry; import net.minecraftforge.common.ForgeHooks; -import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.loot.IGlobalLootModifier; import net.minecraftforge.common.util.LogMessageAdapter; import net.minecraftforge.fluids.FluidType; @@ -313,6 +312,7 @@ public class GameData { // the id mapping has reverted, fire remap events for those that care about id changes if (fireEvents) { fireRemapEvent(ImmutableMap.of(), true); + ObjectHolderRegistry.applyObjectHolders(); } // the id mapping has reverted, ensure we sync up the object holders @@ -350,6 +350,9 @@ public class GameData { if (forgeRegistry != null) forgeRegistry.freeze(); + LOGGER.debug(REGISTRIES, "Applying holder lookups: {}", registryKey.location()); + ObjectHolderRegistry.applyObjectHolders(registryKey.location()::equals); + LOGGER.debug(REGISTRIES, "Holder lookups applied: {}", registryKey.location()); } catch (Throwable t) { aggregate.addSuppressed(t); } @@ -688,6 +691,9 @@ public class GameData { // Tell mods that the ids have changed fireRemapEvent(remaps, false); + // The id map changed, ensure we apply object holders + ObjectHolderRegistry.applyObjectHolders(); + // Return an empty list, because we're good return ArrayListMultimap.create(); } diff --git a/src/main/java/net/minecraftforge/registries/ObjectHolderRegistry.java b/src/main/java/net/minecraftforge/registries/ObjectHolderRegistry.java new file mode 100644 index 0000000000..02a35a1f85 --- /dev/null +++ b/src/main/java/net/minecraftforge/registries/ObjectHolderRegistry.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.registries; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import net.minecraft.resources.ResourceLocation; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.ApiStatus; + +/** + * Internal registry for tracking {@link ObjectHolder} references + */ +@ApiStatus.Internal +class ObjectHolderRegistry { + /** + * Exposed to allow modders to register their own notification handlers. + * This runnable will be called after a registry snapshot has been injected and finalized. + * The internal list is backed by a HashSet so it is HIGHLY recommended you implement a proper equals + * and hashCode function to de-duplicate callers here. + * The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on. + */ + static synchronized void addHandler(Consumer> ref) { + objectHolders.add(ref); + } + + /** + * Removed the specified handler from the notification list. + * + * The internal list is backed by a hash set, and so proper hashCode and equals operations are required for success. + * + * The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on. + * + * @return true if handler was matched and removed. + */ + static synchronized boolean removeHandler(Consumer> ref) { + return objectHolders.remove(ref); + } + + //============================================================== + // Everything below is internal, do not use. + //============================================================== + + private static final Logger LOGGER = LogManager.getLogger(); + private static final Set>> objectHolders = new HashSet<>(); + + static void applyObjectHolders() { + try { + LOGGER.debug(ForgeRegistry.REGISTRIES, "Applying holder lookups"); + applyObjectHolders(key -> true); + LOGGER.debug(ForgeRegistry.REGISTRIES, "Holder lookups applied"); + } catch (RuntimeException e) { + // It is more important that the calling contexts continue without exception to prevent further cascading errors + LOGGER.error("", e); + } + } + + static void applyObjectHolders(Predicate filter) { + RuntimeException aggregate = new RuntimeException("Failed to apply some object holders, see suppressed exceptions for details"); + for (Consumer> objectHolder : objectHolders) { + try { + objectHolder.accept(filter); + } catch (Exception e) { + aggregate.addSuppressed(e); + } + } + + if (aggregate.getSuppressed().length > 0) + throw aggregate; + } +} diff --git a/src/main/java/net/minecraftforge/registries/RegistryObject.java b/src/main/java/net/minecraftforge/registries/RegistryObject.java index a57fc9779a..a2af037b85 100644 --- a/src/main/java/net/minecraftforge/registries/RegistryObject.java +++ b/src/main/java/net/minecraftforge/registries/RegistryObject.java @@ -139,6 +139,10 @@ public final class RegistryObject implements Supplier { this.name = name; this.key = (ResourceKey) ResourceKey.create(registry.getRegistryKey(), name); this.optionalRegistry = false; + ObjectHolderRegistry.addHandler(pred -> { + if (pred.test(registry.getRegistryName())) + this.updateReference((IForgeRegistry) registry); + }); this.updateReference((IForgeRegistry) registry); } @@ -146,6 +150,28 @@ public final class RegistryObject implements Supplier { this.name = name; this.key = ResourceKey.create(ResourceKey.createRegistryKey(registryName), name); this.optionalRegistry = optionalRegistry; + final Throwable callerStack = new Throwable("Calling Site from mod: " + modid); + ObjectHolderRegistry.addHandler(new Consumer<>() { + private boolean registryExists = false; + private boolean invalidRegistry = false; + + @Override + public void accept(Predicate pred) { + if (invalidRegistry) + return; + + if (!RegistryObject.this.optionalRegistry && !registryExists) { + if (!registryExists(registryName)) { + invalidRegistry = true; + throw new IllegalStateException("Unable to find registry with key " + registryName + " for mod \"" + modid + "\". Check the 'caused by' to see further stack.", callerStack); + } + registryExists = true; + } + + if (pred.test(registryName)) + RegistryObject.this.updateReference(registryName); + } + }); this.updateReference(registryName); }