mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-15 05:04:15 +01:00
AddonLifecycle Performance Enhancement (#1834)
Co-authored-by: MidoriKami <9083275+midorikami@users.noreply.github.com>
This commit is contained in:
parent
290539c499
commit
c6b2f0d322
4 changed files with 59 additions and 74 deletions
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue