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
This commit is contained in:
LexManos committed 2024-11-14 14:23:00 -08:00
1 parent 83594b0b0a
commit 427fef1d4b
7 files changed
+123 -98

No files matched your search

@@ -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<T> {
private boolean seenRegisterEvent = false;
private DeferredRegister(ResourceKey<? extends Registry<T>> registryKey, String modid, boolean optionalRegistry) {
requireNonNull("registryKey", registryKey);
this.registryKey = registryKey;
this.modid = modid;
this.optionalRegistry = optionalRegistry;
@@ -174,28 +174,25 @@ public class DeferredRegister<T> {
* 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 <I extends T> RegistryObject<I> register(final String name, final Supplier<? extends I> sup) {
public <I extends T> RegistryObject<I> register(final String name, final Supplier<? extends I> 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<I> 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<T>) ret, sup) != null)
if (entries.putIfAbsent((RegistryObject<T>) ret, factory) != null)
throw new IllegalArgumentException("Duplicate registration " + name);
return ret;
@@ -214,18 +211,40 @@ public class DeferredRegister<T> {
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<T> 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<T> 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<T> createTagKey(@NotNull String path) {
Objects.requireNonNull(path);
requireNonNull("path", path);
return createTagKey(ResourceLocation.fromNamespaceAndPath(this.modid, path));
}
@@ -233,16 +252,12 @@ public class DeferredRegister<T> {
* 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<T> 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<T> {
* 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<T> {
*/
@NotNull
public TagKey<T> createOptionalTagKey(@NotNull String path, @NotNull Set<? extends Supplier<T>> defaults) {
Objects.requireNonNull(path);
requireNonNull("path", path);
return createOptionalTagKey(ResourceLocation.fromNamespaceAndPath(this.modid, path), defaults);
}
@@ -269,8 +282,6 @@ public class DeferredRegister<T> {
* 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<T> {
* 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<T> name, @NotNull Set<? extends Supplier<T>> defaults) {
Objects.requireNonNull(defaults);
requireNonNull("defaults", defaults);
if (optionalTags == null)
optionalTags = Multimaps.newSetMultimap(new IdentityHashMap<>(), HashSet::new);
@@ -332,7 +341,7 @@ public class DeferredRegister<T> {
*/
@NotNull
public ResourceLocation getRegistryName() {
return Objects.requireNonNull(this.registryKey).location();
return this.registryKey.location();
}
private RegistryHolder<T> makeRegistry(final ResourceLocation registryName, final Supplier<RegistryBuilder<T>> sup) {
@@ -357,6 +366,12 @@ public class DeferredRegister<T> {
Multimaps.asMap(this.optionalTags).forEach(tagManager::addOptionalTagDefaults);
}
private static <T> 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<T> {
event.register(registryKey, e.getKey().getId(), () -> e.getValue().get());
e.getKey().updateReference(event);
}
if (registryFactory == null && event.getForgeRegistry() != null)
onFill(event.getForgeRegistry());
}
}
@@ -244,9 +244,9 @@ public class ForgeRegistry<V> implements IForgeRegistryInternal<V>, IForgeRegist
return wrapper;
}
void onBindTags(Map<TagKey<V>, HolderSet.Named<V>> tags, Set<TagKey<V>> defaultedTags) {
void onBindTags(Map<TagKey<V>, HolderSet.Named<V>> tags) {
if (this.tagManager != null)
this.tagManager.bind(tags, defaultedTags);
this.tagManager.bind(tags);
}
@NotNull
@@ -23,18 +23,15 @@ import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;
class ForgeRegistryTagManager<V> implements ITagManager<V>
{
class ForgeRegistryTagManager<V> implements ITagManager<V> {
private final ForgeRegistry<V> owner;
private volatile Map<TagKey<V>, ITag<V>> tags = new IdentityHashMap<>();
ForgeRegistryTagManager(ForgeRegistry<V> owner)
{
ForgeRegistryTagManager(ForgeRegistry<V> owner) {
this.owner = owner;
}
void bind(Map<TagKey<V>, HolderSet.Named<V>> holderTags, Set<TagKey<V>> defaultedTags)
{
void bind(Map<TagKey<V>, HolderSet.Named<V>> holderTags) {
IdentityHashMap<TagKey<V>, ITag<V>> newTags = new IdentityHashMap<>(this.tags);
// Forcefully unbind all pre-existing tags
@@ -49,13 +46,11 @@ class ForgeRegistryTagManager<V> implements ITagManager<V>
@NotNull
@Override
public ITag<V> getTag(@NotNull TagKey<V> name)
{
public ITag<V> getTag(@NotNull TagKey<V> name) {
Objects.requireNonNull(name);
ITag<V> tag = this.tags.get(name);
if (tag == null)
{
if (tag == null) {
// Create empty tag
tag = new ForgeRegistryTag<>(name);
@@ -72,16 +67,14 @@ class ForgeRegistryTagManager<V> implements ITagManager<V>
@SuppressWarnings({ "unchecked", "rawtypes" })
@NotNull
@Override
public Optional<IReverseTag<V>> getReverseTag(@NotNull V value)
{
public Optional<IReverseTag<V>> getReverseTag(@NotNull V value) {
Objects.requireNonNull(value);
// All Holders are implementors of IReverseTag
return (Optional<IReverseTag<V>>) (Optional) this.owner.getHolder(value);
}
@Override
public boolean isKnownTagName(@NotNull TagKey<V> name)
{
public boolean isKnownTagName(@NotNull TagKey<V> name) {
Objects.requireNonNull(name);
ITag<V> tag = this.tags.get(name);
return tag != null && tag.isBound();
@@ -89,37 +82,32 @@ class ForgeRegistryTagManager<V> implements ITagManager<V>
@NotNull
@Override
public Iterator<ITag<V>> iterator()
{
public Iterator<ITag<V>> iterator() {
return Iterators.unmodifiableIterator(this.tags.values().iterator());
}
@NotNull
@Override
public Stream<ITag<V>> stream()
{
public Stream<ITag<V>> stream() {
return this.tags.values().stream();
}
@NotNull
@Override
public Stream<TagKey<V>> getTagNames()
{
public Stream<TagKey<V>> getTagNames() {
return this.tags.keySet().stream();
}
@NotNull
@Override
public TagKey<V> createTagKey(@NotNull ResourceLocation location)
{
public TagKey<V> createTagKey(@NotNull ResourceLocation location) {
Objects.requireNonNull(location);
return TagKey.create(this.owner.getRegistryKey(), location);
}
@NotNull
@Override
public TagKey<V> createOptionalTagKey(@NotNull ResourceLocation location, @NotNull Set<? extends Supplier<V>> defaults)
{
public TagKey<V> createOptionalTagKey(@NotNull ResourceLocation location, @NotNull Set<? extends Supplier<V>> defaults) {
TagKey<V> tagKey = createTagKey(location);
addOptionalTagDefaults(tagKey, defaults);
@@ -128,8 +116,7 @@ class ForgeRegistryTagManager<V> implements ITagManager<V>
}
@Override
public void addOptionalTagDefaults(@NotNull TagKey<V> name, @NotNull Set<? extends Supplier<V>> defaults)
{
public void addOptionalTagDefaults(@NotNull TagKey<V> name, @NotNull Set<? extends Supplier<V>> defaults) {
Objects.requireNonNull(name);
Objects.requireNonNull(defaults);
@@ -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<ResourceLocation, ForgeRegistry<?>> 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())
{
@@ -318,6 +318,7 @@ class NamespacedWrapper<T> extends MappedRegistry<T> 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<T> extends MappedRegistry<T> 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<T> prepareTagReload(TagLoader.LoadResult<T> data) {
if (!this.frozen)
@@ -395,6 +404,23 @@ class NamespacedWrapper<T> extends MappedRegistry<T> 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<T> extends MappedRegistry<T> implements ILockableRegistr
}
NamespacedWrapper.this.frozenTags = MappedRegistry.TagSet.fromMap(oldBindings);
NamespacedWrapper.this.delegate.onBindTags(NamespacedWrapper.this.tags);
NamespacedWrapper.this.refreshTagsInHoldersForge();
}
};
@@ -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<? extends Registry<?>> registryKey;
@Nullable
final ForgeRegistry<?> forgeRegistry;
private final ForgeRegistry<?> forgeRegistry;
@Nullable
private final Registry<?> vanillaRegistry;
RegisterEvent(@NotNull ResourceKey<? extends Registry<?>> registryKey, @Nullable ForgeRegistry<?> forgeRegistry, @Nullable Registry<?> vanillaRegistry)
{
RegisterEvent(@NotNull ResourceKey<? extends Registry<?>> 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 <T> void register(ResourceKey<? extends Registry<T>> registryKey, ResourceLocation name, Supplier<T> valueSupplier)
{
if (this.registryKey.equals(registryKey))
{
public <T> void register(ResourceKey<? extends Registry<T>> registryKey, ResourceLocation name, Supplier<T> 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 <T> the type of the registry
* @see #register(ResourceKey, ResourceLocation, Supplier) a register variant targeted towards registering one or two objects
*/
public <T> void register(ResourceKey<? extends Registry<T>> registryKey, Consumer<RegisterHelper<T>> consumer)
{
public <T> void register(ResourceKey<? extends Registry<T>> registryKey, Consumer<RegisterHelper<T>> 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<? extends Registry<?>> getRegistryKey()
{
public ResourceKey<? extends Registry<?>> getRegistryKey() {
return registryKey;
}
@@ -91,8 +83,7 @@ public class RegisterEvent extends Event implements IModBusEvent
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> IForgeRegistry<T> getForgeRegistry()
{
public <T> IForgeRegistry<T> getForgeRegistry() {
return (IForgeRegistry<T>) forgeRegistry;
}
@@ -101,20 +92,17 @@ public class RegisterEvent extends Event implements IModBusEvent
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> Registry<T> getVanillaRegistry()
{
public <T> Registry<T> getVanillaRegistry() {
return (Registry<T>) vanillaRegistry;
}
@Override
public String toString()
{
public String toString() {
return "RegisterEvent";
}
@FunctionalInterface
public interface RegisterHelper<T>
{
public interface RegisterHelper<T> {
/**
* 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<T> key, T value)
{
default void register(ResourceKey<T> key, T value) {
register(key.location(), value);
}
@@ -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<V> extends Iterable<ITag<V>>
{
public interface ITagManager<V> extends Iterable<ITag<V>> {
/**
* 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.