Understanding the Nexus Addon Lifecycle

Updated 21 October, 2026

Understanding the Nexus Addon Lifecycle

Nexus can load, unload, update and reload your addon at any time while the game is running. That is what makes development fast and updates seamless for users, but it also means your addon has to start and stop cleanly, possibly many times per session. This guide walks through each stage of an addon's life, explains what Nexus guarantees at each point, and lists the rules that keep your addon from crashing the game.

From DLL to running addon

When Nexus finds a DLL in the addons folder, at startup or because a file changed while the game is running, it goes through these steps:

  1. Load the library. Windows maps your DLL into the game process and runs its DllMain.
  2. Read the definition. Nexus calls GetAddonDef and checks the result: required fields, a known API version and no other loaded addon with the same signature.
  3. Decide whether to load. Addons that were disabled last time, are waiting for an update after a game patch, or may only load during startup are released again at this point. They stay in the addon list, but none of your code beyond GetAddonDef runs.
  4. Check for updates. In the background, Nexus asks your update provider for a newer version. If it finds one, it downloads it and reloads your addon with it, see Updates.
  5. Call Load. Your addon receives the API table and sets itself up.
  6. Announce it. Nexus raises EV_ADDON_LOADED with your signature, then EV_MUMBLE_IDENTITY_UPDATED with the current player identity, so your addon immediately receives the identity without waiting for it to change.

DllMain

Keep DllMain empty, or omit it. It runs while Windows holds the loader lock, so loading other libraries, starting and waiting for threads or calling into Nexus from there can deadlock the game. The only thing commonly done in DllMain is storing the module handle, which you need to load embedded resources:

static HMODULE s_Module = nullptr;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		s_Module = hModule;
	}
	return TRUE;
}

Load

Load is where your addon comes to life. A typical load function:

void AddonLoad(AddonAPI_t* aApi)
{
	APIDefs = aApi;

	ImGui::SetCurrentContext((ImGuiContext*)APIDefs->ImguiContext);
	ImGui::SetAllocatorFunctions(
		(void* (*)(size_t, void*))APIDefs->ImguiMalloc,
		(void (*)(void*, void*))APIDefs->ImguiFree
	);

	Settings::Load(APIDefs->Paths_GetAddonDirectory("MyAddon/settings.json"));

	APIDefs->InputBinds_RegisterWithString("KB_MYADDON_TOGGLE", ProcessKeybind, "ALT+SHIFT+M");
	APIDefs->Events_Subscribe(EV_MUMBLE_IDENTITY_UPDATED, OnIdentityUpdated);

	/* Register render callbacks last, once everything they use is ready. */
	APIDefs->GUI_Register(RT_Render, AddonRender);
	APIDefs->GUI_Register(RT_OptionsRender, AddonOptions);
}
  • Keep it fast. Do not block on network requests or large files. Start that work on your own thread, and show a loading state until it is done.
  • Do not assume a specific thread. Callbacks can start firing as soon as you register them, possibly on other threads and before Load has returned. Register callbacks once the data they touch is initialized.
  • Do not draw. ImGui calls belong in render callbacks only. Setting the context and allocators, as above, is the exception.

While running

Once loaded, your code only runs when Nexus calls one of your callbacks: render callbacks every frame, event handlers when events are raised, keybind handlers when keys are pressed, and so on. These callbacks are invoked from different threads:

Callback Thread
Render callbacks The game's render thread, once per frame.
Texture receivers The render thread, once the texture is ready. If the texture already exists, the receiver is called right away on the calling thread.
Font receivers The render thread, before a frame is drawn.
WndProc callbacks The game's window thread.
Keybind handlers A separate thread for each key press and release.
Event handlers Whichever thread raised the event. For ArcDPS combat events, that is ArcDPS's thread.

If your callbacks share data, protect it with a mutex or use atomic variables. The simplest robust pattern is to have handlers only set flags or push messages into a queue, and to do the actual work in your render callback.

Unload

Nexus calls Unload before it removes your addon from memory: when the user disables it, before an update, before a reload during development, and when the game closes. After Unload returns, your DLL is unloaded, and any code or data in it is gone.

Your unload function must undo everything your addon did:

  • Deregister render callbacks, keybinds, event subscriptions, WndProc callbacks, Quick Access shortcuts, context menus and close-on-escape windows.
  • Release fonts you requested.
  • Stop and join every thread you started. A thread that is still running when the DLL is unloaded crashes the game.
  • Disable and remove MinHook hooks you created.
  • Save settings and free memory you allocated.
void AddonUnload()
{
	APIDefs->GUI_Deregister(AddonOptions);
	APIDefs->GUI_Deregister(AddonRender);

	APIDefs->Events_Unsubscribe(EV_MUMBLE_IDENTITY_UPDATED, OnIdentityUpdated);
	APIDefs->InputBinds_Deregister("KB_MYADDON_TOGGLE");

	Worker::Stop(); /* Signals the thread and joins it. */

	Settings::Save();
	APIDefs = nullptr;
}

Deregister render callbacks first, so nothing draws with state you are about to tear down.

The safety net

After Unload returns, Nexus scans its registries for anything that still points into your DLL and removes it, then writes a warning such as Removed 3 unreleased references from "MyAddon.dll" to the log, broken down by category. This prevents the most common crashes, but treat the warning as a bug: Nexus cannot clean up threads, hooks or memory, and it cannot tell another addon that your shared resources are gone.

Unloading during shutdown

When the game closes, Unload is called for every addon, including those with hot-loading disabled. Save what you need, but avoid slow work such as network requests: the game is shutting down around you.

State across reloads

Because the DLL is unloaded and loaded again, all global and static variables start fresh after a reload. Anything that should survive has to be stored outside your DLL:

  • Settings belong in a file in your addon folder. Paths_GetAddonDirectory("MyAddon") returns \addons\MyAddon. Nexus does not create the folder, so create it before writing.
  • Short-lived runtime state that should survive a reload within the same session can live in a shared resource. Shared resources stay allocated until the game closes.

Updates

When Nexus finds an update for your addon, it downloads it as MyAddon.dll.update. If your addon can be hot-loaded, Nexus unloads it, replaces the file and loads the new version right away, which looks to your code like a normal unload followed by a load. If hot-loading is disabled, Nexus checks for updates at startup before loading your addon, so a new version is used right away. Updates found later in the session take effect on the next start. The same mechanism drives the development reload loop.

Addons that cannot be unloaded

An addon without an Unload function, or with AF_DisableHotloading, is locked once loaded. Users can still disable or uninstall it, but the change takes effect on the next start, and Nexus tells them so. If your addon still provides Unload, it is called when the game closes. Only lock your addon if it truly cannot be unloaded safely, for example because it installs hooks other code depends on.

Lifecycle events

Other addons can react to yours, and you can react to them, through these events:

Event Payload Raised
EV_ADDON_LOADED uint32_t* signature After an addon's Load returned.
EV_ADDON_UNLOADED uint32_t* signature After an addon's Unload returned, while the game is running.
EV_VOLATILE_ADDON_DISABLED uint32_t* signature When a volatile addon was not loaded because the game has been updated.

Nexus.h does not define EV_VOLATILE_ADDON_DISABLED, so use the string "EV_VOLATILE_ADDON_DISABLED" directly. Lifecycle events are useful if your addon integrates with another one, for example to re-acquire a shared resource after the other addon was reloaded. See Event Handling and Communication Between Addons.