Skip to content

Reload listeners - #368

Open
IchHabeHunger54 wants to merge 7 commits into
neoforged:mainfrom
IchHabeHunger54:reload-listeners
Open

Reload listeners#368
IchHabeHunger54 wants to merge 7 commits into
neoforged:mainfrom
IchHabeHunger54:reload-listeners

Conversation

@IchHabeHunger54

@IchHabeHunger54 IchHabeHunger54 commented Jul 27, 2026

Copy link
Copy Markdown
Member

Adds reload listener docs. Since this covers both client and server (as they are almost the same), they are placed in the root Resources folder.

Opening as draft as it is not yet complete. At the time of writing, the second half of the article, as well as any and all links, are missing. EDIT: Done.

Closes #306 .


Preview URL: https://pr-368.neoforged-docs-previews.pages.dev

@IchHabeHunger54 IchHabeHunger54 changed the title reload listeners, part 1 Reload listeners Jul 27, 2026
@neoforged-pages-deployments
neoforged-pages-deployments Bot deployed to neoforged-docs-previews (Preview) July 27, 2026 20:33 Active
@neoforged-pages-deployments

neoforged-pages-deployments Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Pages

Name Result
Last commit: 7f9a816e2f8ae6d1d59055df713ed1f3e5d84b33
Status: ✅ Deploy successful!
Preview URL: https://e134d686.neoforged-docs-previews.pages.dev
PR Preview URL: https://pr-368.neoforged-docs-previews.pages.dev

@IchHabeHunger54 IchHabeHunger54 added the addition Adding or rewriting information. label Jul 27, 2026
@neoforged-pages-deployments
neoforged-pages-deployments Bot deployed to neoforged-docs-previews (Preview) July 27, 2026 21:02 Active
@IchHabeHunger54
IchHabeHunger54 marked this pull request as ready for review July 27, 2026 21:47
@IchHabeHunger54
IchHabeHunger54 requested a review from a team July 27, 2026 21:47
@neoforged-pages-deployments
neoforged-pages-deployments Bot deployed to neoforged-docs-previews (Preview) July 27, 2026 21:51 Active
Comment thread docs/resources/reloadlisteners.md
Comment thread docs/resources/reloadlisteners.md Outdated

In some situations, integrating with the [existing resource systems][resources] provided by Minecraft or NeoForge just isn't going to cut it. Instead, having your system load files by itself from a resource or data pack is more desirable. For this purpose, you can register a custom reload listener, implementing `PreparableReloadListener` or one of its subinterfaces/subclasses.

The idea behind a reload listener is simple: When a resource pack or data pack reload happens, the listener is called upon to read its contents from the new set of resource or data packs, loaded into a global `ResourceManager` and usually reference-copied into other places for easier access. It will then keep the contents until the next reload, at which point the contents will be discarded and the cycle starts anew.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be worth being more specific here by saying the pack repository since it could expand into the subpacks we briefly touch on the datagen docs.

Comment thread docs/resources/reloadlisteners.md Outdated
Comment thread docs/resources/reloadlisteners.md Outdated
Comment thread docs/resources/reloadlisteners.md Outdated
Comment thread docs/resources/reloadlisteners.md Outdated
Comment thread docs/resources/reloadlisteners.md Outdated
Comment thread docs/resources/reloadlisteners.md Outdated
Comment thread docs/resources/reloadlisteners.md Outdated
Comment thread docs/resources/reloadlisteners.md Outdated
@neoforged-pages-deployments
neoforged-pages-deployments Bot deployed to neoforged-docs-previews (Preview) July 28, 2026 19:59 Active
@neoforged-pages-deployments
neoforged-pages-deployments Bot deployed to neoforged-docs-previews (Preview) July 29, 2026 10:39 Active
The idea behind a reload listener is simple: When a resource pack or data pack reload happens, the listener is called upon to read its contents from the new set of resource or data packs. It will then keep the contents until the next reload, at which point the contents will be discarded and the cycle starts anew.

:::warning
On the server [side][sides], the more robust [datapack registry][datapackregistries] or [data map][datamaps] systems should be preferred over a reload listener, if possible.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would mark this as info instead of warning because there's nothing wrong with using a custom reload listener instead. There is also nothing inherently more robust about the two alternatives, they are just existing systems that may or may not work for a given use case.

|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| **Loads From** | Resource packs (`assets` folder) | Data packs (`data` folder) |
| **First Reload**<br/>(Physical Client) | - Startup | - Creating a new world<br/>("Preparing for world creation...")<br/>- Joining an existing world<br/>- Joining a server |
| **Subsequent Reloads**<br/>(Physical Client) | - Changing resource packs in the Options menu<br/>- Downloading a server's custom resource pack on server join<br/>- Pressing F3+T | - Leaving a world or server<br/>(unloads the listener) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Leaving a world or server (unloads the listener)

This is wrong or at the very least incredibly confusing. Leaving a world or server does unload the data loaded during the last server-side resource reload but it does not touch the reload listeners at all. If a listener is neither kept in a field nor in Neo's "retained listeners" (more on that later) then it goes out of scope right after the reload completes, if it's retained then it goes out of scope with the data and if it's kept in a field then it stays around forever.

Comment on lines +82 to +94
```java
// For client-side reload listeners
@SubscribeEvent // on the game event bus only on the physical client
public static void addClientReloadListeners(AddClientReloadListenersEvent event) {
event.addListener(MyReloadListener.ID, MyReloadListener.INSTANCE);
}

// For server-side reload listeners
@SubscribeEvent // on the game event bus
public static void addServerReloadListeners(AddServerReloadListenersEvent event) {
event.addListener(MyReloadListener.ID, MyReloadListener.INSTANCE);
}
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite the additional warning below, this example should use different class names for server and client reload listeners to avoid confusion.

Server reload listeners should also ideally not be kept in a static field and instead use AddServerReloadListenersEvent#addRetainedListener() and later retrieve the listener from ReloadableServerResources#getListener(). Storing a server reload listener in a static field risks leaking loaded data, requiring manual cleanup, as well as reaching across sides.

Comment on lines +269 to +293
```java
// Create a record (or class) holding our data to pass to another listener
public record MyPendingResources(/* any data here */) {}

// Can also extend/implement any subclass/subinterface of PreparableReloadListener
public class MyReloadListener implements PreparableReloadListener {
// other stuff here

// Create a StateKey with our record as the type
public static final StateKey<MyPendingResources> STATE_KEY = new StateKey<>();

// Override prepareSharedState() to add our pending resources
@Override
public void prepareSharedState(PreparableReloadListener.SharedState currentReload) {
currentReload.set(STATE_KEY, new MyPendingResources(/* any data here */));
}

// Then, use in reload() like so:
@Override
public CompletableFuture<Void> reload(SharedState currentReload, Executor taskExecutor, PreparationBarrier barrier, Executor reloadExecutor) {
MyPendingResources pending = currentReload.get(STATE_KEY);
// do the reload here, using `pending`
}
}
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is incomplete (doesn't show how to "wait" for the other listener's data) and confusing (the state is provided and used by the same listener).

The shared object basically must be a "dangling" CompletableFuture (i.e. Neo's AnimationLoader) or map thereof (vanilla's AtlasManager). The dependent listener retrieves this future from the SharedState and incorporates it into its future chain to wait on it (see use of AtlasManager.PENDING_STITCH in ModelManager#reload()). The listener being depended on produces the data, retrieves the future from the SharedState and manually complete()s it (see AnimationLoader#reload()) which effectively "wakes up" the dependent listener.
The statement below this comment is incorrect (the SharedState is just a "dumb" map, it performs no synchronization whatsoever) and should be replaced with a rough explanation of this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

addition Adding or rewriting information.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add docs for resource listeners

3 participants