diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 940973ad8..5488bef2a 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -9,6 +9,7 @@ using Dalamud.Game.Network.Internal; using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; +using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; using Serilog; @@ -145,7 +146,7 @@ namespace Dalamud.Game.ClientState private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) { this.TerritoryType = terriType; - this.TerritoryChanged?.Invoke(this, terriType); + this.TerritoryChanged?.Raise(this, terriType); Log.Debug("TerritoryType changed: {0}", terriType); @@ -154,7 +155,7 @@ namespace Dalamud.Game.ClientState private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e) { - this.CfPop?.Invoke(this, e); + this.CfPop?.Raise(this, e); } private void FrameworkOnOnUpdateEvent(Framework framework1) @@ -171,7 +172,7 @@ namespace Dalamud.Game.ClientState Log.Debug("Is login"); this.lastConditionNone = false; this.IsLoggedIn = true; - this.Login?.Invoke(this, null); + this.Login?.Raise(this, null); gameGui.ResetUiHideState(); } @@ -180,7 +181,7 @@ namespace Dalamud.Game.ClientState Log.Debug("Is logout"); this.lastConditionNone = true; this.IsLoggedIn = false; - this.Logout?.Invoke(this, null); + this.Logout?.Raise(this, null); gameGui.ResetUiHideState(); } @@ -193,11 +194,11 @@ namespace Dalamud.Game.ClientState if (this.IsPvP) { - this.EnterPvP?.Invoke(); + this.EnterPvP?.Raise(); } else { - this.LeavePvP?.Invoke(); + this.LeavePvP?.Raise(); } } } diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index e6a61611c..921fa1ce4 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -362,63 +362,64 @@ namespace Dalamud.Game this.LastUpdate = DateTime.Now; this.LastUpdateUTC = DateTime.UtcNow; - try + this.runOnNextTickTaskList.RemoveAll(x => x.Run()); + + if (StatsEnabled && this.Update != null) { - this.runOnNextTickTaskList.RemoveAll(x => x.Run()); + // Stat Tracking for Framework Updates + var invokeList = this.Update.GetInvocationList(); + var notUpdated = StatsHistory.Keys.ToList(); - if (StatsEnabled && this.Update != null) + // Individually invoke OnUpdate handlers and time them. + foreach (var d in invokeList) { - // Stat Tracking for Framework Updates - var invokeList = this.Update.GetInvocationList(); - var notUpdated = StatsHistory.Keys.ToList(); - - // Individually invoke OnUpdate handlers and time them. - foreach (var d in invokeList) + statsStopwatch.Restart(); + try { - statsStopwatch.Restart(); d.Method.Invoke(d.Target, new object[] { this }); - statsStopwatch.Stop(); - - var key = $"{d.Target}::{d.Method.Name}"; - if (notUpdated.Contains(key)) - notUpdated.Remove(key); - - if (!StatsHistory.ContainsKey(key)) - StatsHistory.Add(key, new List()); - - StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); - - if (StatsHistory[key].Count > 1000) - { - StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); - } + } + catch (Exception ex) + { + Log.Error(ex, "Exception while dispatching Framework::Update event."); } - // Cleanup handlers that are no longer being called - foreach (var key in notUpdated) + statsStopwatch.Stop(); + + var key = $"{d.Target}::{d.Method.Name}"; + if (notUpdated.Contains(key)) + notUpdated.Remove(key); + + if (!StatsHistory.ContainsKey(key)) + StatsHistory.Add(key, new List()); + + StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); + + if (StatsHistory[key].Count > 1000) { - if (StatsHistory[key].Count > 0) - { - StatsHistory[key].RemoveAt(0); - } - else - { - StatsHistory.Remove(key); - } + StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); } } - else + + // Cleanup handlers that are no longer being called + foreach (var key in notUpdated) { - this.Update?.Invoke(this); + if (StatsHistory[key].Count > 0) + { + StatsHistory[key].RemoveAt(0); + } + else + { + StatsHistory.Remove(key); + } } } - catch (Exception ex) + else { - Log.Error(ex, "Exception while dispatching Framework::Update event."); + this.Update?.Raise(this); } } - original: + original: return this.updateHook.OriginalDisposeSafe(framework); } diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index e8b36ecf9..e8f2b68b1 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -466,14 +466,7 @@ namespace Dalamud.Game.Gui var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); this.HoveredItem = itemId; - try - { - this.HoveredItemChanged?.Invoke(this, itemId); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } + this.HoveredItemChanged?.Raise(this, itemId); Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X")); } @@ -515,14 +508,7 @@ namespace Dalamud.Game.Gui this.HoveredAction.ActionKind = actionKind; this.HoveredAction.BaseActionID = actionId; this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C); - try - { - this.HoveredActionChanged?.Invoke(this, this.HoveredAction); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } + this.HoveredActionChanged?.Raise(this, this.HoveredAction); Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X")); } @@ -562,14 +548,7 @@ namespace Dalamud.Game.Gui // TODO(goat): We should read this from memory directly, instead of relying on catching every toggle. this.GameUiHidden = !this.GameUiHidden; - try - { - this.UiHideToggled?.Invoke(this, this.GameUiHidden); - } - catch (Exception ex) - { - Log.Error(ex, "Error on OnUiHideToggled event dispatch"); - } + this.UiHideToggled?.Raise(this, this.GameUiHidden); Log.Debug("UiHide toggled: {0}", this.GameUiHidden); diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index a5f5f8bb3..b1a9c4aeb 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -288,7 +288,7 @@ namespace Dalamud.Game.Network.Internal Service.GetNullable()?.Print($"Duty pop: {cfcName}"); } - this.CfPop?.Invoke(this, cfCondition); + this.CfPop?.Raise(this, cfCondition); }).ContinueWith((task) => Log.Error(task.Exception, "CfPop.Invoke failed."), TaskContinuationOptions.OnlyOnFaulted); } } diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 27414b71e..087787c1d 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -773,7 +773,7 @@ namespace Dalamud.Interface.Internal var customFontFirstConfigIndex = ioFonts.ConfigData.Size; Log.Verbose("[FONT] Invoke OnBuildFonts"); - this.BuildFonts?.Invoke(); + this.BuildFonts?.Raise(); Log.Verbose("[FONT] OnBuildFonts OK!"); for (int i = customFontFirstConfigIndex, i_ = ioFonts.ConfigData.Size; i < i_; i++) @@ -881,7 +881,7 @@ namespace Dalamud.Interface.Internal } Log.Verbose("[FONT] Invoke OnAfterBuildFonts"); - this.AfterBuildFonts?.Invoke(); + this.AfterBuildFonts?.Raise(); Log.Verbose("[FONT] OnAfterBuildFonts OK!"); if (ioFonts.Fonts[0].NativePtr != DefaultFont.NativePtr) @@ -978,7 +978,7 @@ namespace Dalamud.Interface.Internal Log.Verbose($"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}"); #endif - this.ResizeBuffers?.Invoke(); + this.ResizeBuffers?.Raise(); // We have to ensure we're working with the main swapchain, // as viewports might be resizing as well @@ -1105,7 +1105,7 @@ namespace Dalamud.Interface.Internal var snap = ImGuiManagedAsserts.GetSnapshot(); if (this.IsDispatchingEvents) - this.Draw?.Invoke(); + this.Draw?.Raise(); ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 3a8927da9..4849ee80b 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -10,6 +10,7 @@ using Dalamud.Interface.GameFonts; using Dalamud.Interface.Internal; using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.Notifications; +using Dalamud.Utility; using ImGuiNET; using ImGuiScene; using Serilog; @@ -392,7 +393,7 @@ namespace Dalamud.Interface /// internal void OpenConfig() { - this.OpenConfigUi?.Invoke(); + this.OpenConfigUi?.Raise(); } /// @@ -400,7 +401,7 @@ namespace Dalamud.Interface /// internal void NotifyHideUi() { - this.HideUi?.Invoke(); + this.HideUi?.Raise(); } /// @@ -408,7 +409,7 @@ namespace Dalamud.Interface /// internal void NotifyShowUi() { - this.ShowUi?.Invoke(); + this.ShowUi?.Raise(); } private void OnDraw() @@ -428,7 +429,7 @@ namespace Dalamud.Interface if (!this.lastFrameUiHideState) { this.lastFrameUiHideState = true; - this.HideUi?.Invoke(); + this.HideUi?.Raise(); } return; @@ -437,7 +438,7 @@ namespace Dalamud.Interface if (this.lastFrameUiHideState) { this.lastFrameUiHideState = false; - this.ShowUi?.Invoke(); + this.ShowUi?.Raise(); } if (!this.interfaceManager.FontsReady) @@ -470,7 +471,7 @@ namespace Dalamud.Interface try { - this.Draw?.Invoke(); + this.Draw?.Raise(); } catch (Exception ex) { @@ -503,17 +504,17 @@ namespace Dalamud.Interface private void OnBuildFonts() { - this.BuildFonts?.Invoke(); + this.BuildFonts?.Raise(); } private void OnAfterBuildFonts() { - this.AfterBuildFonts?.Invoke(); + this.AfterBuildFonts?.Raise(); } private void OnResizeBuffers() { - this.ResizeBuffers?.Invoke(); + this.ResizeBuffers?.Raise(); } } } diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index d432c69d6..a5b235635 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -1262,28 +1262,14 @@ internal partial class PluginManager : IDisposable, IServiceType { this.DetectAvailablePluginUpdates(); - try - { - this.OnAvailablePluginsChanged?.Invoke(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error notifying {nameof(this.OnAvailablePluginsChanged)}"); - } + this.OnAvailablePluginsChanged?.Raise(); } private void NotifyInstalledPluginsChanged() { this.DetectAvailablePluginUpdates(); - try - { - this.OnInstalledPluginsChanged?.Invoke(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error notifying {nameof(this.OnInstalledPluginsChanged)}"); - } + this.OnInstalledPluginsChanged?.Raise(); } private static class Locs diff --git a/Dalamud/Utility/EventHandlerExtensions.cs b/Dalamud/Utility/EventHandlerExtensions.cs new file mode 100644 index 000000000..8812034ef --- /dev/null +++ b/Dalamud/Utility/EventHandlerExtensions.cs @@ -0,0 +1,98 @@ +using System; +using System.Linq; + +using Dalamud.Game; +using Serilog; + +using static Dalamud.Game.Framework; + +namespace Dalamud.Utility +{ + /// + /// Extensions for Events. + /// + internal static class EventHandlerExtensions + { + /// + /// Replacement for Invoke() on EventHandlers to catch exceptions that stop event propagation in case + /// of a thrown Exception inside of an invocation. + /// + /// The EventHandler in question. + /// Default sender for Invoke equivalent. + /// Default EventArgs for Invoke equivalent. + public static void Raise(this EventHandler eh, object sender, EventArgs e) + { + if (eh == null) + return; + + foreach (var handler in eh.GetInvocationList().Cast()) + { + HandleRaise(() => handler(sender, e)); + } + } + + /// + /// Replacement for Invoke() on generic EventHandlers to catch exceptions that stop event propagation in case + /// of a thrown Exception inside of an invocation. + /// + /// The EventHandler in question. + /// Default sender for Invoke equivalent. + /// Default EventArgs for Invoke equivalent. + /// Type of EventArgs. + public static void Raise(this EventHandler eh, object sender, T e) + { + if (eh == null) + return; + + foreach (var handler in eh.GetInvocationList().Cast>()) + { + HandleRaise(() => handler(sender, e)); + } + } + + /// + /// Replacement for Invoke() on event Actions to catch exceptions that stop event propagation in case + /// of a thrown Exception inside of an invocation. + /// + /// The Action in question. + public static void Raise(this Action act) + { + if (act == null) + return; + + foreach (var action in act.GetInvocationList().Cast()) + { + HandleRaise(action); + } + } + + /// + /// Replacement for Invoke() on OnUpdateDelegate to catch exceptions that stop event propagation in case + /// of a thrown Exception inside of an invocation. + /// + /// The OnUpdateDelegate in question. + /// Framework to be passed on to OnUpdateDelegate. + public static void Raise(this OnUpdateDelegate updateDelegate, Framework framework) + { + if (updateDelegate == null) + return; + + foreach (var action in updateDelegate.GetInvocationList().Cast()) + { + HandleRaise(() => action(framework)); + } + } + + private static void HandleRaise(Action act) + { + try + { + act(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception during raise of {handler}", act.Method); + } + } + } +}