From c6b2f0d322cb42400a55429330484f836b76ecb0 Mon Sep 17 00:00:00 2001 From: KazWolfe Date: Fri, 21 Jun 2024 14:59:16 -0700 Subject: [PATCH] AddonLifecycle Performance Enhancement (#1834) Co-authored-by: MidoriKami <9083275+midorikami@users.noreply.github.com> --- .../Game/Addon/Lifecycle/AddonLifecycle.cs | 54 ++++++++----------- .../AddonLifecycleAddressResolver.cs | 19 ++----- .../AddonLifecycleReceiveEventListener.cs | 50 +++++++++-------- .../Data/Widgets/AddonLifecycleWidget.cs | 10 ++-- 4 files changed, 59 insertions(+), 74 deletions(-) diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index be334f441..9252507a6 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -8,7 +8,6 @@ using Dalamud.Hooking.Internal; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; -using Dalamud.Memory; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -32,13 +31,13 @@ internal unsafe class AddonLifecycle : IInternalDisposableService private readonly nint disallowedReceiveEventAddress; private readonly AddonLifecycleAddressResolver address; - private readonly CallHook onAddonSetupHook; - private readonly CallHook onAddonSetup2Hook; + private readonly CallHook onAddonSetupHook; + private readonly CallHook onAddonSetup2Hook; private readonly Hook onAddonFinalizeHook; - private readonly CallHook onAddonDrawHook; - private readonly CallHook onAddonUpdateHook; - private readonly Hook onAddonRefreshHook; - private readonly CallHook onAddonRequestedUpdateHook; + private readonly CallHook onAddonDrawHook; + private readonly CallHook onAddonUpdateHook; + private readonly Hook onAddonRefreshHook; + private readonly CallHook onAddonRequestedUpdateHook; [ServiceManager.ServiceConstructor] private AddonLifecycle(TargetSigScanner sigScanner) @@ -46,16 +45,17 @@ internal unsafe class AddonLifecycle : IInternalDisposableService this.address = new AddonLifecycleAddressResolver(); this.address.Setup(sigScanner); - // We want value of the function pointer at vFunc[2] - this.disallowedReceiveEventAddress = ((nint*)this.address.AtkEventListener)![2]; + this.disallowedReceiveEventAddress = (nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveEvent; - this.onAddonSetupHook = new CallHook(this.address.AddonSetup, this.OnAddonSetup); - this.onAddonSetup2Hook = new CallHook(this.address.AddonSetup2, this.OnAddonSetup); + var refreshAddonAddress = (nint)AtkStage.Instance()->RaptureAtkUnitManager->AtkUnitManager.VirtualTable->RefreshAddon; + + this.onAddonSetupHook = new CallHook(this.address.AddonSetup, this.OnAddonSetup); + this.onAddonSetup2Hook = new CallHook(this.address.AddonSetup2, this.OnAddonSetup); this.onAddonFinalizeHook = Hook.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize); - this.onAddonDrawHook = new CallHook(this.address.AddonDraw, this.OnAddonDraw); - this.onAddonUpdateHook = new CallHook(this.address.AddonUpdate, this.OnAddonUpdate); - this.onAddonRefreshHook = Hook.FromAddress(this.address.AddonOnRefresh, this.OnAddonRefresh); - this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate); + this.onAddonDrawHook = new CallHook(this.address.AddonDraw, this.OnAddonDraw); + this.onAddonUpdateHook = new CallHook(this.address.AddonUpdate, this.OnAddonUpdate); + this.onAddonRefreshHook = Hook.FromAddress(refreshAddonAddress, this.OnAddonRefresh); + this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate); this.onAddonSetupHook.Enable(); this.onAddonSetup2Hook.Enable(); @@ -66,18 +66,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService this.onAddonRequestedUpdateHook.Enable(); } - private delegate void AddonSetupDelegate(AtkUnitBase* addon, uint valueCount, AtkValue* values); - private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase); - private delegate void AddonDrawDelegate(AtkUnitBase* addon); - - private delegate void AddonUpdateDelegate(AtkUnitBase* addon, float delta); - - private delegate void AddonOnRequestedUpdateDelegate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData); - - private delegate byte AddonOnRefreshDelegate(AtkUnitManager* unitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values); - /// /// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks. /// @@ -121,7 +111,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService { if (this.ReceiveEventListeners.FirstOrDefault(listeners => listeners.AddonNames.Contains(listener.AddonName)) is { } receiveEventListener) { - receiveEventListener.Hook?.Enable(); + receiveEventListener.TryEnable(); } } }); @@ -149,7 +139,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService // If there are no other listeners listening for this event, disable the hook. if (!this.EventListeners.Any(listeners => listeners.AddonName.Contains(listener.AddonName) && listener.EventType is AddonEvent.PreReceiveEvent or AddonEvent.PostReceiveEvent)) { - receiveEventListener.Hook?.Disable(); + receiveEventListener.Disable(); } } } @@ -198,7 +188,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService if (receiveEventAddress != this.disallowedReceiveEventAddress) { // If we have a ReceiveEvent listener already made for this hook address, add this addon's name to that handler. - if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.HookAddress == receiveEventAddress) is { } existingListener) + if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.FunctionAddress == receiveEventAddress) is { } existingListener) { if (!existingListener.AddonNames.Contains(addonName)) { @@ -217,7 +207,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService { if (this.ReceiveEventListeners.FirstOrDefault(listener => listener.AddonNames.Contains(addonName)) is { } receiveEventListener) { - receiveEventListener.Hook?.Enable(); + receiveEventListener.TryEnable(); } } } @@ -333,9 +323,9 @@ internal unsafe class AddonLifecycle : IInternalDisposableService this.InvokeListenersSafely(AddonEvent.PostUpdate, arg); } - private byte OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values) + private bool OnAddonRefresh(AtkUnitManager* thisPtr, AtkUnitBase* addon, uint valueCount, AtkValue* values) { - byte result = 0; + var result = false; using var returner = this.argsPool.Rent(out AddonRefreshArgs arg); arg.AddonInternal = (nint)addon; @@ -347,7 +337,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService try { - result = this.onAddonRefreshHook.Original(atkUnitManager, addon, valueCount, values); + result = this.onAddonRefreshHook.Original(thisPtr, addon, valueCount, values); } catch (Exception e) { diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs index df25d0a46..9a33bf920 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleAddressResolver.cs @@ -1,9 +1,11 @@ -namespace Dalamud.Game.Addon.Lifecycle; +using FFXIVClientStructs.FFXIV.Component.GUI; + +namespace Dalamud.Game.Addon.Lifecycle; /// /// AddonLifecycleService memory address resolver. /// -internal class AddonLifecycleAddressResolver : BaseAddressResolver +internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver { /// /// Gets the address of the addon setup hook invoked by the AtkUnitManager. @@ -38,17 +40,6 @@ internal class AddonLifecycleAddressResolver : BaseAddressResolver /// Gets the address of the addon onRequestedUpdate hook invoked by virtual function call. /// public nint AddonOnRequestedUpdate { get; private set; } - - /// - /// Gets the address of AtkUnitManager_vf10 which triggers addon onRefresh. - /// - public nint AddonOnRefresh { get; private set; } - - /// - /// Gets the address of AtkEventListener base vTable. - /// This is used to ensure that we do not hook ReceiveEvents that resolve back to the internal handler. - /// - public nint AtkEventListener { get; private set; } /// /// Scan for and setup any configured address pointers. @@ -62,7 +53,5 @@ internal class AddonLifecycleAddressResolver : BaseAddressResolver this.AddonDraw = sig.ScanText("FF 90 ?? ?? ?? ?? 83 EB 01 79 C1"); this.AddonUpdate = sig.ScanText("FF 90 ?? ?? ?? ?? 40 88 AF"); this.AddonOnRequestedUpdate = sig.ScanText("FF 90 98 01 00 00 48 8B 5C 24 30 48 83 C4 20"); - this.AddonOnRefresh = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 41 8B F8 48 8B DA"); - this.AtkEventListener = sig.GetStaticAddressFromSig("4C 8D 3D ?? ?? ?? ?? 49 8D 8E"); } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs index 90a8eb844..a66440b25 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs @@ -3,7 +3,6 @@ using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Hooking; using Dalamud.Logging.Internal; -using Dalamud.Memory; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -29,53 +28,60 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable internal AddonLifecycleReceiveEventListener(AddonLifecycle service, string addonName, nint receiveEventAddress) { this.AddonLifecycle = service; - this.AddonNames = new List { addonName }; - this.Hook = Hook.FromAddress(receiveEventAddress, this.OnReceiveEvent); + this.AddonNames = [addonName]; + this.FunctionAddress = receiveEventAddress; } - /// - /// Addon Receive Event Function delegate. - /// - /// Addon Pointer. - /// Event Type. - /// Unique Event ID. - /// Event Data. - /// Unknown. - public delegate void AddonReceiveEventDelegate(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, nint a5); - /// /// Gets the list of addons that use this receive event hook. /// public List AddonNames { get; init; } - + /// - /// Gets the address of the registered hook. + /// Gets the address of the ReceiveEvent function as provided by the vtable on setup. /// - public nint HookAddress => this.Hook?.Address ?? nint.Zero; + public nint FunctionAddress { get; init; } /// /// Gets the contained hook for these addons. /// - public Hook? Hook { get; init; } + public Hook? Hook { get; private set; } /// /// Gets or sets the Reference to AddonLifecycle service instance. /// private AddonLifecycle AddonLifecycle { get; set; } + /// + /// Try to hook and enable this receive event handler. + /// + public void TryEnable() + { + this.Hook ??= Hook.FromAddress(this.FunctionAddress, this.OnReceiveEvent); + this.Hook?.Enable(); + } + + /// + /// Disable the hook for this receive event handler. + /// + public void Disable() + { + this.Hook?.Disable(); + } + /// public void Dispose() { this.Hook?.Dispose(); } - private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, nint data) + private void OnReceiveEvent(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { // Check that we didn't get here through a call to another addons handler. var addonName = addon->NameString; if (!this.AddonNames.Contains(addonName)) { - this.Hook!.Original(addon, eventType, eventParam, atkEvent, data); + this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData); return; } @@ -84,16 +90,16 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable arg.AtkEventType = (byte)eventType; arg.EventParam = eventParam; arg.AtkEvent = (IntPtr)atkEvent; - arg.Data = data; + arg.Data = (nint)atkEventData; this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, arg); eventType = (AtkEventType)arg.AtkEventType; eventParam = arg.EventParam; atkEvent = (AtkEvent*)arg.AtkEvent; - data = arg.Data; + atkEventData = (AtkEventData*)arg.Data; try { - this.Hook!.Original(addon, eventType, eventParam, atkEvent, data); + this.Hook!.Original(addon, eventType, eventParam, atkEvent, atkEventData); } catch (Exception e) { diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs index 0ac83c126..53066765e 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs @@ -15,7 +15,7 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets; public class AddonLifecycleWidget : IDataWindowWidget { /// - public string[]? CommandShortcuts { get; init; } = { "AddonLifecycle" }; + public string[]? CommandShortcuts { get; init; } = ["AddonLifecycle"]; /// public string DisplayName { get; init; } = "Addon Lifecycle"; @@ -74,7 +74,7 @@ public class AddonLifecycleWidget : IDataWindowWidget ImGui.Indent(); var listeners = this.AddonLifecycle.EventListeners.Where(listener => listener.EventType == eventType).ToList(); - if (!listeners.Any()) + if (listeners.Count == 0) { ImGui.Text("No Listeners Registered for Event"); } @@ -90,7 +90,7 @@ public class AddonLifecycleWidget : IDataWindowWidget ImGui.Text(listener.AddonName is "" ? "GLOBAL" : listener.AddonName); ImGui.TableNextColumn(); - ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType.FullName}::{listener.FunctionDelegate.Method.Name}"); + ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType?.FullName ?? "Unknown Declaring Type"}::{listener.FunctionDelegate.Method.Name}"); } ImGui.EndTable(); @@ -107,7 +107,7 @@ public class AddonLifecycleWidget : IDataWindowWidget var listeners = this.AddonLifecycle.ReceiveEventListeners; - if (!listeners.Any()) + if (listeners.Count == 0) { ImGui.Text("No ReceiveEvent Hooks are Registered"); } @@ -120,7 +120,7 @@ public class AddonLifecycleWidget : IDataWindowWidget ImGui.Text("Hook Address"); ImGui.NextColumn(); - ImGui.Text(receiveEventListener.HookAddress.ToString("X")); + ImGui.Text(receiveEventListener.FunctionAddress.ToString("X")); ImGui.NextColumn(); ImGui.Text("Hook Status");