AddonLifecycle Performance Enhancement (#1834)

Co-authored-by: MidoriKami <9083275+midorikami@users.noreply.github.com>
This commit is contained in:
KazWolfe 2024-06-21 14:59:16 -07:00 committed by Kaz Wolfe
parent 290539c499
commit c6b2f0d322
No known key found for this signature in database
GPG key ID: 258813F53A16EBB4
4 changed files with 59 additions and 74 deletions

View file

@ -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<AddonSetupDelegate> onAddonSetupHook;
private readonly CallHook<AddonSetupDelegate> onAddonSetup2Hook;
private readonly CallHook<AtkUnitBase.Delegates.OnSetup> onAddonSetupHook;
private readonly CallHook<AtkUnitBase.Delegates.OnSetup> onAddonSetup2Hook;
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
private readonly CallHook<AddonDrawDelegate> onAddonDrawHook;
private readonly CallHook<AddonUpdateDelegate> onAddonUpdateHook;
private readonly Hook<AddonOnRefreshDelegate> onAddonRefreshHook;
private readonly CallHook<AddonOnRequestedUpdateDelegate> onAddonRequestedUpdateHook;
private readonly CallHook<AtkUnitBase.Delegates.Draw> onAddonDrawHook;
private readonly CallHook<AtkUnitBase.Delegates.Update> onAddonUpdateHook;
private readonly Hook<AtkUnitManager.Delegates.RefreshAddon> onAddonRefreshHook;
private readonly CallHook<AtkUnitBase.Delegates.OnRequestedUpdate> 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<AddonSetupDelegate>(this.address.AddonSetup, this.OnAddonSetup);
this.onAddonSetup2Hook = new CallHook<AddonSetupDelegate>(this.address.AddonSetup2, this.OnAddonSetup);
var refreshAddonAddress = (nint)AtkStage.Instance()->RaptureAtkUnitManager->AtkUnitManager.VirtualTable->RefreshAddon;
this.onAddonSetupHook = new CallHook<AtkUnitBase.Delegates.OnSetup>(this.address.AddonSetup, this.OnAddonSetup);
this.onAddonSetup2Hook = new CallHook<AtkUnitBase.Delegates.OnSetup>(this.address.AddonSetup2, this.OnAddonSetup);
this.onAddonFinalizeHook = Hook<AddonFinalizeDelegate>.FromAddress(this.address.AddonFinalize, this.OnAddonFinalize);
this.onAddonDrawHook = new CallHook<AddonDrawDelegate>(this.address.AddonDraw, this.OnAddonDraw);
this.onAddonUpdateHook = new CallHook<AddonUpdateDelegate>(this.address.AddonUpdate, this.OnAddonUpdate);
this.onAddonRefreshHook = Hook<AddonOnRefreshDelegate>.FromAddress(this.address.AddonOnRefresh, this.OnAddonRefresh);
this.onAddonRequestedUpdateHook = new CallHook<AddonOnRequestedUpdateDelegate>(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
this.onAddonDrawHook = new CallHook<AtkUnitBase.Delegates.Draw>(this.address.AddonDraw, this.OnAddonDraw);
this.onAddonUpdateHook = new CallHook<AtkUnitBase.Delegates.Update>(this.address.AddonUpdate, this.OnAddonUpdate);
this.onAddonRefreshHook = Hook<AtkUnitManager.Delegates.RefreshAddon>.FromAddress(refreshAddonAddress, this.OnAddonRefresh);
this.onAddonRequestedUpdateHook = new CallHook<AtkUnitBase.Delegates.OnRequestedUpdate>(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);
/// <summary>
/// Gets a list of all AddonLifecycle ReceiveEvent Listener Hooks.
/// </summary>
@ -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)
{

View file

@ -1,9 +1,11 @@
namespace Dalamud.Game.Addon.Lifecycle;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace Dalamud.Game.Addon.Lifecycle;
/// <summary>
/// AddonLifecycleService memory address resolver.
/// </summary>
internal class AddonLifecycleAddressResolver : BaseAddressResolver
internal unsafe class AddonLifecycleAddressResolver : BaseAddressResolver
{
/// <summary>
/// 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.
/// </summary>
public nint AddonOnRequestedUpdate { get; private set; }
/// <summary>
/// Gets the address of AtkUnitManager_vf10 which triggers addon onRefresh.
/// </summary>
public nint AddonOnRefresh { get; private set; }
/// <summary>
/// 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.
/// </summary>
public nint AtkEventListener { get; private set; }
/// <summary>
/// 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");
}
}

View file

@ -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<string> { addonName };
this.Hook = Hook<AddonReceiveEventDelegate>.FromAddress(receiveEventAddress, this.OnReceiveEvent);
this.AddonNames = [addonName];
this.FunctionAddress = receiveEventAddress;
}
/// <summary>
/// Addon Receive Event Function delegate.
/// </summary>
/// <param name="addon">Addon Pointer.</param>
/// <param name="eventType">Event Type.</param>
/// <param name="eventParam">Unique Event ID.</param>
/// <param name="atkEvent">Event Data.</param>
/// <param name="a5">Unknown.</param>
public delegate void AddonReceiveEventDelegate(AtkUnitBase* addon, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, nint a5);
/// <summary>
/// Gets the list of addons that use this receive event hook.
/// </summary>
public List<string> AddonNames { get; init; }
/// <summary>
/// Gets the address of the registered hook.
/// Gets the address of the ReceiveEvent function as provided by the vtable on setup.
/// </summary>
public nint HookAddress => this.Hook?.Address ?? nint.Zero;
public nint FunctionAddress { get; init; }
/// <summary>
/// Gets the contained hook for these addons.
/// </summary>
public Hook<AddonReceiveEventDelegate>? Hook { get; init; }
public Hook<AtkUnitBase.Delegates.ReceiveEvent>? Hook { get; private set; }
/// <summary>
/// Gets or sets the Reference to AddonLifecycle service instance.
/// </summary>
private AddonLifecycle AddonLifecycle { get; set; }
/// <summary>
/// Try to hook and enable this receive event handler.
/// </summary>
public void TryEnable()
{
this.Hook ??= Hook<AtkUnitBase.Delegates.ReceiveEvent>.FromAddress(this.FunctionAddress, this.OnReceiveEvent);
this.Hook?.Enable();
}
/// <summary>
/// Disable the hook for this receive event handler.
/// </summary>
public void Disable()
{
this.Hook?.Disable();
}
/// <inheritdoc/>
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)
{

View file

@ -15,7 +15,7 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
public class AddonLifecycleWidget : IDataWindowWidget
{
/// <inheritdoc/>
public string[]? CommandShortcuts { get; init; } = { "AddonLifecycle" };
public string[]? CommandShortcuts { get; init; } = ["AddonLifecycle"];
/// <inheritdoc/>
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");