Understanding how plugins are loaded, initialized, and unloaded in Dalamud
Plugins in Dalamud follow a well-defined lifecycle from discovery to unload. Understanding this lifecycle is crucial for building robust plugins that integrate cleanly with the framework.
public MyPlugin(DalamudPluginInterface pluginInterface, IFramework framework){ framework.Update += OnFrameworkUpdate;}private void OnFrameworkUpdate(IFramework framework){ // Called every game tick (~60 FPS)}
Plugins unload when the user disables them or when Dalamud shuts down:
1
Unload Request
The Plugin Manager initiates unload, changing state to PluginState.Unloading.
2
Dispose Called
Your plugin’s Dispose() method is invoked:
public void Dispose(){ // Clean up all resources framework.Update -= OnFrameworkUpdate; pluginInterface.UiBuilder.Draw -= DrawUI; commands.RemoveHandler("/mycommand");}
Always unregister ALL event handlers and commands in Dispose! Failure to do so can cause crashes.
3
Grace Period
Dalamud waits for a grace period before unloading the assembly:
// From Dalamud/Plugin/Internal/PluginManager.cs:51public const int PluginWaitBeforeFreeDefault = 1000; // 1 second
This ensures all pending operations complete.
4
Assembly Unload
The plugin’s AssemblyLoadContext is unloaded:
// From Dalamud/Plugin/Internal/Loader/PluginLoader.cs:116if (this.context.IsCollectible) this.context.Unload();
// Goodpublic MyPlugin(IFramework framework){ this.framework = framework; this.framework.Update += OnUpdate;}// Badpublic MyPlugin(IFramework framework){ Thread.Sleep(5000); // Never do this! var player = GetLocalPlayer(); // Might not be ready yet!}
Use Framework.RunOnFrameworkThread() for game state access
Be careful with background tasks
Task.Run(async () =>{ var data = await FetchDataFromApi(); // Switch back to framework thread for UI updates await framework.RunOnFrameworkThread(() => { UpdateUIWithData(data); });});
Performance
Framework.Update runs every frame (~60 FPS)
Keep event handlers fast (<16ms)
Cache expensive computations
Use throttling for expensive operations
private DateTime lastCheck = DateTime.MinValue;private void OnUpdate(IFramework framework){ // Only check every second instead of 60 times per second if (DateTime.Now - lastCheck < TimeSpan.FromSeconds(1)) return; lastCheck = DateTime.Now; DoExpensiveCheck();}