From 8c8ec6a4572fd3e59b738fc7545fcddc764f4519 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 10 May 2022 23:05:24 -0400 Subject: remove unused IsPerformanceCritical event field --- src/SMAPI/Framework/Events/ManagedEvent.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'src/SMAPI/Framework/Events/ManagedEvent.cs') diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 4b8a770d..a16beb77 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -36,9 +36,6 @@ namespace StardewModdingAPI.Framework.Events /// public string EventName { get; } - /// - public bool IsPerformanceCritical { get; } - /********* ** Public methods @@ -46,12 +43,10 @@ namespace StardewModdingAPI.Framework.Events /// Construct an instance. /// A human-readable name for the event. /// The mod registry with which to identify mods. - /// Whether the event is typically called at least once per second. - public ManagedEvent(string eventName, ModRegistry modRegistry, bool isPerformanceCritical = false) + public ManagedEvent(string eventName, ModRegistry modRegistry) { this.EventName = eventName; this.ModRegistry = modRegistry; - this.IsPerformanceCritical = isPerformanceCritical; } /// Get whether anything is listening to the event. -- cgit From 45f674303454fb27327a0404ed403ac15ed04580 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 11 May 2022 17:59:44 -0400 Subject: optimize raising events for the most common cases --- docs/release-notes.md | 3 ++ src/SMAPI/Framework/Events/ManagedEvent.cs | 81 ++++++++++++++++++++---------- src/SMAPI/Framework/SCore.cs | 3 +- 3 files changed, 60 insertions(+), 27 deletions(-) (limited to 'src/SMAPI/Framework/Events/ManagedEvent.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index d66fea5d..7ccd466a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,9 @@ # Release notes ## Upcoming release +* For players: + * Further improved performance in some cases. + * For mod authors: * Fixed error when loading a `.xnb` file through the old content API without the file extension. * Fixed asset propagation for player sprites not fully updating recolor masks in some cases. 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 /// The underlying event handlers. private readonly List> Handlers = new(); - /// A cached snapshot of , or null to rebuild it next raise. + /// A cached snapshot of the sorted by event priority, or null to rebuild it next raise. private ManagedEventHandler[]? CachedHandlers = Array.Empty>(); /// The total number of event handlers registered for this events, regardless of whether they're still registered. private int RegistrationIndex; - /// Whether new handlers were added since the last raise. - private bool HasNewHandlers; + /// Whether handlers were removed since the last raise. + private bool HasRemovedHandlers; + + /// Whether any of the handlers have a custom priority. + 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 /// Raise the event and notify all handlers. /// The event arguments to pass. - /// A lambda which returns true if the event should be raised for the given mod. - public void Raise(TEventArgs args, Func? 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 handler in this.GetHandlers()) + { + try + { + handler.Handler(null, args); + } + catch (Exception ex) + { + this.LogError(handler, ex); + } + } } /// Raise the event and notify all handlers. @@ -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 handler in handlers) + foreach (ManagedEventHandler 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 /// Log an exception from an event handler. /// The event handler instance. /// The exception that was raised. - protected void LogError(ManagedEventHandler handler, Exception ex) + private void LogError(ManagedEventHandler handler, Exception ex) { handler.SourceMod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error); } + + /// Get cached copy of the sorted handlers to invoke. + /// 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. + private ManagedEventHandler[] GetHandlers() + { + ManagedEventHandler[]? 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)); } /// Constructor a content manager to read game content files. -- cgit From cae1063ad99a29aed3a0c162bad1d2842376c608 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 11 May 2022 19:15:22 -0400 Subject: move filtering only used in one place out of managed event --- src/SMAPI/Framework/Events/ManagedEvent.cs | 6 +----- src/SMAPI/Framework/SCore.cs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) (limited to 'src/SMAPI/Framework/Events/ManagedEvent.cs') diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index abeea098..a34106ce 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -118,8 +118,7 @@ namespace StardewModdingAPI.Framework.Events /// Raise the event and notify all handlers. /// Invoke an event handler. This receives the mod which registered the handler, and should invoke the callback with the event arguments to pass it. - /// A lambda which returns true if the event should be raised for the given mod. - public void Raise(Action> invoke, Func? match = null) + public void Raise(Action> invoke) { // skip if no handlers if (this.Handlers.Count == 0) @@ -128,9 +127,6 @@ namespace StardewModdingAPI.Framework.Events // raise event foreach (ManagedEventHandler handler in this.GetHandlers()) { - if (match != null && !match(handler.SourceMod)) - continue; - try { invoke(handler.SourceMod, args => handler.Handler(null, args)); diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index c3f0c05f..1fea6d69 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1231,8 +1231,17 @@ namespace StardewModdingAPI.Framework modIDs.Remove(message.FromModID); // don't send a broadcast back to the sender // raise events - var args = new ModMessageReceivedEventArgs(message, this.Toolkit.JsonHelper); - this.EventManager.ModMessageReceived.Raise((_, invoke) => invoke(args), mod => modIDs.Contains(mod.Manifest.UniqueID)); + ModMessageReceivedEventArgs? args = null; + this.EventManager.ModMessageReceived.Raise( + invoke: (mod, invoke) => + { + if (modIDs.Contains(mod.Manifest.UniqueID)) + { + args ??= new(message, this.Toolkit.JsonHelper); + invoke(args); + } + } + ); } /// Constructor a content manager to read game content files. -- cgit From 077c897d53371d12c812be5145d917c59583d7cb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 11 May 2022 19:29:57 -0400 Subject: replace event.HasListeners() with property --- src/SMAPI/Framework/Events/IManagedEvent.cs | 3 +++ src/SMAPI/Framework/Events/ManagedEvent.cs | 11 +++++------ src/SMAPI/Framework/SCore.cs | 10 +++++----- src/SMAPI/Framework/SGame.cs | 4 ++-- 4 files changed, 15 insertions(+), 13 deletions(-) (limited to 'src/SMAPI/Framework/Events/ManagedEvent.cs') diff --git a/src/SMAPI/Framework/Events/IManagedEvent.cs b/src/SMAPI/Framework/Events/IManagedEvent.cs index 0ae8c55a..55994c04 100644 --- a/src/SMAPI/Framework/Events/IManagedEvent.cs +++ b/src/SMAPI/Framework/Events/IManagedEvent.cs @@ -8,5 +8,8 @@ namespace StardewModdingAPI.Framework.Events *********/ /// A human-readable name for the event. string EventName { get; } + + /// Whether any handlers are listening to the event. + bool HasListeners { get; } } } diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index a34106ce..8a3ca839 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -39,6 +39,9 @@ namespace StardewModdingAPI.Framework.Events /// public string EventName { get; } + /// + public bool HasListeners { get; private set; } + /********* ** Public methods @@ -52,12 +55,6 @@ namespace StardewModdingAPI.Framework.Events this.ModRegistry = modRegistry; } - /// Get whether anything is listening to the event. - public bool HasListeners() - { - return this.Handlers.Count > 0; - } - /// Add an event handler. /// The event handler. /// The mod which added the event handler. @@ -70,6 +67,7 @@ namespace StardewModdingAPI.Framework.Events this.Handlers.Add(managedHandler); this.CachedHandlers = null; + this.HasListeners = true; this.HasPriorities |= priority != EventPriority.Normal; } } @@ -88,6 +86,7 @@ namespace StardewModdingAPI.Framework.Events this.Handlers.RemoveAt(i); this.CachedHandlers = null; + this.HasListeners = this.Handlers.Count != 0; this.HasRemovedHandlers = true; break; } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 667fcf0f..c1e03634 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -886,7 +886,7 @@ namespace StardewModdingAPI.Framework bool raiseWorldEvents = !state.SaveID.IsChanged; // don't report changes from unloaded => loaded // location list changes - if (state.Locations.LocationList.IsChanged && (events.LocationListChanged.HasListeners() || verbose)) + if (state.Locations.LocationList.IsChanged && (events.LocationListChanged.HasListeners || verbose)) { var added = state.Locations.LocationList.Added.ToArray(); var removed = state.Locations.LocationList.Removed.ToArray(); @@ -929,7 +929,7 @@ namespace StardewModdingAPI.Framework events.ObjectListChanged.Raise(new ObjectListChangedEventArgs(location, locState.Objects.Added, locState.Objects.Removed)); // chest items changed - if (events.ChestInventoryChanged.HasListeners()) + if (events.ChestInventoryChanged.HasListeners) { foreach (var pair in locState.ChestItems) { @@ -1077,7 +1077,7 @@ namespace StardewModdingAPI.Framework } // raise event - if (this.EventManager.LocaleChanged.HasListeners()) + if (this.EventManager.LocaleChanged.HasListeners) { this.EventManager.LocaleChanged.Raise( new LocaleChangedEventArgs( @@ -1140,7 +1140,7 @@ namespace StardewModdingAPI.Framework /// The asset name that was loaded. private void OnAssetLoaded(IContentManager contentManager, IAssetName assetName) { - if (this.EventManager.AssetReady.HasListeners()) + if (this.EventManager.AssetReady.HasListeners) this.EventManager.AssetReady.Raise(new AssetReadyEventArgs(assetName, assetName.GetBaseAssetName())); } @@ -1148,7 +1148,7 @@ namespace StardewModdingAPI.Framework /// The invalidated asset names. private void OnAssetsInvalidated(IList assetNames) { - if (this.EventManager.AssetsInvalidated.HasListeners()) + if (this.EventManager.AssetsInvalidated.HasListeners) this.EventManager.AssetsInvalidated.Raise(new AssetsInvalidatedEventArgs(assetNames, assetNames.Select(p => p.GetBaseAssetName()))); } diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 0a8a068f..38043e1c 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -353,7 +353,7 @@ namespace StardewModdingAPI.Framework } if (Game1.currentMinigame != null) { - if (events.Rendering.HasListeners()) + if (events.Rendering.HasListeners) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); events.Rendering.RaiseEmpty(); @@ -372,7 +372,7 @@ namespace StardewModdingAPI.Framework Game1.PushUIMode(); this.drawOverlays(Game1.spriteBatch); Game1.PopUIMode(); - if (events.Rendered.HasListeners()) + if (events.Rendered.HasListeners) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null); events.Rendered.RaiseEmpty(); -- cgit