diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index 403671920..e38f56921 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -38,7 +38,8 @@ internal unsafe class AddonLifecycle : IInternalDisposableService /// /// Gets a list of all AddonLifecycle Event Listeners. /// - internal Dictionary> EventListeners { get; } = []; + /// Mapping is: EventType -> AddonName -> ListenerList + internal Dictionary>> EventListeners { get; } = []; /// void IInternalDisposableService.DisposeService() @@ -61,8 +62,18 @@ internal unsafe class AddonLifecycle : IInternalDisposableService /// The listener to register. internal void RegisterListener(AddonLifecycleEventListener listener) { - this.EventListeners.TryAdd(listener.EventType, [ listener ]); - this.EventListeners[listener.EventType].Add(listener); + if (!this.EventListeners.ContainsKey(listener.EventType)) + { + this.EventListeners.TryAdd(listener.EventType, []); + } + + // Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type + if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName)) + { + this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []); + } + + this.EventListeners[listener.EventType][listener.AddonName].Add(listener); } /// @@ -71,9 +82,12 @@ internal unsafe class AddonLifecycle : IInternalDisposableService /// The listener to unregister. internal void UnregisterListener(AddonLifecycleEventListener listener) { - if (this.EventListeners.TryGetValue(listener.EventType, out var listenerList)) + if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners)) { - listenerList.Remove(listener); + if (addonListeners.TryGetValue(listener.AddonName, out var addonListener)) + { + addonListener.Remove(listener); + } } } @@ -86,22 +100,37 @@ internal unsafe class AddonLifecycle : IInternalDisposableService internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "") { // Early return if we don't have any listeners of this type - if (!this.EventListeners.TryGetValue(eventType, out var listenerList)) return; + if (!this.EventListeners.TryGetValue(eventType, out var addonListeners)) return; - // Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better. - foreach (var listener in listenerList) + // Handle listeners for this event type that don't care which addon is triggering it + if (addonListeners.TryGetValue(string.Empty, out var globalListeners)) { - // Match on string.empty for listeners that want events for all addons. - if (!string.IsNullOrWhiteSpace(listener.AddonName) && !args.IsAddon(listener.AddonName)) - continue; - - try + foreach (var listener in globalListeners) { - listener.FunctionDelegate.Invoke(eventType, args); + try + { + listener.FunctionDelegate.Invoke(eventType, args); + } + catch (Exception e) + { + Log.Error(e, $"Exception in {blame} during {eventType} invoke, for global addon event listener."); + } } - catch (Exception e) + } + + // Handle listeners that are listening for this addon and event type specifically + if (addonListeners.TryGetValue(args.AddonName, out var addonListener)) + { + foreach (var listener in addonListener) { - Log.Error(e, $"Exception in {blame} during {eventType} invoke."); + try + { + listener.FunctionDelegate.Invoke(eventType, args); + } + catch (Exception e) + { + Log.Error(e, $"Exception in {blame} during {eventType} invoke, for specific addon {args.AddonName}."); + } } } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs index 73c4e540a..0f193556b 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonLifecycleWidget.cs @@ -2,7 +2,8 @@ using System.Diagnostics.CodeAnalysis; using Dalamud.Bindings.ImGui; using Dalamud.Game.Addon.Lifecycle; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Utility; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; @@ -57,35 +58,38 @@ public class AddonLifecycleWidget : IDataWindowWidget { if (!this.Ready) return; - foreach (var (listenerType, listeners) in this.AddonLifecycle.EventListeners) + foreach (var (eventType, addonListeners) in this.AddonLifecycle.EventListeners) { - if (ImGui.CollapsingHeader(listenerType.ToString())) + using var eventId = ImRaii.PushId(eventType.ToString()); + + if (ImGui.CollapsingHeader(eventType.ToString())) { - ImGui.Indent(); + using var eventIndent = ImRaii.PushIndent(); - if (listeners.Count == 0) + if (addonListeners.Count == 0) { - ImGui.Text("No Listeners Registered for Event"u8); + ImGui.Text("No Addons Registered for Event"u8); } - if (ImGui.BeginTable("AddonLifecycleListenersTable"u8, 2)) + foreach (var (addonName, listeners) in addonListeners) { - ImGui.TableSetupColumn("##AddonName"u8, ImGuiTableColumnFlags.WidthFixed, 100.0f * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("##MethodInvoke"u8, ImGuiTableColumnFlags.WidthStretch); + using var addonId = ImRaii.PushId(addonName); - foreach (var listener in listeners) + if (ImGui.CollapsingHeader(addonName.IsNullOrEmpty() ? "GLOBAL" : addonName)) { - ImGui.TableNextColumn(); - ImGui.Text(listener.AddonName is "" ? "GLOBAL" : listener.AddonName); + using var addonIndent = ImRaii.PushIndent(); - ImGui.TableNextColumn(); - ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType?.FullName ?? "Unknown Declaring Type"}::{listener.FunctionDelegate.Method.Name}"); + if (listeners.Count == 0) + { + ImGui.Text("No Listeners Registered for Event"u8); + } + + foreach (var listener in listeners) + { + ImGui.Text($"{listener.FunctionDelegate.Method.DeclaringType?.FullName ?? "Unknown Declaring Type"}::{listener.FunctionDelegate.Method.Name}"); + } } - - ImGui.EndTable(); } - - ImGui.Unindent(); } } }