Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.21.x] Topologically sort reload listeners based on dependency ordering #1915

Merged
merged 14 commits into from
Feb 6, 2025
Merged
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
--- a/net/minecraft/server/packs/resources/ReloadableResourceManager.java
+++ b/net/minecraft/server/packs/resources/ReloadableResourceManager.java
@@ -73,4 +_,10 @@
@@ -33,6 +_,12 @@
this.resources.close();
}

+ /**
+ * @deprecated Neo: Use {@link net.neoforged.neoforge.client.event.AddClientReloadListenerEvent}.
+ *
+ * @throws UnsupportedOperationException if called after the event has been fired.
+ */
+ @Deprecated
public void registerReloadListener(PreparableReloadListener p_10714_) {
this.listeners.add(p_10714_);
}
@@ -72,5 +_,24 @@
@Override
public Stream<PackResources> listPacks() {
return this.resources.listPacks();
}
+ }
+
+ public void registerReloadListenerIfNotPresent(PreparableReloadListener listener) {
+ if (!this.listeners.contains(listener)) {
+ this.registerReloadListener(listener);
+ }
+ /**
+ * Neo: Expose the reload listeners so they can be passed to the event.
+ *
+ * @return The (immutable) list of reload listeners.
+ */
+ public List<PreparableReloadListener> getListeners() {
+ return this.listeners;
+ }
+
+ /**
+ * Neo: Updates the {@link #listeners} with the sorted list from the event.
+ *
+ * @implNote The returned list is immutable, so after this method is called, {@link #registerReloadListener(PreparableReloadListener)} will throw.
+ */
+ @org.jetbrains.annotations.ApiStatus.Internal
+ public void updateListenersFrom(net.neoforged.neoforge.event.SortedReloadListenerEvent event) {
+ this.listeners = net.neoforged.neoforge.resource.ReloadListenerSort.sort(event);
}
}
8 changes: 6 additions & 2 deletions src/main/java/net/neoforged/neoforge/client/ClientHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.asm.enumextension.ExtensionInfo;
import net.neoforged.neoforge.client.entity.animation.json.AnimationTypeManager;
import net.neoforged.neoforge.client.event.AddClientReloadListenerEvent;
import net.neoforged.neoforge.client.event.AddSectionGeometryEvent;
import net.neoforged.neoforge.client.event.CalculateDetachedCameraDistanceEvent;
import net.neoforged.neoforge.client.event.CalculatePlayerTurnEvent;
Expand All @@ -151,7 +152,6 @@
import net.neoforged.neoforge.client.event.InputEvent;
import net.neoforged.neoforge.client.event.ModelEvent;
import net.neoforged.neoforge.client.event.MovementInputUpdateEvent;
import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent;
import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent;
import net.neoforged.neoforge.client.event.RegisterMaterialAtlasesEvent;
Expand Down Expand Up @@ -975,7 +975,11 @@ public static void initClientHooks(Minecraft mc, ReloadableResourceManager resou
GameTestHooks.registerGametests();
registerSpriteSourceTypes();
MenuScreens.init();
ModLoader.postEvent(new RegisterClientReloadListenersEvent(resourceManager));

var rlEvent = new AddClientReloadListenerEvent(resourceManager);
ModLoader.postEvent(rlEvent);
resourceManager.updateListenersFrom(rlEvent);

ModLoader.postEvent(new EntityRenderersEvent.RegisterLayerDefinitions());
ModLoader.postEvent(new EntityRenderersEvent.RegisterRenderers());
ModLoader.postEvent(new RegisterRenderStateModifiersEvent());
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import net.neoforged.fml.config.ModConfigs;
import net.neoforged.neoforge.client.color.item.FluidContentsTint;
import net.neoforged.neoforge.client.entity.animation.json.AnimationLoader;
import net.neoforged.neoforge.client.event.AddClientReloadListenerEvent;
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
import net.neoforged.neoforge.client.event.ModelEvent;
import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent;
import net.neoforged.neoforge.client.event.RegisterItemModelsEvent;
import net.neoforged.neoforge.client.event.RegisterNamedRenderTypesEvent;
Expand All @@ -38,6 +38,7 @@
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
import net.neoforged.neoforge.client.gui.ConfigurationScreen;
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
import net.neoforged.neoforge.client.loading.ClientModLoader;
import net.neoforged.neoforge.client.model.EmptyModel;
import net.neoforged.neoforge.client.model.UnbakedCompositeModel;
import net.neoforged.neoforge.client.model.item.DynamicFluidContainerModel;
Expand All @@ -64,7 +65,10 @@
import net.neoforged.neoforge.common.data.internal.VanillaSoundDefinitionsProvider;
import net.neoforged.neoforge.common.util.SelfTest;
import net.neoforged.neoforge.data.event.GatherDataEvent;
import net.neoforged.neoforge.internal.BrandingControl;
import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion;
import net.neoforged.neoforge.resource.NeoForgeReloadListeners;
import net.neoforged.neoforge.resource.VanillaClientListeners;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
Expand Down Expand Up @@ -131,9 +135,16 @@ static void onRegisterModelLoaders(ModelEvent.RegisterLoaders event) {
}

@SubscribeEvent
static void onRegisterReloadListeners(RegisterClientReloadListenersEvent event) {
event.registerReloadListener(ObjLoader.INSTANCE);
event.registerReloadListener(AnimationLoader.INSTANCE);
static void onRegisterReloadListeners(AddClientReloadListenerEvent event) {
event.addListener(NeoForgeReloadListeners.CLIENT_MOD_LOADING, ClientModLoader::onResourceReload);
event.addListener(NeoForgeReloadListeners.BRANDING, BrandingControl.resourceManagerReloadListener());

// These run before vanilla reload listeners, so we add them before LANGUAGE, the first vanilla one.
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
event.addDependency(NeoForgeReloadListeners.CLIENT_MOD_LOADING, NeoForgeReloadListeners.BRANDING);
event.addDependency(NeoForgeReloadListeners.BRANDING, VanillaClientListeners.LANGUAGE);

event.addListener(NeoForgeReloadListeners.OBJ_LOADER, ObjLoader.INSTANCE);
event.addListener(NeoForgeReloadListeners.ENTITY_ANIMATIONS, AnimationLoader.INSTANCE);
}

@SubscribeEvent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.client.event;

import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ReloadableResourceManager;
import net.neoforged.fml.LogicalSide;
import net.neoforged.fml.event.IModBusEvent;
import net.neoforged.neoforge.event.SortedReloadListenerEvent;
import net.neoforged.neoforge.resource.VanillaClientListeners;
import org.jetbrains.annotations.ApiStatus;

/**
* This event allows mods to register client-side reload listeners to the resource manager.
* This event is fired once during the construction of the {@link Minecraft} instance.
* <p>
* This event is only fired on the {@linkplain LogicalSide#CLIENT logical client}.
*
* @see {@link AddReloadListenerEvent} for registering server-side reload listeners.
*/
public class AddClientReloadListenerEvent extends SortedReloadListenerEvent implements IModBusEvent {
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
@ApiStatus.Internal
public AddClientReloadListenerEvent(ReloadableResourceManager resourceManager) {
super(resourceManager.getListeners(), AddClientReloadListenerEvent::lookupName);
}

private static ResourceLocation lookupName(PreparableReloadListener listener) {
ResourceLocation key = VanillaClientListeners.getNameForClass(listener.getClass());
if (key == null) {
if (listener.getClass().getPackageName().startsWith("net.minecraft")) {
throw new IllegalArgumentException("A key for the reload listener " + listener + " was not provided in VanillaClientListeners!");
} else {
throw new IllegalArgumentException("A non-vanilla reload listener " + listener + " was added via mixin before the AddClientReloadListenerEvent! Mod-added listeners must go through the event.");
}
}
return key;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.common.NeoForgeConfig;
import net.neoforged.neoforge.common.util.LogicalSidedProvider;
import net.neoforged.neoforge.internal.BrandingControl;
import net.neoforged.neoforge.internal.CommonModLoader;
import net.neoforged.neoforge.logging.CrashReportExtender;
import net.neoforged.neoforge.resource.ResourcePackLoader;
Expand Down Expand Up @@ -63,12 +62,15 @@ public static void begin(final Minecraft minecraft, final PackRepository default
if (error == null) {
ResourcePackLoader.populatePackRepository(defaultResourcePacks, PackType.CLIENT_RESOURCES, false);
DataPackConfig.DEFAULT.addModPacks(ResourcePackLoader.getPackNames(PackType.SERVER_DATA));
mcResourceManager.registerReloadListener(ClientModLoader::onResourceReload);
mcResourceManager.registerReloadListener(BrandingControl.resourceManagerReloadListener());
}
}

private static CompletableFuture<Void> onResourceReload(final PreparableReloadListener.PreparationBarrier stage, final ResourceManager resourceManager, final Executor asyncExecutor, final Executor syncExecutor) {
/**
* This method can be bound as a method reference to {@link PreparableReloadListener}.
* <p>
* It is used as the entrypoint for client mod loading, which starts when {@link Minecraft} triggers the first resource reload.
*/
public static CompletableFuture<Void> onResourceReload(final PreparableReloadListener.PreparationBarrier stage, final ResourceManager resourceManager, final Executor asyncExecutor, final Executor syncExecutor) {
return CompletableFuture.runAsync(() -> startModLoading(syncExecutor, asyncExecutor), ModWorkManager.parallelExecutor())
.thenCompose(stage::wait)
.thenRunAsync(() -> finishModLoading(syncExecutor, asyncExecutor), ModWorkManager.parallelExecutor());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
import net.neoforged.neoforge.client.event.AddClientReloadListenerEvent;
import net.neoforged.neoforge.client.event.ModelEvent;
import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;

/**
* A loader for custom {@linkplain UnbakedModel unbaked models}.
* <p>
* If you do any caching, you should implement {@link ResourceManagerReloadListener} and register it with
* {@link RegisterClientReloadListenersEvent}.
* {@link AddClientReloadListenerEvent}.
*
* @see ModelEvent.RegisterLoaders
* @see RegisterClientReloadListenersEvent
* @see AddClientReloadListenerEvent
*/
public interface UnbakedModelLoader<T extends UnbakedModel> {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@
import net.neoforged.neoforge.network.payload.RegistryDataMapSyncPayload;
import net.neoforged.neoforge.registries.DataMapLoader;
import net.neoforged.neoforge.registries.RegistryManager;
import net.neoforged.neoforge.resource.NeoForgeReloadListeners;
import net.neoforged.neoforge.server.command.ConfigCommand;
import net.neoforged.neoforge.server.command.NeoForgeCommand;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public class NeoForgeEventHandler {
private static LootModifierManager LOOT_MODIFIER_MANAGER;
private static DataMapLoader DATA_MAP_LOADER;

@SubscribeEvent(priority = EventPriority.HIGH)
public void onEntityJoinWorld(EntityJoinLevelEvent event) {
Entity entity = event.getEntity();
Expand Down Expand Up @@ -102,7 +106,7 @@ public void playerLogin(PlayerEvent.PlayerLoggedInEvent event) {
@SubscribeEvent
public void tagsUpdated(TagsUpdatedEvent event) {
if (event.getUpdateCause() == TagsUpdatedEvent.UpdateCause.SERVER_DATA_LOAD) {
DATA_MAPS.apply();
DATA_MAP_LOADER.apply();
}
}

Expand Down Expand Up @@ -146,25 +150,17 @@ public void onCommandsRegister(RegisterCommandsEvent event) {
ConfigCommand.register(event.getDispatcher());
}

private static LootModifierManager INSTANCE;
private static DataMapLoader DATA_MAPS;

@SubscribeEvent
public void onResourceReload(AddReloadListenerEvent event) {
INSTANCE = new LootModifierManager();
event.addListener(INSTANCE);
event.addListener(DATA_MAPS = new DataMapLoader(event.getConditionContext(), event.getRegistryAccess()));
event.addListener(NeoForgeReloadListeners.LOOT_MODIFIERS, LOOT_MODIFIER_MANAGER = new LootModifierManager());
event.addListener(NeoForgeReloadListeners.DATA_MAPS, DATA_MAP_LOADER = new DataMapLoader(event.getConditionContext(), event.getRegistryAccess()));
event.addListener(NeoForgeReloadListeners.CREATIVE_TABS, CreativeModeTabRegistry.getReloadListener());
}

static LootModifierManager getLootModifierManager() {
if (INSTANCE == null)
if (LOOT_MODIFIER_MANAGER == null)
throw new IllegalStateException("Can not retrieve LootModifierManager until resources have loaded once.");
return INSTANCE;
}

@SubscribeEvent
public void resourceReloadListeners(AddReloadListenerEvent event) {
event.addListener(CreativeModeTabRegistry.getReloadListener());
return LOOT_MODIFIER_MANAGER;
}

@SubscribeEvent(priority = EventPriority.HIGHEST)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.common.util;

import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import org.spongepowered.include.com.google.common.base.Preconditions;

@ApiStatus.Internal
public class VanillaClassToKey {
Shadows-of-Fire marked this conversation as resolved.
Show resolved Hide resolved
/**
* Converts a vanilla class name into an identifier compliant with the rules set by {@link ResourceLocation}.
* <p>
* This conversion is done by translating all uppercase characters into an underscore plus the lowercase version of the original character.
*
* @param cls The class to convert.
* @return A lower_snake_case representation of that class's original PascalCase name.
* @throws IllegalArgumentException if the class is not from Minecraft, or if the class does not have a {@link Class#getSimpleName() simple name}.
*/
public static ResourceLocation convert(Class<?> cls) {
Preconditions.checkArgument(cls.getPackageName().startsWith("net.minecraft"), "Automatic name conversion can only be applied to net.minecraft classes. Provided: " + cls.getName());
Preconditions.checkArgument(!cls.getSimpleName().isEmpty(), "Automatic name conversion can only happen for identifiable classes (per Class#getSimpleName()). Provided: " + cls.getName());

StringBuilder sb = new StringBuilder();
cls.getSimpleName().chars().mapMulti((value, consumer) -> {
if (Character.isUpperCase((char) value)) {
consumer.accept('_');
consumer.accept(Character.toLowerCase((char) value));
} else {
consumer.accept(value);
}
}).forEach(i -> sb.append((char) i));

return ResourceLocation.withDefaultNamespace(sb.substring(1)); // The string will be prefixed with an additional `_` since the first character is uppercase.
}
}
Loading
Loading