diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-05-11 17:59:44 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-05-11 17:59:44 -0400 |
commit | 45f674303454fb27327a0404ed403ac15ed04580 (patch) | |
tree | f51233799fd2d49073d2b29fe995400329f26523 /src/SMAPI/Framework | |
parent | d097825c84bbe7d4b4812d4948358dd22abd166a (diff) | |
download | SMAPI-45f674303454fb27327a0404ed403ac15ed04580.tar.gz SMAPI-45f674303454fb27327a0404ed403ac15ed04580.tar.bz2 SMAPI-45f674303454fb27327a0404ed403ac15ed04580.zip |
optimize raising events for the most common cases
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r-- | src/SMAPI/Framework/Events/ManagedEvent.cs | 81 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 3 |
2 files changed, 57 insertions, 27 deletions
diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index a16beb77..abeea098 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -20,14 +20,17 @@ namespace StardewModdingAPI.Framework.Events /// <summary>The underlying event handlers.</summary> private readonly List<ManagedEventHandler<TEventArgs>> Handlers = new(); - /// <summary>A cached snapshot of <see cref="Handlers"/>, or <c>null</c> to rebuild it next raise.</summary> + /// <summary>A cached snapshot of the <see cref="Handlers"/> sorted by event priority, or <c>null</c> to rebuild it next raise.</summary> private ManagedEventHandler<TEventArgs>[]? CachedHandlers = Array.Empty<ManagedEventHandler<TEventArgs>>(); /// <summary>The total number of event handlers registered for this events, regardless of whether they're still registered.</summary> private int RegistrationIndex; - /// <summary>Whether new handlers were added since the last raise.</summary> - private bool HasNewHandlers; + /// <summary>Whether handlers were removed since the last raise.</summary> + private bool HasRemovedHandlers; + + /// <summary>Whether any of the handlers have a custom priority.</summary> + private bool HasPriorities; /********* @@ -67,7 +70,7 @@ namespace StardewModdingAPI.Framework.Events this.Handlers.Add(managedHandler); this.CachedHandlers = null; - this.HasNewHandlers = true; + this.HasPriorities |= priority != EventPriority.Normal; } } @@ -85,6 +88,7 @@ namespace StardewModdingAPI.Framework.Events this.Handlers.RemoveAt(i); this.CachedHandlers = null; + this.HasRemovedHandlers = true; break; } } @@ -92,10 +96,24 @@ namespace StardewModdingAPI.Framework.Events /// <summary>Raise the event and notify all handlers.</summary> /// <param name="args">The event arguments to pass.</param> - /// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param> - public void Raise(TEventArgs args, Func<IModMetadata, bool>? match = null) + public void Raise(TEventArgs args) { - this.Raise((_, invoke) => invoke(args), match); + // skip if no handlers + if (this.Handlers.Count == 0) + return; + + // raise event + foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers()) + { + try + { + handler.Handler(null, args); + } + catch (Exception ex) + { + this.LogError(handler, ex); + } + } } /// <summary>Raise the event and notify all handlers.</summary> @@ -107,31 +125,15 @@ namespace StardewModdingAPI.Framework.Events if (this.Handlers.Count == 0) return; - // update cached data - // (This is debounced here to avoid repeatedly sorting when handlers are added/removed, - // and keeping a separate cached list allows changes during enumeration.) - var handlers = this.CachedHandlers; // iterate local copy in case a mod adds/removes a handler while handling the event, which will set this field to null - if (handlers == null) - { - lock (this.Handlers) - { - if (this.HasNewHandlers && this.Handlers.Any(p => p.Priority != EventPriority.Normal)) - this.Handlers.Sort(); - - this.CachedHandlers = handlers = this.Handlers.ToArray(); - this.HasNewHandlers = false; - } - } - // raise event - foreach (ManagedEventHandler<TEventArgs> handler in handlers) + foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers()) { if (match != null && !match(handler.SourceMod)) continue; try { - invoke(handler.SourceMod, args => handler.Handler.Invoke(null, args)); + invoke(handler.SourceMod, args => handler.Handler(null, args)); } catch (Exception ex) { @@ -147,9 +149,36 @@ namespace StardewModdingAPI.Framework.Events /// <summary>Log an exception from an event handler.</summary> /// <param name="handler">The event handler instance.</param> /// <param name="ex">The exception that was raised.</param> - protected void LogError(ManagedEventHandler<TEventArgs> handler, Exception ex) + private void LogError(ManagedEventHandler<TEventArgs> handler, Exception ex) { handler.SourceMod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error); } + + /// <summary>Get cached copy of the sorted handlers to invoke.</summary> + /// <remarks>This returns the handlers sorted by priority, and allows iterating the list even if a mod adds/removes handlers while handling it. This is debounced when requested to avoid repeatedly sorting when handlers are added/removed.</remarks> + private ManagedEventHandler<TEventArgs>[] GetHandlers() + { + ManagedEventHandler<TEventArgs>[]? handlers = this.CachedHandlers; + + if (handlers == null) + { + lock (this.Handlers) + { + // recheck priorities + if (this.HasRemovedHandlers) + this.HasPriorities = this.Handlers.Any(p => p.Priority != EventPriority.Normal); + + // sort by priority if needed + if (this.HasPriorities) + this.Handlers.Sort(); + + // update cache + this.CachedHandlers = handlers = this.Handlers.ToArray(); + this.HasRemovedHandlers = false; + } + } + + return handlers; + } } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index f882682e..c3f0c05f 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1231,7 +1231,8 @@ namespace StardewModdingAPI.Framework modIDs.Remove(message.FromModID); // don't send a broadcast back to the sender // raise events - this.EventManager.ModMessageReceived.Raise(new ModMessageReceivedEventArgs(message, this.Toolkit.JsonHelper), mod => modIDs.Contains(mod.Manifest.UniqueID)); + var args = new ModMessageReceivedEventArgs(message, this.Toolkit.JsonHelper); + this.EventManager.ModMessageReceived.Raise((_, invoke) => invoke(args), mod => modIDs.Contains(mod.Manifest.UniqueID)); } /// <summary>Constructor a content manager to read game content files.</summary> |