From 7e687b320e8caf80af63d8021f890fdd48128c3a Mon Sep 17 00:00:00 2001 From: modmuss <modmuss50@gmail.com> Date: Sun, 14 Jul 2024 15:01:57 +0100 Subject: [PATCH] Add API to create reload listeners with a registry lookup. (#3927) * Add API to create reload listeners with a registry lookup. * Document this is only for server data * Review feedback --- .../api/resource/ResourceManagerHelper.java | 13 +++ .../loader/ResourceManagerHelperImpl.java | 99 +++++++++++++++++-- .../fabric-resource-loader-v0.accesswidener | 1 + .../loader/ResourceReloadListenerTestMod.java | 21 ++++ 4 files changed, 127 insertions(+), 7 deletions(-) diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/ResourceManagerHelper.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/ResourceManagerHelper.java index b5b5dd9e7..39d2d3246 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/ResourceManagerHelper.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/ResourceManagerHelper.java @@ -16,8 +16,11 @@ package net.fabricmc.fabric.api.resource; +import java.util.function.Function; + import org.jetbrains.annotations.ApiStatus; +import net.minecraft.registry.RegistryWrapper; import net.minecraft.resource.ResourceManager; import net.minecraft.resource.ResourceType; import net.minecraft.text.Text; @@ -49,6 +52,16 @@ public interface ResourceManagerHelper { */ void registerReloadListener(IdentifiableResourceReloadListener listener); + /** + * Register a resource reload listener for a given resource manager type. + * + * <p>Note: This is only supported for server data reload listeners. + * + * @param identifier The identifier of the listener. + * @param listenerFactory A function that creates a new instance of the listener with a given registry lookup. + */ + void registerReloadListener(Identifier identifier, Function<RegistryWrapper.WrapperLookup, IdentifiableResourceReloadListener> listenerFactory); + /** * Get the ResourceManagerHelper instance for a given resource type. * diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java index d3521ab16..9621b69b7 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java @@ -25,13 +25,18 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import com.google.common.collect.Lists; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.recipe.RecipeManager; +import net.minecraft.registry.RegistryWrapper; import net.minecraft.resource.ResourcePack; import net.minecraft.resource.ResourcePackInfo; import net.minecraft.resource.ResourcePackPosition; @@ -53,10 +58,16 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper { private static final Logger LOGGER = LoggerFactory.getLogger(ResourceManagerHelperImpl.class); private final Set<Identifier> addedListenerIds = new HashSet<>(); + private final Set<ListenerFactory> listenerFactories = new LinkedHashSet<>(); private final Set<IdentifiableResourceReloadListener> addedListeners = new LinkedHashSet<>(); + private final ResourceType type; + + private ResourceManagerHelperImpl(ResourceType type) { + this.type = type; + } public static ResourceManagerHelperImpl get(ResourceType type) { - return registryMap.computeIfAbsent(type, (t) -> new ResourceManagerHelperImpl()); + return registryMap.computeIfAbsent(type, ResourceManagerHelperImpl::new); } /** @@ -74,7 +85,7 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper { public static boolean registerBuiltinResourcePack(Identifier id, String subPath, ModContainer container, Text displayName, ResourcePackActivationType activationType) { // Assuming the mod has multiple paths, we simply "hope" that the file separator is *not* different across them List<Path> paths = container.getRootPaths(); - String separator = paths.get(0).getFileSystem().getSeparator(); + String separator = paths.getFirst().getFileSystem().getSeparator(); subPath = subPath.replace("/", separator); ModNioResourcePack resourcePack = ModNioResourcePack.create(id.toString(), container, subPath, ResourceType.CLIENT_RESOURCES, activationType, false); ModNioResourcePack dataPack = ModNioResourcePack.create(id.toString(), container, subPath, ResourceType.SERVER_DATA, activationType, false); @@ -168,7 +179,16 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper { // trust them 100%. Only code doesn't lie. // - We addReloadListener all custom listeners after vanilla listeners. Same reasons. - List<IdentifiableResourceReloadListener> listenersToAdd = Lists.newArrayList(addedListeners); + final RegistryWrapper.WrapperLookup wrapperLookup = getWrapperLookup(listeners); + List<IdentifiableResourceReloadListener> listenersToAdd = Lists.newArrayList(); + + for (ListenerFactory addedListener : listenerFactories) { + listenersToAdd.add(addedListener.get(wrapperLookup)); + } + + addedListeners.clear(); + addedListeners.addAll(listenersToAdd); + Set<Identifier> resolvedIds = new HashSet<>(); for (ResourceReloader listener : listeners) { @@ -200,15 +220,80 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper { } } + // A bit of a hack to get the registry, but it works. + @Nullable + private RegistryWrapper.WrapperLookup getWrapperLookup(List<ResourceReloader> listeners) { + if (type == ResourceType.CLIENT_RESOURCES) { + // We don't need the registry for client resources. + return null; + } + + for (ResourceReloader resourceReloader : listeners) { + if (resourceReloader instanceof RecipeManager recipeManager) { + return recipeManager.registryLookup; + } + } + + throw new IllegalStateException("No RecipeManager found in listeners!"); + } + @Override public void registerReloadListener(IdentifiableResourceReloadListener listener) { - if (!addedListenerIds.add(listener.getFabricId())) { - LOGGER.warn("Tried to register resource reload listener " + listener.getFabricId() + " twice!"); + registerReloadListener(new SimpleResourceReloaderFactory(listener)); + } + + @Override + public void registerReloadListener(Identifier identifier, Function<RegistryWrapper.WrapperLookup, IdentifiableResourceReloadListener> listenerFactory) { + if (type == ResourceType.CLIENT_RESOURCES) { + throw new IllegalArgumentException("Cannot register a registry listener for the client resource type!"); + } + + registerReloadListener(new RegistryResourceReloaderFactory(identifier, listenerFactory)); + } + + private void registerReloadListener(ListenerFactory factory) { + if (!addedListenerIds.add(factory.id())) { + LOGGER.warn("Tried to register resource reload listener " + factory.id() + " twice!"); return; } - if (!addedListeners.add(listener)) { - throw new RuntimeException("Listener with previously unknown ID " + listener.getFabricId() + " already in listener set!"); + if (!listenerFactories.add(factory)) { + throw new RuntimeException("Listener with previously unknown ID " + factory.id() + " already in listener set!"); + } + } + + private sealed interface ListenerFactory permits SimpleResourceReloaderFactory, RegistryResourceReloaderFactory { + Identifier id(); + + IdentifiableResourceReloadListener get(RegistryWrapper.WrapperLookup registry); + } + + private record SimpleResourceReloaderFactory(IdentifiableResourceReloadListener listener) implements ListenerFactory { + @Override + public Identifier id() { + return listener.getFabricId(); + } + + @Override + public IdentifiableResourceReloadListener get(RegistryWrapper.WrapperLookup registry) { + return listener; + } + } + + private record RegistryResourceReloaderFactory(Identifier id, Function<RegistryWrapper.WrapperLookup, IdentifiableResourceReloadListener> listenerFactory) implements ListenerFactory { + private RegistryResourceReloaderFactory { + Objects.requireNonNull(listenerFactory); + } + + @Override + public IdentifiableResourceReloadListener get(RegistryWrapper.WrapperLookup registry) { + final IdentifiableResourceReloadListener listener = listenerFactory.apply(registry); + + if (!id.equals(listener.getFabricId())) { + throw new IllegalStateException("Listener factory for " + id + " created a listener with ID " + listener.getFabricId()); + } + + return listener; } } } diff --git a/fabric-resource-loader-v0/src/main/resources/fabric-resource-loader-v0.accesswidener b/fabric-resource-loader-v0/src/main/resources/fabric-resource-loader-v0.accesswidener index a60e57afe..8c0642a75 100644 --- a/fabric-resource-loader-v0/src/main/resources/fabric-resource-loader-v0.accesswidener +++ b/fabric-resource-loader-v0/src/main/resources/fabric-resource-loader-v0.accesswidener @@ -4,3 +4,4 @@ accessible method net/minecraft/resource/NamespaceResourceManager getMetadataPat accessible method net/minecraft/resource/NamespaceResourceManager loadMetadata (Lnet/minecraft/resource/InputSupplier;)Lnet/minecraft/resource/metadata/ResourceMetadata; accessible field net/minecraft/resource/FileResourcePackProvider source Lnet/minecraft/resource/ResourcePackSource; accessible field net/minecraft/resource/ResourcePackManager providers Ljava/util/Set; +accessible field net/minecraft/recipe/RecipeManager registryLookup Lnet/minecraft/registry/RegistryWrapper$WrapperLookup; diff --git a/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/ResourceReloadListenerTestMod.java b/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/ResourceReloadListenerTestMod.java index d57faf2e1..bbbfc8a33 100644 --- a/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/ResourceReloadListenerTestMod.java +++ b/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/ResourceReloadListenerTestMod.java @@ -18,7 +18,11 @@ package net.fabricmc.fabric.test.resource.loader; import java.util.Collection; import java.util.Collections; +import java.util.Objects; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.RegistryWrapper; import net.minecraft.resource.ResourceManager; import net.minecraft.resource.ResourceType; import net.minecraft.util.Identifier; @@ -116,5 +120,22 @@ public class ResourceReloadListenerTestMod implements ModInitializer { serverResources = true; } }); + + ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(RegistryReloader.ID, RegistryReloader::new); + } + + private record RegistryReloader(RegistryWrapper.WrapperLookup wrapperLookup) implements SimpleSynchronousResourceReloadListener { + private static final Identifier ID = Identifier.of(MODID, "registry_reloader"); + + @Override + public Identifier getFabricId() { + return ID; + } + + @Override + public void reload(ResourceManager manager) { + Objects.requireNonNull(wrapperLookup); + wrapperLookup.getWrapperOrThrow(RegistryKeys.ENCHANTMENT).getOrThrow(Enchantments.FORTUNE); + } } }