From 427fef1d4bcaf5946bdaa0104da2df9173fbcda3 Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 14 Nov 2024 14:23:00 -0800 Subject: [PATCH] Added support for using optional tags on Forge registries Add DeferredRegister.key(name) helper function to make ResourceKey creation easier Fix TagManager not binding tags Fix Unbound Tag Exception when modders register tags during Register events Closes #10154 --- .../registries/DeferredRegister.java | 78 ++++++++++++------- .../registries/ForgeRegistry.java | 4 +- .../registries/ForgeRegistryTagManager.java | 39 ++++------ .../minecraftforge/registries/GameData.java | 29 ++++--- .../registries/NamespacedWrapper.java | 27 +++++++ .../registries/RegisterEvent.java | 41 ++++------ .../registries/tags/ITagManager.java | 3 +- 7 files changed, 123 insertions(+), 98 deletions(-) diff --git a/src/main/java/net/minecraftforge/registries/DeferredRegister.java b/src/main/java/net/minecraftforge/registries/DeferredRegister.java index dcb6b1ab9a..bd274749de 100644 --- a/src/main/java/net/minecraftforge/registries/DeferredRegister.java +++ b/src/main/java/net/minecraftforge/registries/DeferredRegister.java @@ -25,7 +25,6 @@ import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.function.Supplier; @@ -161,6 +160,7 @@ public class DeferredRegister { private boolean seenRegisterEvent = false; private DeferredRegister(ResourceKey> registryKey, String modid, boolean optionalRegistry) { + requireNonNull("registryKey", registryKey); this.registryKey = registryKey; this.modid = modid; this.optionalRegistry = optionalRegistry; @@ -174,28 +174,25 @@ public class DeferredRegister { * Adds a new supplier to the list of entries to be registered, and returns a RegistryObject that will be populated with the created entry automatically. * * @param name The new entry's name, it will automatically have the modid prefixed. - * @param sup A factory for the new entry, it should return a new instance every time it is called. + * @param factory A factory for the new entry, it should return a new instance every time it is called. * @return A RegistryObject that will be updated with when the entries in the registry change. */ @SuppressWarnings("unchecked") - public RegistryObject register(final String name, final Supplier sup) { + public RegistryObject register(final String name, final Supplier factory) { if (seenRegisterEvent) throw new IllegalStateException("Cannot register new entries to DeferredRegister after RegisterEvent has been fired."); - Objects.requireNonNull(name); - Objects.requireNonNull(sup); + requireNonNull("name", name); + requireNonNull("factory", factory); var key = ResourceLocation.fromNamespaceAndPath(modid, name); RegistryObject ret; - if (this.registryKey != null) { - if (this.optionalRegistry) - ret = RegistryObject.createOptional(key, this.registryKey, this.modid); - else - ret = RegistryObject.create(key, this.registryKey, this.modid); - } else - throw new IllegalStateException("Could not create RegistryObject in DeferredRegister"); + if (this.optionalRegistry) + ret = RegistryObject.createOptional(key, this.registryKey, this.modid); + else + ret = RegistryObject.create(key, this.registryKey, this.modid); - if (entries.putIfAbsent((RegistryObject) ret, sup) != null) + if (entries.putIfAbsent((RegistryObject) ret, factory) != null) throw new IllegalArgumentException("Duplicate registration " + name); return ret; @@ -214,18 +211,40 @@ public class DeferredRegister { return makeRegistry(this.registryKey.location(), sup); } + /** + * Creates a ResourceKey based on the current modid and provided path as the location and the registry name linked to this DeferredRegister. + * To control the namespace, use {@link #key(ResourceLocation)}. + * + * @see #key(ResourceLocation) + */ + @NotNull + public ResourceKey key(@NotNull String path) { + requireNonNull("path", path); + return key(ResourceLocation.fromNamespaceAndPath(this.modid, path)); + } + + /** + * Creates a tag key based on the provided resource location and the registry name linked to this DeferredRegister. + * To use the current modid as the namespace, use {@link #createTagKey(String)}. + * + * @see #key(String) + */ + @NotNull + public ResourceKey key(@NotNull ResourceLocation location) { + requireNonNull("location", location); + return ResourceKey.create(getRegistryKey(), location); + } + /** * Creates a tag key based on the current modid and provided path as the location and the registry name linked to this DeferredRegister. * To control the namespace, use {@link #createTagKey(ResourceLocation)}. * - * @throws IllegalStateException If the registry name was not set. - * Use the factories that take {@link #create(ResourceLocation, String) a registry name} or {@link #create(IForgeRegistry, String) forge registry}. * @see #createTagKey(ResourceLocation) * @see #createOptionalTagKey(String, Set) */ @NotNull public TagKey createTagKey(@NotNull String path) { - Objects.requireNonNull(path); + requireNonNull("path", path); return createTagKey(ResourceLocation.fromNamespaceAndPath(this.modid, path)); } @@ -233,16 +252,12 @@ public class DeferredRegister { * Creates a tag key based on the provided resource location and the registry name linked to this DeferredRegister. * To use the current modid as the namespace, use {@link #createTagKey(String)}. * - * @throws IllegalStateException If the registry name was not set. - * Use the factories that take {@link #create(ResourceLocation, String) a registry name} or {@link #create(IForgeRegistry, String) forge registry}. * @see #createTagKey(String) * @see #createOptionalTagKey(ResourceLocation, Set) */ @NotNull public TagKey createTagKey(@NotNull ResourceLocation location) { - if (this.registryKey == null) - throw new IllegalStateException("The registry name was not set, cannot create a tag key"); - Objects.requireNonNull(location); + requireNonNull("location", location); return TagKey.create(this.registryKey, location); } @@ -251,8 +266,6 @@ public class DeferredRegister { * Useful on the client side when a server may not provide a specific tag. * To control the namespace, use {@link #createOptionalTagKey(ResourceLocation, Set)}. * - * @throws IllegalStateException If the registry name was not set. - * Use the factories that take {@link #create(ResourceLocation, String) a registry name} or {@link #create(IForgeRegistry, String) forge registry}. * @see #createTagKey(String) * @see #createTagKey(ResourceLocation) * @see #createOptionalTagKey(ResourceLocation, Set) @@ -260,7 +273,7 @@ public class DeferredRegister { */ @NotNull public TagKey createOptionalTagKey(@NotNull String path, @NotNull Set> defaults) { - Objects.requireNonNull(path); + requireNonNull("path", path); return createOptionalTagKey(ResourceLocation.fromNamespaceAndPath(this.modid, path), defaults); } @@ -269,8 +282,6 @@ public class DeferredRegister { * Useful on the client side when a server may not provide a specific tag. * To use the current modid as the namespace, use {@link #createOptionalTagKey(String, Set)}. * - * @throws IllegalStateException If the registry name was not set. - * Use the factories that take {@link #create(ResourceLocation, String) a registry name} or {@link #create(IForgeRegistry, String) forge registry}. * @see #createTagKey(String) * @see #createTagKey(ResourceLocation) * @see #createOptionalTagKey(String, Set) @@ -290,13 +301,11 @@ public class DeferredRegister { * The set of defaults will be bound to the tag if the tag is not loaded from any datapacks. * Useful on the client side when a server may not provide a specific tag. * - * @throws IllegalStateException If the registry name was not set. - * Use the factories that take {@link #create(ResourceLocation, String) a registry name} or {@link #create(IForgeRegistry, String) forge registry}. * @see #createOptionalTagKey(String, Set) * @see #createOptionalTagKey(ResourceLocation, Set) */ public void addOptionalTagDefaults(@NotNull TagKey name, @NotNull Set> defaults) { - Objects.requireNonNull(defaults); + requireNonNull("defaults", defaults); if (optionalTags == null) optionalTags = Multimaps.newSetMultimap(new IdentityHashMap<>(), HashSet::new); @@ -332,7 +341,7 @@ public class DeferredRegister { */ @NotNull public ResourceLocation getRegistryName() { - return Objects.requireNonNull(this.registryKey).location(); + return this.registryKey.location(); } private RegistryHolder makeRegistry(final ResourceLocation registryName, final Supplier> sup) { @@ -357,6 +366,12 @@ public class DeferredRegister { Multimaps.asMap(this.optionalTags).forEach(tagManager::addOptionalTagDefaults); } + private static T requireNonNull(String name, T value) { + if (value == null) + throw new IllegalArgumentException(name + " can not be null"); + return value; + } + private class EventDispatcher { @SubscribeEvent public void handleEvent(RegisterEvent event) { @@ -366,6 +381,9 @@ public class DeferredRegister { event.register(registryKey, e.getKey().getId(), () -> e.getValue().get()); e.getKey().updateReference(event); } + + if (registryFactory == null && event.getForgeRegistry() != null) + onFill(event.getForgeRegistry()); } } diff --git a/src/main/java/net/minecraftforge/registries/ForgeRegistry.java b/src/main/java/net/minecraftforge/registries/ForgeRegistry.java index fe3e1cdcc4..74e672ee8c 100644 --- a/src/main/java/net/minecraftforge/registries/ForgeRegistry.java +++ b/src/main/java/net/minecraftforge/registries/ForgeRegistry.java @@ -244,9 +244,9 @@ public class ForgeRegistry implements IForgeRegistryInternal, IForgeRegist return wrapper; } - void onBindTags(Map, HolderSet.Named> tags, Set> defaultedTags) { + void onBindTags(Map, HolderSet.Named> tags) { if (this.tagManager != null) - this.tagManager.bind(tags, defaultedTags); + this.tagManager.bind(tags); } @NotNull diff --git a/src/main/java/net/minecraftforge/registries/ForgeRegistryTagManager.java b/src/main/java/net/minecraftforge/registries/ForgeRegistryTagManager.java index dabc20f37c..80b2a7103b 100644 --- a/src/main/java/net/minecraftforge/registries/ForgeRegistryTagManager.java +++ b/src/main/java/net/minecraftforge/registries/ForgeRegistryTagManager.java @@ -23,18 +23,15 @@ import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; -class ForgeRegistryTagManager implements ITagManager -{ +class ForgeRegistryTagManager implements ITagManager { private final ForgeRegistry owner; private volatile Map, ITag> tags = new IdentityHashMap<>(); - ForgeRegistryTagManager(ForgeRegistry owner) - { + ForgeRegistryTagManager(ForgeRegistry owner) { this.owner = owner; } - void bind(Map, HolderSet.Named> holderTags, Set> defaultedTags) - { + void bind(Map, HolderSet.Named> holderTags) { IdentityHashMap, ITag> newTags = new IdentityHashMap<>(this.tags); // Forcefully unbind all pre-existing tags @@ -49,13 +46,11 @@ class ForgeRegistryTagManager implements ITagManager @NotNull @Override - public ITag getTag(@NotNull TagKey name) - { + public ITag getTag(@NotNull TagKey name) { Objects.requireNonNull(name); ITag tag = this.tags.get(name); - if (tag == null) - { + if (tag == null) { // Create empty tag tag = new ForgeRegistryTag<>(name); @@ -72,16 +67,14 @@ class ForgeRegistryTagManager implements ITagManager @SuppressWarnings({ "unchecked", "rawtypes" }) @NotNull @Override - public Optional> getReverseTag(@NotNull V value) - { + public Optional> getReverseTag(@NotNull V value) { Objects.requireNonNull(value); // All Holders are implementors of IReverseTag return (Optional>) (Optional) this.owner.getHolder(value); } @Override - public boolean isKnownTagName(@NotNull TagKey name) - { + public boolean isKnownTagName(@NotNull TagKey name) { Objects.requireNonNull(name); ITag tag = this.tags.get(name); return tag != null && tag.isBound(); @@ -89,37 +82,32 @@ class ForgeRegistryTagManager implements ITagManager @NotNull @Override - public Iterator> iterator() - { + public Iterator> iterator() { return Iterators.unmodifiableIterator(this.tags.values().iterator()); } @NotNull @Override - public Stream> stream() - { + public Stream> stream() { return this.tags.values().stream(); } @NotNull @Override - public Stream> getTagNames() - { + public Stream> getTagNames() { return this.tags.keySet().stream(); } @NotNull @Override - public TagKey createTagKey(@NotNull ResourceLocation location) - { + public TagKey createTagKey(@NotNull ResourceLocation location) { Objects.requireNonNull(location); return TagKey.create(this.owner.getRegistryKey(), location); } @NotNull @Override - public TagKey createOptionalTagKey(@NotNull ResourceLocation location, @NotNull Set> defaults) - { + public TagKey createOptionalTagKey(@NotNull ResourceLocation location, @NotNull Set> defaults) { TagKey tagKey = createTagKey(location); addOptionalTagDefaults(tagKey, defaults); @@ -128,8 +116,7 @@ class ForgeRegistryTagManager implements ITagManager } @Override - public void addOptionalTagDefaults(@NotNull TagKey name, @NotNull Set> defaults) - { + public void addOptionalTagDefaults(@NotNull TagKey name, @NotNull Set> defaults) { Objects.requireNonNull(name); Objects.requireNonNull(defaults); diff --git a/src/main/java/net/minecraftforge/registries/GameData.java b/src/main/java/net/minecraftforge/registries/GameData.java index 3a0ccc56e5..ee67bb60ba 100644 --- a/src/main/java/net/minecraftforge/registries/GameData.java +++ b/src/main/java/net/minecraftforge/registries/GameData.java @@ -247,26 +247,32 @@ public class GameData { } @SuppressWarnings("deprecation") - public static void unfreezeData() - { + public static void unfreezeData() { LOGGER.debug(REGISTRIES, "Unfreezing vanilla registries"); BuiltInRegistries.REGISTRY.stream().filter(r -> r instanceof MappedRegistry).forEach(r -> ((MappedRegistry)r).unfreeze()); } - public static void freezeData() - { + public static void freezeData() { LOGGER.debug(REGISTRIES, "Freezing registries"); - BuiltInRegistries.REGISTRY.stream().filter(r -> r instanceof MappedRegistry).forEach(r -> ((MappedRegistry)r).freeze()); - - for (Map.Entry> r : RegistryManager.ACTIVE.registries.entrySet()) - { - loadRegistry(r.getKey(), RegistryManager.ACTIVE, RegistryManager.FROZEN, true); + for (var reg : BuiltInRegistries.REGISTRY) { + if (reg instanceof NamespacedWrapper named) { + // This is called after we fire our register events, but before doesn't load any tags. + // So lets skip the validation for this pass and just make every unbound tag empty + // Tags will be bound later when tag data is reloaded/synced + named.bindAllUnboundTagsToEmpty(); + named.freeze(); + } else if (reg instanceof MappedRegistry maped) + maped.freeze(); } - RegistryManager.FROZEN.registries.forEach((name, reg) -> - { + + for (var r : RegistryManager.ACTIVE.registries.entrySet()) + loadRegistry(r.getKey(), RegistryManager.ACTIVE, RegistryManager.FROZEN, true); + + RegistryManager.FROZEN.registries.forEach((name, reg) -> { reg.validateContent(name); reg.freeze(); }); + RegistryManager.ACTIVE.registries.forEach((name, reg) -> { reg.freeze(); reg.bake(); @@ -764,6 +770,7 @@ public class GameData { int index = name.lastIndexOf(':'); String oldPrefix = index == -1 ? "" : name.substring(0, index).toLowerCase(Locale.ROOT); name = index == -1 ? name : name.substring(index + 1); + @SuppressWarnings("removal") String prefix = ModLoadingContext.get().getActiveNamespace(); if (warnOverrides && !oldPrefix.equals(prefix) && !oldPrefix.isEmpty()) { diff --git a/src/main/java/net/minecraftforge/registries/NamespacedWrapper.java b/src/main/java/net/minecraftforge/registries/NamespacedWrapper.java index e64330caf6..ee7afcfaad 100644 --- a/src/main/java/net/minecraftforge/registries/NamespacedWrapper.java +++ b/src/main/java/net/minecraftforge/registries/NamespacedWrapper.java @@ -318,6 +318,7 @@ class NamespacedWrapper extends MappedRegistry implements ILockableRegistr throw new IllegalStateException("Unbound tags in registry " + this.key() + ": " + unbound); this.frozenTags = MappedRegistry.TagSet.fromMap(this.tags); + this.delegate.onBindTags(this.tags); this.refreshTagsInHoldersForge(); return this; } @@ -378,6 +379,14 @@ class NamespacedWrapper extends MappedRegistry implements ILockableRegistr tag.bind(List.of()); } + void bindAllUnboundTagsToEmpty() { + this.validateWrite(); + for (var tag : this.tags.values()) { + if (!tag.isBound()) + tag.bind(List.of()); + } + } + @Override public Registry.PendingTags prepareTagReload(TagLoader.LoadResult data) { if (!this.frozen) @@ -395,6 +404,23 @@ class NamespacedWrapper extends MappedRegistry implements ILockableRegistr _new.put(key, List.copyOf(entry.getValue())); } + for (var entry : this.optionalTags.entries()) { + var key = entry.getKey(); + if (data.tags().containsKey(key)) + continue; + + var value = entry.getValue().get(); + var holder = this.getHolder(value).orElse(null); + if (holder == null) + continue; + + var existing = this.tags.get(key); + if (existing == null) + existing = this.createTag(key); + _old.put(key, existing); + _new.put(key, List.of(holder)); + } + var oldBindings = _old.build(); var newBindings = _new.build(); @@ -444,6 +470,7 @@ class NamespacedWrapper extends MappedRegistry implements ILockableRegistr } NamespacedWrapper.this.frozenTags = MappedRegistry.TagSet.fromMap(oldBindings); + NamespacedWrapper.this.delegate.onBindTags(NamespacedWrapper.this.tags); NamespacedWrapper.this.refreshTagsInHoldersForge(); } }; diff --git a/src/main/java/net/minecraftforge/registries/RegisterEvent.java b/src/main/java/net/minecraftforge/registries/RegisterEvent.java index 120f26a648..1b6c79a1c6 100644 --- a/src/main/java/net/minecraftforge/registries/RegisterEvent.java +++ b/src/main/java/net/minecraftforge/registries/RegisterEvent.java @@ -25,17 +25,15 @@ import java.util.function.Supplier; * @see #register(ResourceKey, ResourceLocation, Supplier) * @see #register(ResourceKey, Consumer) */ -public class RegisterEvent extends Event implements IModBusEvent -{ +public class RegisterEvent extends Event implements IModBusEvent { @NotNull private final ResourceKey> registryKey; @Nullable - final ForgeRegistry forgeRegistry; + private final ForgeRegistry forgeRegistry; @Nullable private final Registry vanillaRegistry; - RegisterEvent(@NotNull ResourceKey> registryKey, @Nullable ForgeRegistry forgeRegistry, @Nullable Registry vanillaRegistry) - { + RegisterEvent(@NotNull ResourceKey> registryKey, @Nullable ForgeRegistry forgeRegistry, @Nullable Registry vanillaRegistry) { this.registryKey = registryKey; this.forgeRegistry = forgeRegistry; this.vanillaRegistry = vanillaRegistry; @@ -51,10 +49,8 @@ public class RegisterEvent extends Event implements IModBusEvent * @see #register(ResourceKey, Consumer) a register variant making registration of multiple objects less redundant */ @SuppressWarnings({ "unchecked", "rawtypes" }) - public void register(ResourceKey> registryKey, ResourceLocation name, Supplier valueSupplier) - { - if (this.registryKey.equals(registryKey)) - { + public void register(ResourceKey> registryKey, ResourceLocation name, Supplier valueSupplier) { + if (this.registryKey.equals(registryKey)) { if (this.forgeRegistry != null) ((IForgeRegistry) this.forgeRegistry).register(name, valueSupplier.get()); else if (this.vanillaRegistry != null) @@ -69,20 +65,16 @@ public class RegisterEvent extends Event implements IModBusEvent * @param the type of the registry * @see #register(ResourceKey, ResourceLocation, Supplier) a register variant targeted towards registering one or two objects */ - public void register(ResourceKey> registryKey, Consumer> consumer) - { + public void register(ResourceKey> registryKey, Consumer> consumer) { if (this.registryKey.equals(registryKey)) - { consumer.accept((name, value) -> register(registryKey, name, () -> value)); - } } /** * @return The registry key linked to this event */ @NotNull - public ResourceKey> getRegistryKey() - { + public ResourceKey> getRegistryKey() { return registryKey; } @@ -91,8 +83,7 @@ public class RegisterEvent extends Event implements IModBusEvent */ @Nullable @SuppressWarnings("unchecked") - public IForgeRegistry getForgeRegistry() - { + public IForgeRegistry getForgeRegistry() { return (IForgeRegistry) forgeRegistry; } @@ -101,20 +92,17 @@ public class RegisterEvent extends Event implements IModBusEvent */ @Nullable @SuppressWarnings("unchecked") - public Registry getVanillaRegistry() - { + public Registry getVanillaRegistry() { return (Registry) vanillaRegistry; } @Override - public String toString() - { + public String toString() { return "RegisterEvent"; } @FunctionalInterface - public interface RegisterHelper - { + public interface RegisterHelper { /** * Registers the given value with the given name to the registry. * The namespace is inferred based on the active mod container. @@ -123,8 +111,8 @@ public class RegisterEvent extends Event implements IModBusEvent * @param name the name of the object to register as its key with the namespaced inferred from the active mod container * @param value the object value */ - default void register(String name, T value) - { + @SuppressWarnings("removal") + default void register(String name, T value) { register(ResourceLocation.fromNamespaceAndPath(ModLoadingContext.get().getActiveNamespace(), name), value); } @@ -134,8 +122,7 @@ public class RegisterEvent extends Event implements IModBusEvent * @param key the resource key of the object to register * @param value the object value */ - default void register(ResourceKey key, T value) - { + default void register(ResourceKey key, T value) { register(key.location(), value); } diff --git a/src/main/java/net/minecraftforge/registries/tags/ITagManager.java b/src/main/java/net/minecraftforge/registries/tags/ITagManager.java index 1d9a9a9b1b..7f72b58254 100644 --- a/src/main/java/net/minecraftforge/registries/tags/ITagManager.java +++ b/src/main/java/net/minecraftforge/registries/tags/ITagManager.java @@ -20,8 +20,7 @@ import java.util.stream.Stream; * A tag manager holds information about all tags currently bound to a forge registry. * This should be preferred to any {@link Holder}-related methods. */ -public interface ITagManager extends Iterable> -{ +public interface ITagManager extends Iterable> { /** * Queries this tag manager for a tag with the given tag key. * If it does not exist, this will create an empty tag and return it.