From b395e92faae0a197a2d1c2e10e835e38fcc6502c Mon Sep 17 00:00:00 2001 From: Chase W Date: Mon, 15 Jun 2020 15:28:03 -0400 Subject: [PATCH 1/4] Implemented event priority attribute --- src/SMAPI/Events/EventPriority.cs | 29 ++++++++++++++ src/SMAPI/Events/EventPriorityAttribute.cs | 29 ++++++++++++++ src/SMAPI/Framework/Events/ManagedEvent.cs | 45 +++++++++++++++++----- 3 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 src/SMAPI/Events/EventPriority.cs create mode 100644 src/SMAPI/Events/EventPriorityAttribute.cs diff --git a/src/SMAPI/Events/EventPriority.cs b/src/SMAPI/Events/EventPriority.cs new file mode 100644 index 00000000..17f5fbb7 --- /dev/null +++ b/src/SMAPI/Events/EventPriority.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StardewModdingAPI.Events +{ + /// + /// Event priority for method handlers. + /// + public enum EventPriority + { + /// + /// Low priority. + /// + Low = 3, + + /// + /// Normal priority. This is the default. + /// + Normal = 2, + + /// + /// High priority. + /// + High = 1, + } +} diff --git a/src/SMAPI/Events/EventPriorityAttribute.cs b/src/SMAPI/Events/EventPriorityAttribute.cs new file mode 100644 index 00000000..c5683931 --- /dev/null +++ b/src/SMAPI/Events/EventPriorityAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StardewModdingAPI.Events +{ + /// + /// An attribute for controlling event priority of an event handler. + /// + [AttributeUsage(AttributeTargets.Method)] + public class EventPriorityAttribute : System.Attribute + { + /// + /// The priority for the method marked by this attribute. + /// + public EventPriority Priority { get; } + + /// + /// Constructor. + /// + /// The priority for method marked by this attribute. + public EventPriorityAttribute( EventPriority priority ) + { + this.Priority = priority; + } + } +} diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 118b73ac..172b25c0 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -1,6 +1,9 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using StardewModdingAPI.Events; using StardewModdingAPI.Framework.PerformanceMonitoring; namespace StardewModdingAPI.Framework.Events @@ -13,7 +16,7 @@ namespace StardewModdingAPI.Framework.Events ** Fields *********/ /// The underlying event. - private event EventHandler Event; + private IList> EventHandlers = new List>(); /// Writes messages to the log. private readonly IMonitor Monitor; @@ -77,23 +80,23 @@ namespace StardewModdingAPI.Framework.Events /// The mod which added the event handler. public void Add(EventHandler handler, IModMetadata mod) { - this.Event += handler; - this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast>()); + this.EventHandlers.Add(handler); + this.AddTracking(mod, handler, this.EventHandlers); } /// Remove an event handler. /// The event handler. public void Remove(EventHandler handler) { - this.Event -= handler; - this.RemoveTracking(handler, this.Event?.GetInvocationList().Cast>()); + this.EventHandlers.Remove(handler); + this.RemoveTracking(handler, this.EventHandlers); } /// Raise the event and notify all handlers. /// The event arguments to pass. public void Raise(TEventArgs args) { - if (this.Event == null) + if (this.EventHandlers.Count == 0) return; @@ -118,7 +121,7 @@ namespace StardewModdingAPI.Framework.Events /// A lambda which returns true if the event should be raised for the given mod. public void RaiseForMods(TEventArgs args, Func match) { - if (this.Event == null) + if (this.EventHandlers.Count == 0) return; foreach (EventHandler handler in this.CachedInvocationList) @@ -154,6 +157,30 @@ namespace StardewModdingAPI.Framework.Events : mod.DisplayName; } + /// + /// Get the event priority of an event handler. + /// + /// The event handler to get the priority of. + /// The event priority of the event handler. + private EventPriority GetPriorityOfHandler(EventHandler handler) + { + CustomAttributeData attr = handler.Method.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(EventPriorityAttribute)); + if (attr == null) + return EventPriority.Normal; + return (EventPriority) attr.ConstructorArguments[0].Value; + } + + /// + /// Sort an invocation list by its priority. + /// + /// The invocation list. + /// An array of the event handlers sorted by their priority. + private EventHandler[] GetCachedInvocationList(IEnumerable> invocationList ) + { + EventHandler[] handlers = invocationList?.ToArray() ?? new EventHandler[0]; + return handlers.OrderBy((h1) => this.GetPriorityOfHandler(h1)).ToArray(); + } + /// Track an event handler. /// The mod which added the handler. /// The event handler. @@ -161,7 +188,7 @@ namespace StardewModdingAPI.Framework.Events protected void AddTracking(IModMetadata mod, EventHandler handler, IEnumerable> invocationList) { this.SourceMods[handler] = mod; - this.CachedInvocationList = invocationList?.ToArray() ?? new EventHandler[0]; + this.CachedInvocationList = this.GetCachedInvocationList(invocationList); } /// Remove tracking for an event handler. @@ -169,7 +196,7 @@ namespace StardewModdingAPI.Framework.Events /// The updated event invocation list. protected void RemoveTracking(EventHandler handler, IEnumerable> invocationList) { - this.CachedInvocationList = invocationList?.ToArray() ?? new EventHandler[0]; + this.CachedInvocationList = this.GetCachedInvocationList(invocationList); if (!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) this.SourceMods.Remove(handler); } From fc29fe918a89623544b011c76217aa1ea1975d00 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 15 Jun 2020 18:58:05 -0400 Subject: [PATCH 2/4] refactor & optimize event code a bit, drop old support for unknown event handlers --- src/SMAPI/Events/EventPriority.cs | 24 +-- src/SMAPI/Events/EventPriorityAttribute.cs | 31 ++-- src/SMAPI/Framework/Events/EventManager.cs | 5 +- src/SMAPI/Framework/Events/ManagedEvent.cs | 138 ++++++------------ .../Framework/Events/ManagedEventHandler.cs | 62 ++++++++ .../Framework/Events/ModDisplayEvents.cs | 20 +-- .../Framework/Events/ModGameLoopEvents.cs | 28 ++-- src/SMAPI/Framework/Events/ModInputEvents.cs | 8 +- .../Framework/Events/ModMultiplayerEvents.cs | 6 +- src/SMAPI/Framework/Events/ModPlayerEvents.cs | 6 +- .../Framework/Events/ModSpecialisedEvents.cs | 6 +- src/SMAPI/Framework/Events/ModWorldEvents.cs | 8 +- src/SMAPI/Framework/SCore.cs | 2 +- 13 files changed, 166 insertions(+), 178 deletions(-) create mode 100644 src/SMAPI/Framework/Events/ManagedEventHandler.cs diff --git a/src/SMAPI/Events/EventPriority.cs b/src/SMAPI/Events/EventPriority.cs index 17f5fbb7..e1fb00ac 100644 --- a/src/SMAPI/Events/EventPriority.cs +++ b/src/SMAPI/Events/EventPriority.cs @@ -1,29 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace StardewModdingAPI.Events { - /// - /// Event priority for method handlers. - /// + /// The event priorities for method handlers. public enum EventPriority { - /// - /// Low priority. - /// + /// Low priority. Low = 3, - /// - /// Normal priority. This is the default. - /// + /// The default priority. Normal = 2, - /// - /// High priority. - /// - High = 1, + /// High priority. + High = 1 } } diff --git a/src/SMAPI/Events/EventPriorityAttribute.cs b/src/SMAPI/Events/EventPriorityAttribute.cs index c5683931..207e7862 100644 --- a/src/SMAPI/Events/EventPriorityAttribute.cs +++ b/src/SMAPI/Events/EventPriorityAttribute.cs @@ -1,27 +1,24 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace StardewModdingAPI.Events { - /// - /// An attribute for controlling event priority of an event handler. - /// + /// An attribute which specifies the priority for an event handler. [AttributeUsage(AttributeTargets.Method)] - public class EventPriorityAttribute : System.Attribute + public class EventPriorityAttribute : Attribute { - /// - /// The priority for the method marked by this attribute. - /// - public EventPriority Priority { get; } + /********* + ** Accessors + *********/ + /// The event handler priority, relative to other handlers across all mods registered for this event. + internal EventPriority Priority { get; } - /// - /// Constructor. - /// - /// The priority for method marked by this attribute. - public EventPriorityAttribute( EventPriority priority ) + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The event handler priority, relative to other handlers across all mods registered for this event. Higher-priority handlers are notified before lower-priority handlers. + public EventPriorityAttribute(EventPriority priority) { this.Priority = priority; } diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index a9dfda97..538fde59 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -174,15 +174,14 @@ namespace StardewModdingAPI.Framework.Events ** Public methods *********/ /// Construct an instance. - /// Writes messages to the log. /// The mod registry with which to identify mods. /// Tracks performance metrics. - public EventManager(IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor) + public EventManager(ModRegistry modRegistry, PerformanceMonitor performanceMonitor) { // create shortcut initializers ManagedEvent ManageEventOf(string typeName, string eventName, bool isPerformanceCritical = false) { - return new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry, performanceMonitor, isPerformanceCritical); + return new ManagedEvent($"{typeName}.{eventName}", modRegistry, performanceMonitor, isPerformanceCritical); } // init events (new) diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 172b25c0..b0f0ae71 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -15,24 +14,24 @@ namespace StardewModdingAPI.Framework.Events /********* ** Fields *********/ - /// The underlying event. - private IList> EventHandlers = new List>(); - - /// Writes messages to the log. - private readonly IMonitor Monitor; + /// The underlying event handlers. + private readonly List> EventHandlers = new List>(); /// The mod registry with which to identify mods. protected readonly ModRegistry ModRegistry; - /// The display names for the mods which added each delegate. - private readonly IDictionary, IModMetadata> SourceMods = new Dictionary, IModMetadata>(); - - /// The cached invocation list. - private EventHandler[] CachedInvocationList; - /// Tracks performance metrics. private readonly PerformanceMonitor PerformanceMonitor; + /// The total number of event handlers registered for this events, regardless of whether they're still registered. + private int RegistrationIndex; + + /// Whether any registered event handlers have a custom priority value. + private bool HasCustomPriorities; + + /// Whether event handlers should be sorted before the next invocation. + private bool NeedsSort; + /********* ** Accessors @@ -49,14 +48,12 @@ namespace StardewModdingAPI.Framework.Events *********/ /// Construct an instance. /// A human-readable name for the event. - /// Writes messages to the log. /// The mod registry with which to identify mods. /// Tracks performance metrics. /// Whether the event is typically called at least once per second. - public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry, PerformanceMonitor performanceMonitor, bool isPerformanceCritical = false) + public ManagedEvent(string eventName, ModRegistry modRegistry, PerformanceMonitor performanceMonitor, bool isPerformanceCritical = false) { this.EventName = eventName; - this.Monitor = monitor; this.ModRegistry = modRegistry; this.PerformanceMonitor = performanceMonitor; this.IsPerformanceCritical = isPerformanceCritical; @@ -65,14 +62,7 @@ namespace StardewModdingAPI.Framework.Events /// Get whether anything is listening to the event. public bool HasListeners() { - return this.CachedInvocationList?.Length > 0; - } - - /// Add an event handler. - /// The event handler. - public void Add(EventHandler handler) - { - this.Add(handler, this.ModRegistry.GetFromStack()); + return this.EventHandlers.Count > 0; } /// Add an event handler. @@ -80,33 +70,46 @@ namespace StardewModdingAPI.Framework.Events /// The mod which added the event handler. public void Add(EventHandler handler, IModMetadata mod) { - this.EventHandlers.Add(handler); - this.AddTracking(mod, handler, this.EventHandlers); + EventPriority priority = handler.Method.GetCustomAttribute()?.Priority ?? EventPriority.Normal; + var managedHandler = new ManagedEventHandler(handler, this.RegistrationIndex++, priority, mod); + + this.EventHandlers.Add(managedHandler); + this.HasCustomPriorities = this.HasCustomPriorities || managedHandler.HasCustomPriority(); + + if (this.HasCustomPriorities) + this.NeedsSort = true; } /// Remove an event handler. /// The event handler. public void Remove(EventHandler handler) { - this.EventHandlers.Remove(handler); - this.RemoveTracking(handler, this.EventHandlers); + this.EventHandlers.RemoveAll(p => p.Handler == handler); + this.HasCustomPriorities = this.HasCustomPriorities && this.EventHandlers.Any(p => p.HasCustomPriority()); } /// Raise the event and notify all handlers. /// The event arguments to pass. public void Raise(TEventArgs args) { + // sort event handlers by priority + // (This is done here to avoid repeatedly sorting when handlers are added/removed.) + if (this.NeedsSort) + { + this.NeedsSort = false; + this.EventHandlers.Sort(); + } + + // raise if (this.EventHandlers.Count == 0) return; - - this.PerformanceMonitor.Track(this.EventName, () => { - foreach (EventHandler handler in this.CachedInvocationList) + foreach (ManagedEventHandler handler in this.EventHandlers) { try { - this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Invoke(null, args)); + this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Handler.Invoke(null, args)); } catch (Exception ex) { @@ -124,13 +127,13 @@ namespace StardewModdingAPI.Framework.Events if (this.EventHandlers.Count == 0) return; - foreach (EventHandler handler in this.CachedInvocationList) + foreach (ManagedEventHandler handler in this.EventHandlers) { - if (match(this.GetSourceMod(handler))) + if (match(handler.SourceMod)) { try { - handler.Invoke(null, args); + handler.Handler.Invoke(null, args); } catch (Exception ex) { @@ -146,80 +149,21 @@ namespace StardewModdingAPI.Framework.Events *********/ /// Get the mod name for a given event handler to display in performance monitoring reports. /// The event handler. - private string GetModNameForPerformanceCounters(EventHandler handler) + private string GetModNameForPerformanceCounters(ManagedEventHandler handler) { - IModMetadata mod = this.GetSourceMod(handler); - if (mod == null) - return Constants.GamePerformanceCounterName; + IModMetadata mod = handler.SourceMod; return mod.HasManifest() ? mod.Manifest.UniqueID : mod.DisplayName; } - /// - /// Get the event priority of an event handler. - /// - /// The event handler to get the priority of. - /// The event priority of the event handler. - private EventPriority GetPriorityOfHandler(EventHandler handler) - { - CustomAttributeData attr = handler.Method.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(EventPriorityAttribute)); - if (attr == null) - return EventPriority.Normal; - return (EventPriority) attr.ConstructorArguments[0].Value; - } - - /// - /// Sort an invocation list by its priority. - /// - /// The invocation list. - /// An array of the event handlers sorted by their priority. - private EventHandler[] GetCachedInvocationList(IEnumerable> invocationList ) - { - EventHandler[] handlers = invocationList?.ToArray() ?? new EventHandler[0]; - return handlers.OrderBy((h1) => this.GetPriorityOfHandler(h1)).ToArray(); - } - - /// Track an event handler. - /// The mod which added the handler. - /// The event handler. - /// The updated event invocation list. - protected void AddTracking(IModMetadata mod, EventHandler handler, IEnumerable> invocationList) - { - this.SourceMods[handler] = mod; - this.CachedInvocationList = this.GetCachedInvocationList(invocationList); - } - - /// Remove tracking for an event handler. - /// The event handler. - /// The updated event invocation list. - protected void RemoveTracking(EventHandler handler, IEnumerable> invocationList) - { - this.CachedInvocationList = this.GetCachedInvocationList(invocationList); - if (!this.CachedInvocationList.Contains(handler)) // don't remove if there's still a reference to the removed handler (e.g. it was added twice and removed once) - this.SourceMods.Remove(handler); - } - - /// Get the mod which registered the given event handler, if available. - /// The event handler. - protected IModMetadata GetSourceMod(EventHandler handler) - { - return this.SourceMods.TryGetValue(handler, out IModMetadata mod) - ? mod - : null; - } - /// Log an exception from an event handler. /// The event handler instance. /// The exception that was raised. - protected void LogError(EventHandler handler, Exception ex) + protected void LogError(ManagedEventHandler handler, Exception ex) { - IModMetadata mod = this.GetSourceMod(handler); - if (mod != null) - mod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error); - else - this.Monitor.Log($"A mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error); + handler.SourceMod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error); } } } diff --git a/src/SMAPI/Framework/Events/ManagedEventHandler.cs b/src/SMAPI/Framework/Events/ManagedEventHandler.cs new file mode 100644 index 00000000..87591f63 --- /dev/null +++ b/src/SMAPI/Framework/Events/ManagedEventHandler.cs @@ -0,0 +1,62 @@ +using System; +using StardewModdingAPI.Events; + +namespace StardewModdingAPI.Framework.Events +{ + /// An event handler wrapper which tracks metadata about an event handler. + /// The event arguments type. + internal class ManagedEventHandler : IComparable + { + /********* + ** Accessors + *********/ + /// The event handler method. + public EventHandler Handler { get; } + + /// The order in which the event handler was registered, relative to other handlers for this event. + public int RegistrationOrder { get; } + + /// The event handler priority, relative to other handlers for this event. + public EventPriority Priority { get; } + + /// The mod which registered the handler. + public IModMetadata SourceMod { get; set; } + + + /********* + ** Accessors + *********/ + /// Construct an instance. + /// The event handler method. + /// The order in which the event handler was registered, relative to other handlers for this event. + /// The event handler priority, relative to other handlers for this event. + /// The mod which registered the handler. + public ManagedEventHandler(EventHandler handler, int registrationOrder, EventPriority priority, IModMetadata sourceMod) + { + this.Handler = handler; + this.RegistrationOrder = registrationOrder; + this.Priority = priority; + this.SourceMod = sourceMod; + } + + /// Get whether the event handler has a custom priority value. + public bool HasCustomPriority() + { + return this.Priority != EventPriority.Normal; + } + + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// An object to compare with this instance. + /// is not the same type as this instance. + public int CompareTo(object obj) + { + if (!(obj is ManagedEventHandler other)) + throw new ArgumentException("Can't compare to an unrelated object type."); + + int priorityCompare = this.Priority.CompareTo(other.Priority); + return priorityCompare != 0 + ? priorityCompare + : this.RegistrationOrder.CompareTo(other.RegistrationOrder); + } + } +} diff --git a/src/SMAPI/Framework/Events/ModDisplayEvents.cs b/src/SMAPI/Framework/Events/ModDisplayEvents.cs index e383eec6..54d40dee 100644 --- a/src/SMAPI/Framework/Events/ModDisplayEvents.cs +++ b/src/SMAPI/Framework/Events/ModDisplayEvents.cs @@ -13,70 +13,70 @@ namespace StardewModdingAPI.Framework.Events /// Raised after a game menu is opened, closed, or replaced. public event EventHandler MenuChanged { - add => this.EventManager.MenuChanged.Add(value); + add => this.EventManager.MenuChanged.Add(value, this.Mod); remove => this.EventManager.MenuChanged.Remove(value); } /// Raised before the game draws anything to the screen in a draw tick, as soon as the sprite batch is opened. The sprite batch may be closed and reopened multiple times after this event is called, but it's only raised once per draw tick. This event isn't useful for drawing to the screen, since the game will draw over it. public event EventHandler Rendering { - add => this.EventManager.Rendering.Add(value); + add => this.EventManager.Rendering.Add(value, this.Mod); remove => this.EventManager.Rendering.Remove(value); } /// Raised after the game draws to the sprite patch in a draw tick, just before the final sprite batch is rendered to the screen. Since the game may open/close the sprite batch multiple times in a draw tick, the sprite batch may not contain everything being drawn and some things may already be rendered to the screen. Content drawn to the sprite batch at this point will be drawn over all vanilla content (including menus, HUD, and cursor). public event EventHandler Rendered { - add => this.EventManager.Rendered.Add(value); + add => this.EventManager.Rendered.Add(value, this.Mod); remove => this.EventManager.Rendered.Remove(value); } /// Raised before the game world is drawn to the screen. This event isn't useful for drawing to the screen, since the game will draw over it. public event EventHandler RenderingWorld { - add => this.EventManager.RenderingWorld.Add(value); + add => this.EventManager.RenderingWorld.Add(value, this.Mod); remove => this.EventManager.RenderingWorld.Remove(value); } /// Raised after the game world is drawn to the sprite patch, before it's rendered to the screen. Content drawn to the sprite batch at this point will be drawn over the world, but under any active menu, HUD elements, or cursor. public event EventHandler RenderedWorld { - add => this.EventManager.RenderedWorld.Add(value); + add => this.EventManager.RenderedWorld.Add(value, this.Mod); remove => this.EventManager.RenderedWorld.Remove(value); } /// When a menu is open ( isn't null), raised before that menu is drawn to the screen. This includes the game's internal menus like the title screen. Content drawn to the sprite batch at this point will appear under the menu. public event EventHandler RenderingActiveMenu { - add => this.EventManager.RenderingActiveMenu.Add(value); + add => this.EventManager.RenderingActiveMenu.Add(value, this.Mod); remove => this.EventManager.RenderingActiveMenu.Remove(value); } /// When a menu is open ( isn't null), raised after that menu is drawn to the sprite batch but before it's rendered to the screen. Content drawn to the sprite batch at this point will appear over the menu and menu cursor. public event EventHandler RenderedActiveMenu { - add => this.EventManager.RenderedActiveMenu.Add(value); + add => this.EventManager.RenderedActiveMenu.Add(value, this.Mod); remove => this.EventManager.RenderedActiveMenu.Remove(value); } /// Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The vanilla HUD may be hidden at this point (e.g. because a menu is open). Content drawn to the sprite batch at this point will appear under the HUD. public event EventHandler RenderingHud { - add => this.EventManager.RenderingHud.Add(value); + add => this.EventManager.RenderingHud.Add(value, this.Mod); remove => this.EventManager.RenderingHud.Remove(value); } /// Raised after drawing the HUD (item toolbar, clock, etc) to the sprite batch, but before it's rendered to the screen. The vanilla HUD may be hidden at this point (e.g. because a menu is open). Content drawn to the sprite batch at this point will appear over the HUD. public event EventHandler RenderedHud { - add => this.EventManager.RenderedHud.Add(value); + add => this.EventManager.RenderedHud.Add(value, this.Mod); remove => this.EventManager.RenderedHud.Remove(value); } /// Raised after the game window is resized. public event EventHandler WindowResized { - add => this.EventManager.WindowResized.Add(value); + add => this.EventManager.WindowResized.Add(value, this.Mod); remove => this.EventManager.WindowResized.Remove(value); } diff --git a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs index c15460fa..a0119bf8 100644 --- a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs +++ b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs @@ -12,84 +12,84 @@ namespace StardewModdingAPI.Framework.Events /// Raised after the game is launched, right before the first update tick. public event EventHandler GameLaunched { - add => this.EventManager.GameLaunched.Add(value); + add => this.EventManager.GameLaunched.Add(value, this.Mod); remove => this.EventManager.GameLaunched.Remove(value); } /// Raised before the game performs its overall update tick (≈60 times per second). public event EventHandler UpdateTicking { - add => this.EventManager.UpdateTicking.Add(value); + add => this.EventManager.UpdateTicking.Add(value, this.Mod); remove => this.EventManager.UpdateTicking.Remove(value); } /// Raised after the game performs its overall update tick (≈60 times per second). public event EventHandler UpdateTicked { - add => this.EventManager.UpdateTicked.Add(value); + add => this.EventManager.UpdateTicked.Add(value, this.Mod); remove => this.EventManager.UpdateTicked.Remove(value); } /// Raised once per second before the game state is updated. public event EventHandler OneSecondUpdateTicking { - add => this.EventManager.OneSecondUpdateTicking.Add(value); + add => this.EventManager.OneSecondUpdateTicking.Add(value, this.Mod); remove => this.EventManager.OneSecondUpdateTicking.Remove(value); } /// Raised once per second after the game state is updated. public event EventHandler OneSecondUpdateTicked { - add => this.EventManager.OneSecondUpdateTicked.Add(value); + add => this.EventManager.OneSecondUpdateTicked.Add(value, this.Mod); remove => this.EventManager.OneSecondUpdateTicked.Remove(value); } /// Raised before the game creates a new save file. public event EventHandler SaveCreating { - add => this.EventManager.SaveCreating.Add(value); + add => this.EventManager.SaveCreating.Add(value, this.Mod); remove => this.EventManager.SaveCreating.Remove(value); } /// Raised after the game finishes creating the save file. public event EventHandler SaveCreated { - add => this.EventManager.SaveCreated.Add(value); + add => this.EventManager.SaveCreated.Add(value, this.Mod); remove => this.EventManager.SaveCreated.Remove(value); } /// Raised before the game begins writes data to the save file. public event EventHandler Saving { - add => this.EventManager.Saving.Add(value); + add => this.EventManager.Saving.Add(value, this.Mod); remove => this.EventManager.Saving.Remove(value); } /// Raised after the game finishes writing data to the save file. public event EventHandler Saved { - add => this.EventManager.Saved.Add(value); + add => this.EventManager.Saved.Add(value, this.Mod); remove => this.EventManager.Saved.Remove(value); } /// Raised after the player loads a save slot and the world is initialized. public event EventHandler SaveLoaded { - add => this.EventManager.SaveLoaded.Add(value); + add => this.EventManager.SaveLoaded.Add(value, this.Mod); remove => this.EventManager.SaveLoaded.Remove(value); } /// Raised after the game begins a new day (including when the player loads a save). public event EventHandler DayStarted { - add => this.EventManager.DayStarted.Add(value); + add => this.EventManager.DayStarted.Add(value, this.Mod); remove => this.EventManager.DayStarted.Remove(value); } /// Raised before the game ends the current day. This happens before it starts setting up the next day and before . public event EventHandler DayEnding { - add => this.EventManager.DayEnding.Add(value); + add => this.EventManager.DayEnding.Add(value, this.Mod); remove => this.EventManager.DayEnding.Remove(value); } @@ -97,14 +97,14 @@ namespace StardewModdingAPI.Framework.Events public event EventHandler TimeChanged { - add => this.EventManager.TimeChanged.Add(value); + add => this.EventManager.TimeChanged.Add(value, this.Mod); remove => this.EventManager.TimeChanged.Remove(value); } /// Raised after the game returns to the title screen. public event EventHandler ReturnedToTitle { - add => this.EventManager.ReturnedToTitle.Add(value); + add => this.EventManager.ReturnedToTitle.Add(value, this.Mod); remove => this.EventManager.ReturnedToTitle.Remove(value); } diff --git a/src/SMAPI/Framework/Events/ModInputEvents.cs b/src/SMAPI/Framework/Events/ModInputEvents.cs index 6a4298b4..ab26ab3e 100644 --- a/src/SMAPI/Framework/Events/ModInputEvents.cs +++ b/src/SMAPI/Framework/Events/ModInputEvents.cs @@ -12,28 +12,28 @@ namespace StardewModdingAPI.Framework.Events /// Raised after the player presses a button on the keyboard, controller, or mouse. public event EventHandler ButtonPressed { - add => this.EventManager.ButtonPressed.Add(value); + add => this.EventManager.ButtonPressed.Add(value, this.Mod); remove => this.EventManager.ButtonPressed.Remove(value); } /// Raised after the player releases a button on the keyboard, controller, or mouse. public event EventHandler ButtonReleased { - add => this.EventManager.ButtonReleased.Add(value); + add => this.EventManager.ButtonReleased.Add(value, this.Mod); remove => this.EventManager.ButtonReleased.Remove(value); } /// Raised after the player moves the in-game cursor. public event EventHandler CursorMoved { - add => this.EventManager.CursorMoved.Add(value); + add => this.EventManager.CursorMoved.Add(value, this.Mod); remove => this.EventManager.CursorMoved.Remove(value); } /// Raised after the player scrolls the mouse wheel. public event EventHandler MouseWheelScrolled { - add => this.EventManager.MouseWheelScrolled.Add(value); + add => this.EventManager.MouseWheelScrolled.Add(value, this.Mod); remove => this.EventManager.MouseWheelScrolled.Remove(value); } diff --git a/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs b/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs index 152c4e0c..2006b2b5 100644 --- a/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs +++ b/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs @@ -12,21 +12,21 @@ namespace StardewModdingAPI.Framework.Events /// Raised after the mod context for a peer is received. This happens before the game approves the connection, so the player doesn't yet exist in the game. This is the earliest point where messages can be sent to the peer via SMAPI. public event EventHandler PeerContextReceived { - add => this.EventManager.PeerContextReceived.Add(value); + add => this.EventManager.PeerContextReceived.Add(value, this.Mod); remove => this.EventManager.PeerContextReceived.Remove(value); } /// Raised after a mod message is received over the network. public event EventHandler ModMessageReceived { - add => this.EventManager.ModMessageReceived.Add(value); + add => this.EventManager.ModMessageReceived.Add(value, this.Mod); remove => this.EventManager.ModMessageReceived.Remove(value); } /// Raised after the connection with a peer is severed. public event EventHandler PeerDisconnected { - add => this.EventManager.PeerDisconnected.Add(value); + add => this.EventManager.PeerDisconnected.Add(value, this.Mod); remove => this.EventManager.PeerDisconnected.Remove(value); } diff --git a/src/SMAPI/Framework/Events/ModPlayerEvents.cs b/src/SMAPI/Framework/Events/ModPlayerEvents.cs index ca7cfd96..240beb8d 100644 --- a/src/SMAPI/Framework/Events/ModPlayerEvents.cs +++ b/src/SMAPI/Framework/Events/ModPlayerEvents.cs @@ -12,21 +12,21 @@ namespace StardewModdingAPI.Framework.Events /// Raised after items are added or removed to a player's inventory. NOTE: this event is currently only raised for the local player. public event EventHandler InventoryChanged { - add => this.EventManager.InventoryChanged.Add(value); + add => this.EventManager.InventoryChanged.Add(value, this.Mod); remove => this.EventManager.InventoryChanged.Remove(value); } /// Raised after a player skill level changes. This happens as soon as they level up, not when the game notifies the player after their character goes to bed. NOTE: this event is currently only raised for the local player. public event EventHandler LevelChanged { - add => this.EventManager.LevelChanged.Add(value); + add => this.EventManager.LevelChanged.Add(value, this.Mod); remove => this.EventManager.LevelChanged.Remove(value); } /// Raised after a player warps to a new location. NOTE: this event is currently only raised for the local player. public event EventHandler Warped { - add => this.EventManager.Warped.Add(value); + add => this.EventManager.Warped.Add(value, this.Mod); remove => this.EventManager.Warped.Remove(value); } diff --git a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs index 9388bdb2..1d6788e1 100644 --- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs +++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs @@ -12,21 +12,21 @@ namespace StardewModdingAPI.Framework.Events /// Raised when the low-level stage in the game's loading process has changed. This is an advanced event for mods which need to run code at specific points in the loading process. The available stages or when they happen might change without warning in future versions (e.g. due to changes in the game's load process), so mods using this event are more likely to break or have bugs. Most mods should use instead. public event EventHandler LoadStageChanged { - add => this.EventManager.LoadStageChanged.Add(value); + add => this.EventManager.LoadStageChanged.Add(value, this.Mod); remove => this.EventManager.LoadStageChanged.Remove(value); } /// Raised before the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console. public event EventHandler UnvalidatedUpdateTicking { - add => this.EventManager.UnvalidatedUpdateTicking.Add(value); + add => this.EventManager.UnvalidatedUpdateTicking.Add(value, this.Mod); remove => this.EventManager.UnvalidatedUpdateTicking.Remove(value); } /// Raised after the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console. public event EventHandler UnvalidatedUpdateTicked { - add => this.EventManager.UnvalidatedUpdateTicked.Add(value); + add => this.EventManager.UnvalidatedUpdateTicked.Add(value, this.Mod); remove => this.EventManager.UnvalidatedUpdateTicked.Remove(value); } diff --git a/src/SMAPI/Framework/Events/ModWorldEvents.cs b/src/SMAPI/Framework/Events/ModWorldEvents.cs index 2ae69669..21b1b664 100644 --- a/src/SMAPI/Framework/Events/ModWorldEvents.cs +++ b/src/SMAPI/Framework/Events/ModWorldEvents.cs @@ -40,28 +40,28 @@ namespace StardewModdingAPI.Framework.Events /// Raised after NPCs are added or removed in a location. public event EventHandler NpcListChanged { - add => this.EventManager.NpcListChanged.Add(value); + add => this.EventManager.NpcListChanged.Add(value, this.Mod); remove => this.EventManager.NpcListChanged.Remove(value); } /// Raised after objects are added or removed in a location. public event EventHandler ObjectListChanged { - add => this.EventManager.ObjectListChanged.Add(value); + add => this.EventManager.ObjectListChanged.Add(value, this.Mod); remove => this.EventManager.ObjectListChanged.Remove(value); } /// Raised after items are added or removed from a chest. public event EventHandler ChestInventoryChanged { - add => this.EventManager.ChestInventoryChanged.Add(value); + add => this.EventManager.ChestInventoryChanged.Add(value, this.Mod); remove => this.EventManager.ChestInventoryChanged.Remove(value); } /// Raised after terrain features (like floors and trees) are added or removed in a location. public event EventHandler TerrainFeatureListChanged { - add => this.EventManager.TerrainFeatureListChanged.Add(value); + add => this.EventManager.TerrainFeatureListChanged.Add(value, this.Mod); remove => this.EventManager.TerrainFeatureListChanged.Remove(value); } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index de9c955d..c6e69d4e 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -172,7 +172,7 @@ namespace StardewModdingAPI.Framework this.MonitorForGame = this.GetSecondaryMonitor("game"); SCore.PerformanceMonitor = new PerformanceMonitor(this.Monitor); - this.EventManager = new EventManager(this.Monitor, this.ModRegistry, SCore.PerformanceMonitor); + this.EventManager = new EventManager(this.ModRegistry, SCore.PerformanceMonitor); SCore.PerformanceMonitor.InitializePerformanceCounterCollections(this.EventManager); SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); From da95a906bf8e812ddcd99a90a4d49942f02f5623 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 15 Jun 2020 18:59:05 -0400 Subject: [PATCH 3/4] increase event priority range This can be used in cases where more granular priority is needed. --- src/SMAPI/Events/EventPriority.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SMAPI/Events/EventPriority.cs b/src/SMAPI/Events/EventPriority.cs index e1fb00ac..1efb4e2a 100644 --- a/src/SMAPI/Events/EventPriority.cs +++ b/src/SMAPI/Events/EventPriority.cs @@ -4,12 +4,12 @@ namespace StardewModdingAPI.Events public enum EventPriority { /// Low priority. - Low = 3, + Low = -1000, /// The default priority. - Normal = 2, + Normal = 0, /// High priority. - High = 1 + High = 1000 } } From 02e7318d2b99d311a328746b23a359364575f0c5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 15 Jun 2020 19:08:02 -0400 Subject: [PATCH 4/4] merge inconsistent event raise methods --- src/SMAPI/Framework/Events/ManagedEvent.cs | 38 ++++++---------------- src/SMAPI/Framework/SGame.cs | 2 +- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index b0f0ae71..b37fb376 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -90,8 +90,13 @@ namespace StardewModdingAPI.Framework.Events /// Raise the event and notify all handlers. /// The event arguments to pass. - public void Raise(TEventArgs args) + /// A lambda which returns true if the event should be raised for the given mod. + public void Raise(TEventArgs args, Func match = null) { + // skip if no handlers + if (this.EventHandlers.Count == 0) + return; + // sort event handlers by priority // (This is done here to avoid repeatedly sorting when handlers are added/removed.) if (this.NeedsSort) @@ -100,13 +105,14 @@ namespace StardewModdingAPI.Framework.Events this.EventHandlers.Sort(); } - // raise - if (this.EventHandlers.Count == 0) - return; + // raise event this.PerformanceMonitor.Track(this.EventName, () => { foreach (ManagedEventHandler handler in this.EventHandlers) { + if (match != null && !match(handler.SourceMod)) + continue; + try { this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Handler.Invoke(null, args)); @@ -119,30 +125,6 @@ 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 RaiseForMods(TEventArgs args, Func match) - { - if (this.EventHandlers.Count == 0) - return; - - foreach (ManagedEventHandler handler in this.EventHandlers) - { - if (match(handler.SourceMod)) - { - try - { - handler.Handler.Invoke(null, args); - } - catch (Exception ex) - { - this.LogError(handler, ex); - } - } - } - } - /********* ** Private methods diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 2a30b595..23358afb 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -240,7 +240,7 @@ namespace StardewModdingAPI.Framework modIDs.Remove(message.FromModID); // don't send a broadcast back to the sender // raise events - this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID)); + this.Events.ModMessageReceived.Raise(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID)); } /// A callback invoked when custom content is removed from the save data to avoid a crash.