Auto generate paramkeys and return handles to events.

This commit is contained in:
MidoriKami 2023-09-21 20:47:49 -07:00
parent 3b5995e6ab
commit 26838d9f5c
7 changed files with 127 additions and 40 deletions

View file

@ -1,4 +1,6 @@
using Dalamud.Memory;
using System;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
@ -46,9 +48,14 @@ internal unsafe class AddonEventEntry
/// Gets the event type for this event.
/// </summary>
required public AddonEventType EventType { get; init; }
/// <summary>
/// Gets the event handle for this event.
/// </summary>
required internal IAddonEventHandle Handle { get; init; }
/// <summary>
/// Gets the formatted log string for this AddonEventEntry.
/// </summary>
internal string LogString => $"ParamKey: {this.ParamKey}, Addon: {this.AddonName}, Event: {this.EventType}";
internal string LogString => $"ParamKey: {this.ParamKey}, Addon: {this.AddonName}, Event: {this.EventType}, GUID: {this.Handle.EventGuid}";
}

View file

@ -0,0 +1,21 @@
using System;
namespace Dalamud.Game.Addon;
/// <summary>
/// Class that represents a addon event handle.
/// </summary>
public class AddonEventHandle : IAddonEventHandle
{
/// <inheritdoc/>
public uint ParamKey { get; init; }
/// <inheritdoc/>
public string AddonName { get; init; } = "NullAddon";
/// <inheritdoc/>
public AddonEventType EventType { get; init; }
/// <inheritdoc/>
public Guid EventGuid { get; init; }
}

View file

@ -77,33 +77,32 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
/// Registers an event handler for the specified addon, node, and type.
/// </summary>
/// <param name="pluginId">Unique ID for this plugin.</param>
/// <param name="eventId">Unique Id for this event, maximum 0x10000.</param>
/// <param name="atkUnitBase">The parent addon for this event.</param>
/// <param name="atkResNode">The node that will trigger this event.</param>
/// <param name="eventType">The event type for this event.</param>
/// <param name="eventHandler">The handler to call when event is triggered.</param>
internal void AddEvent(string pluginId, uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
/// <returns>IAddonEventHandle used to remove the event.</returns>
internal IAddonEventHandle? AddEvent(string pluginId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
{
if (this.pluginEventControllers.FirstOrDefault(entry => entry.PluginId == pluginId) is { } eventController)
{
eventController.AddEvent(eventId, atkUnitBase, atkResNode, eventType, eventHandler);
}
else
{
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
return eventController.AddEvent(atkUnitBase, atkResNode, eventType, eventHandler);
}
Log.Verbose($"Unable to locate controller for {pluginId}. No event was added.");
return null;
}
/// <summary>
/// Unregisters an event handler with the specified event id and event type.
/// </summary>
/// <param name="pluginId">Unique ID for this plugin.</param>
/// <param name="eventId">The Unique Id for this event.</param>
internal void RemoveEvent(string pluginId, uint eventId)
/// <param name="eventHandle">The Unique Id for this event.</param>
internal void RemoveEvent(string pluginId, IAddonEventHandle eventHandle)
{
if (this.pluginEventControllers.FirstOrDefault(entry => entry.PluginId == pluginId) is { } eventController)
{
eventController.RemoveEvent(eventId);
eventController.RemoveEvent(eventHandle);
}
else
{
@ -239,12 +238,12 @@ internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddon
}
/// <inheritdoc/>
public void AddEvent(uint eventId, IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
=> this.eventManagerService.AddEvent(this.plugin.Manifest.WorkingPluginId.ToString(), eventId, atkUnitBase, atkResNode, eventType, eventHandler);
public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler)
=> this.eventManagerService.AddEvent(this.plugin.Manifest.WorkingPluginId.ToString(), atkUnitBase, atkResNode, eventType, eventHandler);
/// <inheritdoc/>
public void RemoveEvent(uint eventId)
=> this.eventManagerService.RemoveEvent(this.plugin.Manifest.WorkingPluginId.ToString(), eventId);
public void RemoveEvent(IAddonEventHandle eventHandle)
=> this.eventManagerService.RemoveEvent(this.plugin.Manifest.WorkingPluginId.ToString(), eventHandle);
/// <inheritdoc/>
public void SetCursor(AddonCursorType cursor)

View file

@ -0,0 +1,29 @@
using System;
namespace Dalamud.Game.Addon;
/// <summary>
/// Interface representing the data used for managing AddonEvents.
/// </summary>
public interface IAddonEventHandle
{
/// <summary>
/// Gets the param key associated with this event.
/// </summary>
public uint ParamKey { get; init; }
/// <summary>
/// Gets the name of the addon that this event was attached to.
/// </summary>
public string AddonName { get; init; }
/// <summary>
/// Gets the event type associated with this handle.
/// </summary>
public AddonEventType EventType { get; init; }
/// <summary>
/// Gets the unique ID for this handle.
/// </summary>
public Guid EventGuid { get; init; }
}

View file

@ -4,6 +4,7 @@ using System.Linq;
using Dalamud.Game.Gui;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
@ -39,17 +40,27 @@ internal unsafe class PluginEventController : IDisposable
/// <summary>
/// Adds a tracked event.
/// </summary>
/// <param name="eventId">Unique ID of the event to add.</param>
/// <param name="atkUnitBase">The Parent addon for the event.</param>
/// <param name="atkResNode">The Node for the event.</param>
/// <param name="atkEventType">The Event Type.</param>
/// <param name="handler">The delegate to call when invoking this event.</param>
public void AddEvent(uint eventId, nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventHandler handler)
/// <returns>IAddonEventHandle used to remove the event.</returns>
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventHandler handler)
{
var node = (AtkResNode*)atkResNode;
var addon = (AtkUnitBase*)atkUnitBase;
var eventType = (AtkEventType)atkEventType;
var eventId = this.GetNextParamKey();
var eventGuid = Guid.NewGuid();
var eventHandle = new AddonEventHandle
{
AddonName = MemoryHelper.ReadStringNullTerminated((nint)addon->Name),
ParamKey = eventId,
EventType = atkEventType,
EventGuid = eventGuid,
};
var eventEntry = new AddonEventEntry
{
Addon = atkUnitBase,
@ -57,22 +68,25 @@ internal unsafe class PluginEventController : IDisposable
Node = atkResNode,
EventType = atkEventType,
ParamKey = eventId,
Handle = eventHandle,
};
Log.Verbose($"Adding Event: {eventEntry.LogString}");
Log.Verbose($"Adding Event. {eventEntry.LogString}");
this.EventListener.RegisterEvent(addon, node, eventType, eventId);
this.Events.Add(eventEntry);
return eventHandle;
}
/// <summary>
/// Removes a tracked event, also attempts to un-attach the event from native.
/// </summary>
/// <param name="eventId">Unique ID of the event to remove.</param>
public void RemoveEvent(uint eventId)
/// <param name="handle">Unique ID of the event to remove.</param>
public void RemoveEvent(IAddonEventHandle handle)
{
if (this.Events.FirstOrDefault(registeredEvent => registeredEvent.ParamKey == eventId) is not { } targetEvent) return;
if (this.Events.FirstOrDefault(registeredEvent => registeredEvent.Handle == handle) is not { } targetEvent) return;
Log.Verbose($"Removing Event: {targetEvent.LogString}");
Log.Verbose($"Removing Event. {targetEvent.LogString}");
this.TryRemoveEventFromNative(targetEvent);
this.Events.Remove(targetEvent);
}
@ -89,7 +103,7 @@ internal unsafe class PluginEventController : IDisposable
foreach (var registeredEvent in events)
{
this.RemoveEvent(registeredEvent.ParamKey);
this.RemoveEvent(registeredEvent.Handle);
}
}
}
@ -99,11 +113,21 @@ internal unsafe class PluginEventController : IDisposable
{
foreach (var registeredEvent in this.Events.ToList())
{
this.RemoveEvent(registeredEvent.ParamKey);
this.RemoveEvent(registeredEvent.Handle);
}
this.EventListener.Dispose();
}
private uint GetNextParamKey()
{
for (var i = 0u; i < uint.MaxValue; ++i)
{
if (this.Events.All(registeredEvent => registeredEvent.ParamKey != i)) return i;
}
throw new OverflowException($"uint.MaxValue number of ParamKeys used for {this.PluginId}");
}
/// <summary>
/// Attempts to remove a tracked event from native UI.

View file

@ -25,9 +25,6 @@ namespace Dalamud.Game.Gui.Dtr;
public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
{
private const uint BaseNodeId = 1000;
private const uint MouseOverEventIdOffset = 10000;
private const uint MouseOutEventIdOffset = 20000;
private const uint MouseClickEventIdOffset = 30000;
private static readonly ModuleLog Log = new("DtrBar");
@ -51,6 +48,8 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
private readonly ConcurrentBag<DtrBarEntry> newEntries = new();
private readonly List<DtrBarEntry> entries = new();
private readonly Dictionary<uint, List<IAddonEventHandle>> eventHandles = new();
private uint runningNodeIds = BaseNodeId;
@ -328,6 +327,11 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
private void RecreateNodes()
{
this.runningNodeIds = BaseNodeId;
if (this.entries.Any())
{
this.eventHandles.Clear();
}
foreach (var entry in this.entries)
{
entry.TextNode = this.MakeNode(++this.runningNodeIds);
@ -362,10 +366,14 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
var dtr = this.GetDtr();
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, node->AtkResNode.NodeID + MouseOverEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler);
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, node->AtkResNode.NodeID + MouseOutEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler);
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, node->AtkResNode.NodeID + MouseClickEventIdOffset, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler);
this.eventHandles.TryAdd(node->AtkResNode.NodeID, new List<IAddonEventHandle>());
this.eventHandles[node->AtkResNode.NodeID].AddRange(new List<IAddonEventHandle>
{
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOver, this.DtrEventHandler),
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseOut, this.DtrEventHandler),
this.uiEventManager.AddEvent(AddonEventManager.DalamudInternalKey, (nint)dtr, (nint)node, AddonEventType.MouseClick, this.DtrEventHandler),
});
var lastChild = dtr->RootNode->ChildNode;
while (lastChild->PrevSiblingNode != null) lastChild = lastChild->PrevSiblingNode;
Log.Debug($"Found last sibling: {(ulong)lastChild:X}");
@ -387,9 +395,8 @@ public sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
var dtr = this.GetDtr();
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return;
this.uiEventManager.RemoveEvent(AddonEventManager.DalamudInternalKey, node->AtkResNode.NodeID + MouseOverEventIdOffset);
this.uiEventManager.RemoveEvent(AddonEventManager.DalamudInternalKey, node->AtkResNode.NodeID + MouseOutEventIdOffset);
this.uiEventManager.RemoveEvent(AddonEventManager.DalamudInternalKey, node->AtkResNode.NodeID + MouseClickEventIdOffset);
this.eventHandles[node->AtkResNode.NodeID].ForEach(handle => this.uiEventManager.RemoveEvent(AddonEventManager.DalamudInternalKey, handle));
this.eventHandles[node->AtkResNode.NodeID].Clear();
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
var tmpNextNode = node->AtkResNode.NextSiblingNode;

View file

@ -18,18 +18,18 @@ public interface IAddonEventManager
/// <summary>
/// Registers an event handler for the specified addon, node, and type.
/// </summary>
/// <param name="eventId">Unique Id for this event, maximum 0x10000.</param>
/// <param name="atkUnitBase">The parent addon for this event.</param>
/// <param name="atkResNode">The node that will trigger this event.</param>
/// <param name="eventType">The event type for this event.</param>
/// <param name="eventHandler">The handler to call when event is triggered.</param>
void AddEvent(uint eventId, nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventHandler eventHandler);
/// <returns>IAddonEventHandle used to remove the event. Null if no event was added.</returns>
IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventHandler eventHandler);
/// <summary>
/// Unregisters an event handler with the specified event id and event type.
/// </summary>
/// <param name="eventId">The Unique Id for this event.</param>
void RemoveEvent(uint eventId);
/// <param name="eventHandle">Unique handle identifying this event.</param>
void RemoveEvent(IAddonEventHandle eventHandle);
/// <summary>
/// Force the game cursor to be the specified cursor.