mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-07 16:34:36 +01:00
* Use new Lock objects * Fix CA1513: Use ObjectDisposedException.ThrowIf * Fix CA1860: Avoid using 'Enumerable.Any()' extension method * Fix IDE0028: Use collection initializers or expressions * Fix CA2263: Prefer generic overload when type is known * Fix CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons * Fix IDE0270: Null check can be simplified * Fix IDE0280: Use 'nameof' * Fix IDE0009: Add '.this' * Fix IDE0007: Use 'var' instead of explicit type * Fix IDE0062: Make local function static * Fix CA1859: Use concrete types when possible for improved performance * Fix IDE0066: Use switch expression Only applied to where it doesn't look horrendous. * Use is over switch * Fix CA1847: Use String.Contains(char) instead of String.Contains(string) with single characters * Fix SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time. * Fix CA1866: Use 'string.EndsWith(char)' instead of 'string.EndsWith(string)' when you have a string with a single char * Fix IDE0057: Substring can be simplified * Fix IDE0059: Remove unnecessary value assignment * Fix CA1510: Use ArgumentNullException throw helper * Fix IDE0300: Use collection expression for array * Fix IDE0250: Struct can be made 'readonly' * Fix IDE0018: Inline variable declaration * Fix CA1850: Prefer static HashData method over ComputeHash * Fi CA1872: Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' * Update ModuleLog instantiations * Organize usings
212 lines
7.7 KiB
C#
212 lines
7.7 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
using Dalamud.Game.Addon.Events.EventDataTypes;
|
|
using Dalamud.Game.Gui;
|
|
using Dalamud.Logging.Internal;
|
|
using Dalamud.Plugin.Services;
|
|
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
|
|
namespace Dalamud.Game.Addon.Events;
|
|
|
|
/// <summary>
|
|
/// Class to manage creating and cleaning up events per-plugin.
|
|
/// </summary>
|
|
internal unsafe class PluginEventController : IDisposable
|
|
{
|
|
private static readonly ModuleLog Log = ModuleLog.Create<AddonEventManager>();
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PluginEventController"/> class.
|
|
/// </summary>
|
|
public PluginEventController()
|
|
{
|
|
this.EventListener = new AddonEventListener(this.PluginEventListHandler);
|
|
}
|
|
|
|
private AddonEventListener EventListener { get; init; }
|
|
|
|
private List<AddonEventEntry> Events { get; } = [];
|
|
|
|
/// <summary>
|
|
/// Adds a tracked event.
|
|
/// </summary>
|
|
/// <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="eventDelegate">The delegate to call when invoking this event.</param>
|
|
/// <returns>IAddonEventHandle used to remove the event.</returns>
|
|
public IAddonEventHandle AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType atkEventType, IAddonEventManager.AddonEventDelegate eventDelegate)
|
|
{
|
|
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 = addon->NameString,
|
|
ParamKey = eventId,
|
|
EventType = atkEventType,
|
|
EventGuid = eventGuid,
|
|
};
|
|
|
|
var eventEntry = new AddonEventEntry
|
|
{
|
|
Addon = atkUnitBase,
|
|
Delegate = eventDelegate,
|
|
Node = atkResNode,
|
|
EventType = atkEventType,
|
|
ParamKey = eventId,
|
|
Handle = eventHandle,
|
|
};
|
|
|
|
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="handle">Unique ID of the event to remove.</param>
|
|
public void RemoveEvent(IAddonEventHandle handle)
|
|
{
|
|
if (this.Events.FirstOrDefault(registeredEvent => registeredEvent.Handle == handle) is not { } targetEvent) return;
|
|
|
|
Log.Verbose($"Removing Event. {targetEvent.LogString}");
|
|
this.TryRemoveEventFromNative(targetEvent);
|
|
this.Events.Remove(targetEvent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all events attached to the specified addon.
|
|
/// </summary>
|
|
/// <param name="addonName">Addon name to remove events from.</param>
|
|
public void RemoveForAddon(string addonName)
|
|
{
|
|
if (this.Events.Where(entry => entry.AddonName == addonName).ToList() is { Count: not 0 } events)
|
|
{
|
|
Log.Verbose($"Addon: {addonName} is Finalizing, removing {events.Count} events.");
|
|
|
|
foreach (var registeredEvent in events)
|
|
{
|
|
this.RemoveEvent(registeredEvent.Handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void Dispose()
|
|
{
|
|
foreach (var registeredEvent in this.Events.ToList())
|
|
{
|
|
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 event controller.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to remove a tracked event from native UI.
|
|
/// This method performs several safety checks to only remove events from a still active addon.
|
|
/// If any of these checks fail, it likely means the native UI already cleaned up the event, and we don't have to worry about them.
|
|
/// </summary>
|
|
/// <param name="eventEntry">Event entry to remove.</param>
|
|
private void TryRemoveEventFromNative(AddonEventEntry eventEntry)
|
|
{
|
|
// Is the eventEntry addon valid?
|
|
if (eventEntry.AddonName is AddonEventEntry.InvalidAddonName) return;
|
|
|
|
// Is an addon with the same name active?
|
|
var currentAddonPointer = Service<GameGui>.Get().GetAddonByName(eventEntry.AddonName);
|
|
if (currentAddonPointer == nint.Zero) return;
|
|
|
|
// Is our stored addon pointer the same as the active addon pointer?
|
|
if (currentAddonPointer != eventEntry.Addon) return;
|
|
|
|
// Make sure the addon is not unloaded
|
|
var atkUnitBase = currentAddonPointer.Struct;
|
|
if (atkUnitBase->UldManager.LoadedState == AtkLoadState.Unloaded) return;
|
|
|
|
// Does this addon contain the node this event is for? (by address)
|
|
var nodeFound = false;
|
|
foreach (var node in atkUnitBase->UldManager.Nodes)
|
|
{
|
|
// If this node matches our node, then we know our node is still valid.
|
|
if ((nint)node.Value == eventEntry.Node)
|
|
{
|
|
nodeFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we didn't find the node, we can't remove the event.
|
|
if (!nodeFound) return;
|
|
|
|
// Does the node have a registered event matching the parameters we have?
|
|
var atkResNode = (AtkResNode*)eventEntry.Node;
|
|
var eventType = (AtkEventType)eventEntry.EventType;
|
|
var currentEvent = atkResNode->AtkEventManager.Event;
|
|
var eventFound = false;
|
|
while (currentEvent is not null)
|
|
{
|
|
var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey;
|
|
var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address;
|
|
var eventTypeMatches = currentEvent->State.EventType == eventType;
|
|
|
|
if (paramKeyMatches && eventListenerAddressMatches && eventTypeMatches)
|
|
{
|
|
eventFound = true;
|
|
break;
|
|
}
|
|
|
|
// Move to the next event.
|
|
currentEvent = currentEvent->NextEvent;
|
|
}
|
|
|
|
// If we didn't find the event, we can't remove the event.
|
|
if (!eventFound) return;
|
|
|
|
// We have a valid addon, valid node, valid event, and valid key.
|
|
this.EventListener.UnregisterEvent(atkResNode, eventType, eventEntry.ParamKey);
|
|
}
|
|
|
|
private void PluginEventListHandler(AtkEventListener* self, AtkEventType eventType, uint eventParam, AtkEvent* eventPtr, AtkEventData* eventDataPtr)
|
|
{
|
|
try
|
|
{
|
|
if (eventPtr is null) return;
|
|
if (this.Events.FirstOrDefault(handler => handler.ParamKey == eventParam) is not { } eventInfo) return;
|
|
|
|
eventInfo.Delegate.Invoke((AddonEventType)eventType, new AddonEventData
|
|
{
|
|
AddonPointer = (nint)eventPtr->Node,
|
|
NodeTargetPointer = (nint)eventPtr->Target,
|
|
AtkEventDataPointer = (nint)eventDataPtr,
|
|
AtkEventListener = (nint)self,
|
|
AtkEventType = (AddonEventType)eventType,
|
|
Param = eventParam,
|
|
AtkEventPointer = (nint)eventPtr,
|
|
});
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Log.Error(exception, "Exception in PluginEventList custom event invoke.");
|
|
}
|
|
}
|
|
}
|