mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Use hooks instead of lifecycle for NamePlateGui (#2060)
This commit is contained in:
parent
f3bd83fbe9
commit
9a0bc50e23
4 changed files with 244 additions and 66 deletions
|
|
@ -1,14 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
|
|
@ -38,38 +40,44 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
|||
/// </summary>
|
||||
internal static readonly nint EmptyStringPointer = CreateEmptyStringPointer();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly AddonLifecycle addonLifecycle = Service<AddonLifecycle>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||
|
||||
private readonly AddonLifecycleEventListener preRequestedUpdateListener;
|
||||
private readonly NamePlateGuiAddressResolver address;
|
||||
|
||||
private readonly Hook<AtkUnitBase.Delegates.OnRequestedUpdate> onRequestedUpdateHook;
|
||||
|
||||
private NamePlateUpdateContext? context;
|
||||
|
||||
private NamePlateUpdateHandler[] updateHandlers = [];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NamePlateGui()
|
||||
private unsafe NamePlateGui(TargetSigScanner sigScanner)
|
||||
{
|
||||
this.preRequestedUpdateListener = new AddonLifecycleEventListener(
|
||||
AddonEvent.PreRequestedUpdate,
|
||||
"NamePlate",
|
||||
this.OnPreRequestedUpdate);
|
||||
this.address = new NamePlateGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.addonLifecycle.RegisterListener(this.preRequestedUpdateListener);
|
||||
this.onRequestedUpdateHook = Hook<AtkUnitBase.Delegates.OnRequestedUpdate>.FromAddress(
|
||||
this.address.OnRequestedUpdate,
|
||||
this.OnRequestedUpdateDetour);
|
||||
this.onRequestedUpdateHook.Enable();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdate;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void RequestRedraw()
|
||||
{
|
||||
|
|
@ -91,7 +99,7 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
|||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.addonLifecycle.UnregisterListener(this.preRequestedUpdateListener);
|
||||
this.onRequestedUpdateHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -144,65 +152,124 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
|||
this.updateHandlers = handlers.ToArray();
|
||||
}
|
||||
|
||||
private void OnPreRequestedUpdate(AddonEvent type, AddonArgs args)
|
||||
private unsafe void OnRequestedUpdateDetour(
|
||||
AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
if (this.OnDataUpdate == null && this.OnNamePlateUpdate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var calledOriginal = false;
|
||||
|
||||
var reqArgs = (AddonRequestedUpdateArgs)args;
|
||||
if (this.context == null)
|
||||
try
|
||||
{
|
||||
this.context = new NamePlateUpdateContext(this.objectTable, reqArgs);
|
||||
this.CreateHandlers(this.context);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.context.ResetState(reqArgs);
|
||||
}
|
||||
|
||||
var activeNamePlateCount = this.context.ActiveNamePlateCount;
|
||||
if (activeNamePlateCount == 0)
|
||||
return;
|
||||
|
||||
var activeHandlers = this.updateHandlers[..activeNamePlateCount];
|
||||
|
||||
if (this.context.IsFullUpdate)
|
||||
{
|
||||
foreach (var handler in activeHandlers)
|
||||
if (this.OnDataUpdate == null && this.OnNamePlateUpdate == null && this.OnPostDataUpdate == null &&
|
||||
this.OnPostNamePlateUpdate == null)
|
||||
{
|
||||
handler.ResetState();
|
||||
return;
|
||||
}
|
||||
|
||||
this.OnDataUpdate?.Invoke(this.context, activeHandlers);
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, activeHandlers);
|
||||
if (this.context.HasParts)
|
||||
this.ApplyBuilders(activeHandlers);
|
||||
}
|
||||
else
|
||||
{
|
||||
var udpatedHandlers = new List<NamePlateUpdateHandler>(activeNamePlateCount);
|
||||
foreach (var handler in activeHandlers)
|
||||
if (this.context == null)
|
||||
{
|
||||
handler.ResetState();
|
||||
if (handler.IsUpdating)
|
||||
udpatedHandlers.Add(handler);
|
||||
this.context = new NamePlateUpdateContext(this.objectTable);
|
||||
this.CreateHandlers(this.context);
|
||||
}
|
||||
|
||||
if (this.OnDataUpdate is not null)
|
||||
this.context.ResetState(addon, numberArrayData, stringArrayData);
|
||||
|
||||
var activeNamePlateCount = this.context!.ActiveNamePlateCount;
|
||||
if (activeNamePlateCount == 0)
|
||||
return;
|
||||
|
||||
var activeHandlers = this.updateHandlers[..activeNamePlateCount];
|
||||
|
||||
if (this.context.IsFullUpdate)
|
||||
{
|
||||
foreach (var handler in activeHandlers)
|
||||
{
|
||||
handler.ResetState();
|
||||
}
|
||||
|
||||
this.OnDataUpdate?.Invoke(this.context, activeHandlers);
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers);
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, activeHandlers);
|
||||
|
||||
if (this.context.HasParts)
|
||||
this.ApplyBuilders(activeHandlers);
|
||||
|
||||
try
|
||||
{
|
||||
calledOriginal = true;
|
||||
this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate.");
|
||||
}
|
||||
|
||||
this.OnPostNamePlateUpdate?.Invoke(this.context, activeHandlers);
|
||||
this.OnPostDataUpdate?.Invoke(this.context, activeHandlers);
|
||||
}
|
||||
else if (udpatedHandlers.Count != 0)
|
||||
else
|
||||
{
|
||||
var changedHandlersSpan = udpatedHandlers.ToArray().AsSpan();
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers);
|
||||
if (this.context.HasParts)
|
||||
this.ApplyBuilders(changedHandlersSpan);
|
||||
var updatedHandlers = new List<NamePlateUpdateHandler>(activeNamePlateCount);
|
||||
foreach (var handler in activeHandlers)
|
||||
{
|
||||
handler.ResetState();
|
||||
if (handler.IsUpdating)
|
||||
updatedHandlers.Add(handler);
|
||||
}
|
||||
|
||||
if (this.OnDataUpdate is not null)
|
||||
{
|
||||
this.OnDataUpdate?.Invoke(this.context, activeHandlers);
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, updatedHandlers);
|
||||
|
||||
if (this.context.HasParts)
|
||||
this.ApplyBuilders(activeHandlers);
|
||||
|
||||
try
|
||||
{
|
||||
calledOriginal = true;
|
||||
this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate.");
|
||||
}
|
||||
|
||||
this.OnPostNamePlateUpdate?.Invoke(this.context, updatedHandlers);
|
||||
this.OnPostDataUpdate?.Invoke(this.context, activeHandlers);
|
||||
}
|
||||
else if (updatedHandlers.Count != 0)
|
||||
{
|
||||
this.OnNamePlateUpdate?.Invoke(this.context, updatedHandlers);
|
||||
|
||||
if (this.context.HasParts)
|
||||
this.ApplyBuilders(updatedHandlers);
|
||||
|
||||
try
|
||||
{
|
||||
calledOriginal = true;
|
||||
this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate.");
|
||||
}
|
||||
|
||||
this.OnPostNamePlateUpdate?.Invoke(this.context, updatedHandlers);
|
||||
this.OnPostDataUpdate?.Invoke(this.context, activeHandlers);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!calledOriginal)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.onRequestedUpdateHook.Original.Invoke(addon, numberArrayData, stringArrayData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Caught exception when calling original AddonNamePlate OnRequestedUpdate.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -217,6 +284,17 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyBuilders(List<NamePlateUpdateHandler> handlers)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
if (handler.PartsContainer is { } container)
|
||||
{
|
||||
container.ApplyBuilders(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -239,6 +317,7 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate
|
|||
{
|
||||
if (this.OnNamePlateUpdateScoped == null)
|
||||
this.parentService.OnNamePlateUpdate += this.OnNamePlateUpdateForward;
|
||||
|
||||
this.OnNamePlateUpdateScoped += value;
|
||||
}
|
||||
|
||||
|
|
@ -250,6 +329,25 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdate
|
||||
{
|
||||
add
|
||||
{
|
||||
if (this.OnPostNamePlateUpdateScoped == null)
|
||||
this.parentService.OnPostNamePlateUpdate += this.OnPostNamePlateUpdateForward;
|
||||
|
||||
this.OnPostNamePlateUpdateScoped += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
this.OnPostNamePlateUpdateScoped -= value;
|
||||
if (this.OnPostNamePlateUpdateScoped == null)
|
||||
this.parentService.OnPostNamePlateUpdate -= this.OnPostNamePlateUpdateForward;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate
|
||||
{
|
||||
|
|
@ -257,6 +355,7 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate
|
|||
{
|
||||
if (this.OnDataUpdateScoped == null)
|
||||
this.parentService.OnDataUpdate += this.OnDataUpdateForward;
|
||||
|
||||
this.OnDataUpdateScoped += value;
|
||||
}
|
||||
|
||||
|
|
@ -268,10 +367,33 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdate
|
||||
{
|
||||
add
|
||||
{
|
||||
if (this.OnPostDataUpdateScoped == null)
|
||||
this.parentService.OnPostDataUpdate += this.OnPostDataUpdateForward;
|
||||
|
||||
this.OnPostDataUpdateScoped += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
this.OnPostDataUpdateScoped -= value;
|
||||
if (this.OnPostDataUpdateScoped == null)
|
||||
this.parentService.OnPostDataUpdate -= this.OnPostDataUpdateForward;
|
||||
}
|
||||
}
|
||||
|
||||
private event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdateScoped;
|
||||
|
||||
private event INamePlateGui.OnPlateUpdateDelegate? OnPostNamePlateUpdateScoped;
|
||||
|
||||
private event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdateScoped;
|
||||
|
||||
private event INamePlateGui.OnPlateUpdateDelegate? OnPostDataUpdateScoped;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestRedraw()
|
||||
{
|
||||
|
|
@ -284,8 +406,14 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate
|
|||
this.parentService.OnNamePlateUpdate -= this.OnNamePlateUpdateForward;
|
||||
this.OnNamePlateUpdateScoped = null;
|
||||
|
||||
this.parentService.OnPostNamePlateUpdate -= this.OnPostNamePlateUpdateForward;
|
||||
this.OnPostNamePlateUpdateScoped = null;
|
||||
|
||||
this.parentService.OnDataUpdate -= this.OnDataUpdateForward;
|
||||
this.OnDataUpdateScoped = null;
|
||||
|
||||
this.parentService.OnPostDataUpdate -= this.OnPostDataUpdateForward;
|
||||
this.OnPostDataUpdateScoped = null;
|
||||
}
|
||||
|
||||
private void OnNamePlateUpdateForward(
|
||||
|
|
@ -294,9 +422,21 @@ internal class NamePlateGuiPluginScoped : IInternalDisposableService, INamePlate
|
|||
this.OnNamePlateUpdateScoped?.Invoke(context, handlers);
|
||||
}
|
||||
|
||||
private void OnPostNamePlateUpdateForward(
|
||||
INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
|
||||
{
|
||||
this.OnPostNamePlateUpdateScoped?.Invoke(context, handlers);
|
||||
}
|
||||
|
||||
private void OnDataUpdateForward(
|
||||
INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
|
||||
{
|
||||
this.OnDataUpdateScoped?.Invoke(context, handlers);
|
||||
}
|
||||
|
||||
private void OnPostDataUpdateForward(
|
||||
INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
|
||||
{
|
||||
this.OnPostDataUpdateScoped?.Invoke(context, handlers);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs
Normal file
20
Dalamud/Game/Gui/NamePlate/NamePlateGuiAddressResolver.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
namespace Dalamud.Game.Gui.NamePlate;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="NamePlateGui"/> class.
|
||||
/// </summary>
|
||||
internal class NamePlateGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the AddonNamePlate OnRequestedUpdate method. We need to use a hook for this because
|
||||
/// AddonNamePlate.Show calls OnRequestedUpdate directly, bypassing the AddonLifecycle callsite hook.
|
||||
/// </summary>
|
||||
public IntPtr OnRequestedUpdate { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.OnRequestedUpdate = sig.ScanText(
|
||||
"4C 8B DC 41 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B 40 20");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
|
@ -54,13 +53,11 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext
|
|||
/// Initializes a new instance of the <see cref="NamePlateUpdateContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="objectTable">An object table.</param>
|
||||
/// <param name="args">The addon lifecycle arguments for the update request.</param>
|
||||
internal NamePlateUpdateContext(ObjectTable objectTable, AddonRequestedUpdateArgs args)
|
||||
internal NamePlateUpdateContext(ObjectTable objectTable)
|
||||
{
|
||||
this.ObjectTable = objectTable;
|
||||
this.RaptureAtkModule = FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkModule.Instance();
|
||||
this.Ui3DModule = UIModule.Instance()->GetUI3DModule();
|
||||
this.ResetState(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -137,13 +134,15 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext
|
|||
/// <summary>
|
||||
/// Resets the state of the context based on the provided addon lifecycle arguments.
|
||||
/// </summary>
|
||||
/// <param name="args">The addon lifecycle arguments for the update request.</param>
|
||||
internal void ResetState(AddonRequestedUpdateArgs args)
|
||||
/// <param name="addon">A pointer to the addon.</param>
|
||||
/// <param name="numberArrayData">A pointer to the global number array data struct.</param>
|
||||
/// <param name="stringArrayData">A pointer to the global string array data struct.</param>
|
||||
public void ResetState(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
this.Addon = (AddonNamePlate*)args.Addon;
|
||||
this.NumberData = ((NumberArrayData**)args.NumberArrayData)![NamePlateGui.NumberArrayIndex];
|
||||
this.Addon = (AddonNamePlate*)addon;
|
||||
this.NumberData = numberArrayData[NamePlateGui.NumberArrayIndex];
|
||||
this.NumberStruct = (AddonNamePlate.AddonNamePlateNumberArray*)this.NumberData->IntArray;
|
||||
this.StringData = ((StringArrayData**)args.StringArrayData)![NamePlateGui.StringArrayIndex];
|
||||
this.StringData = stringArrayData[NamePlateGui.StringArrayIndex];
|
||||
this.HasParts = false;
|
||||
|
||||
this.ActiveNamePlateCount = this.NumberStruct->ActiveNamePlateCount;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@ public interface INamePlateGui
|
|||
/// </remarks>
|
||||
event OnPlateUpdateDelegate? OnNamePlateUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// An event which fires after nameplate data is updated and at least one nameplate had important updates. The
|
||||
/// subscriber is provided with a list of handlers for nameplates with important updates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Fires before <see cref="OnPostDataUpdate"/>.
|
||||
/// </remarks>
|
||||
event OnPlateUpdateDelegate? OnPostNamePlateUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// An event which fires when nameplate data is updated. The subscriber is provided with a list of handlers for all
|
||||
/// nameplates.
|
||||
|
|
@ -36,6 +45,16 @@ public interface INamePlateGui
|
|||
/// </remarks>
|
||||
event OnPlateUpdateDelegate? OnDataUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// An event which fires after nameplate data is updated. The subscriber is provided with a list of handlers for all
|
||||
/// nameplates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is likely to fire every frame even when no nameplates are actually updated, so in most cases
|
||||
/// <see cref="OnNamePlateUpdate"/> is preferred. Fires after <see cref="OnPostNamePlateUpdate"/>.
|
||||
/// </remarks>
|
||||
event OnPlateUpdateDelegate? OnPostDataUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Requests that all nameplates should be redrawn on the following frame.
|
||||
/// </summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue