From c51e65e0bd07bc58bfab65128cb7719ce56c996b Mon Sep 17 00:00:00 2001 From: MidoriKami Date: Sun, 30 Nov 2025 10:08:40 -0800 Subject: [PATCH] Better unload --- .../Game/Addon/Lifecycle/AddonLifecycle.cs | 33 +++++-------------- .../Game/Addon/Lifecycle/AddonVirtualTable.cs | 15 +++------ 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index d3d0fcebe..5d121bea4 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -19,13 +19,13 @@ namespace Dalamud.Game.Addon.Lifecycle; [ServiceManager.EarlyLoadedService] internal unsafe class AddonLifecycle : IInternalDisposableService { + /// + /// Gets a list of all allocated addon virtual tables. + /// + public static readonly List AllocatedTables = []; + private static readonly ModuleLog Log = new("AddonLifecycle"); - [ServiceManager.ServiceDependency] - private readonly Framework framework = Service.Get(); - - private readonly Dictionary modifiedTables = []; - private Hook? onInitializeAddonHook; [ServiceManager.ServiceConstructor] @@ -47,13 +47,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService this.onInitializeAddonHook?.Dispose(); this.onInitializeAddonHook = null; - this.framework.RunOnFrameworkThread(() => - { - foreach (var virtualTable in this.modifiedTables.Values) - { - virtualTable.Dispose(); - } - }); + AllocatedTables.ForEach(entry => entry.Dispose()); + AllocatedTables.Clear(); } /// @@ -141,18 +136,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService { this.LogInitialize(addon->NameString); - if (!this.modifiedTables.ContainsKey(addon->NameString)) - { - // AddonVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions - var managedVirtualTableEntry = new AddonVirtualTable(addon, this) - { - // This event is invoked when the game itself has disposed of an addon - // We can use this to know when to remove our virtual table entry - OnAddonFinalized = () => this.modifiedTables.Remove(addon->NameString), - }; - - this.modifiedTables.Add(addon->NameString, managedVirtualTableEntry); - } + // AddonVirtualTable class handles creating the virtual table, and overriding each of the tracked virtual functions + AllocatedTables.Add(new AddonVirtualTable(addon, this)); } catch (Exception e) { diff --git a/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs b/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs index db698e626..d91cd648f 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonVirtualTable.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Logging.Internal; @@ -108,17 +109,11 @@ internal unsafe class AddonVirtualTable : IDisposable this.modifiedVirtualTable->Hide = (delegate* unmanaged)Marshal.GetFunctionPointerForDelegate(this.hideFunction); } - /// - /// Gets an event that is invoked when this addon's Finalize method is called from native. - /// - public required Action OnAddonFinalized { get; init; } - - /// - /// WARNING! This should not be called at any time except during dalamud unload. - /// + /// public void Dispose() { - this.atkUnitBase->VirtualTable = this.originalVirtualTable; + // Ensure restoration is done atomically. + Interlocked.Exchange(ref *(nint*)&this.atkUnitBase->VirtualTable, (nint)this.originalVirtualTable); IMemorySpace.Free(this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount); } @@ -131,7 +126,7 @@ internal unsafe class AddonVirtualTable : IDisposable if ((freeFlags & 1) == 1) { IMemorySpace.Free(this.modifiedVirtualTable, 0x8 * VirtualTableEntryCount); - this.OnAddonFinalized(); + AddonLifecycle.AllocatedTables.Remove(this); } return result;