summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/Events/ManagedEvent.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-06-15 21:34:46 -0400
committerGitHub <noreply@github.com>2020-06-15 21:34:46 -0400
commite759332135df95465ceefa74af6fb936d2cf71f1 (patch)
tree74a7626f14d0f737ddc19c45dda646885ea1cd49 /src/SMAPI/Framework/Events/ManagedEvent.cs
parentff7b9a0251484bfb9737f9c6c05637f63efa9551 (diff)
parent02e7318d2b99d311a328746b23a359364575f0c5 (diff)
downloadSMAPI-e759332135df95465ceefa74af6fb936d2cf71f1.tar.gz
SMAPI-e759332135df95465ceefa74af6fb936d2cf71f1.tar.bz2
SMAPI-e759332135df95465ceefa74af6fb936d2cf71f1.zip
Merge pull request #723 from spacechase0/event-priority
Implement event priority attribute
Diffstat (limited to 'src/SMAPI/Framework/Events/ManagedEvent.cs')
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs139
1 files changed, 46 insertions, 93 deletions
diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs
index 118b73ac..b37fb376 100644
--- a/src/SMAPI/Framework/Events/ManagedEvent.cs
+++ b/src/SMAPI/Framework/Events/ManagedEvent.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
+using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.PerformanceMonitoring;
namespace StardewModdingAPI.Framework.Events
@@ -12,24 +14,24 @@ namespace StardewModdingAPI.Framework.Events
/*********
** Fields
*********/
- /// <summary>The underlying event.</summary>
- private event EventHandler<TEventArgs> Event;
-
- /// <summary>Writes messages to the log.</summary>
- private readonly IMonitor Monitor;
+ /// <summary>The underlying event handlers.</summary>
+ private readonly List<ManagedEventHandler<TEventArgs>> EventHandlers = new List<ManagedEventHandler<TEventArgs>>();
/// <summary>The mod registry with which to identify mods.</summary>
protected readonly ModRegistry ModRegistry;
- /// <summary>The display names for the mods which added each delegate.</summary>
- private readonly IDictionary<EventHandler<TEventArgs>, IModMetadata> SourceMods = new Dictionary<EventHandler<TEventArgs>, IModMetadata>();
-
- /// <summary>The cached invocation list.</summary>
- private EventHandler<TEventArgs>[] CachedInvocationList;
-
/// <summary>Tracks performance metrics.</summary>
private readonly PerformanceMonitor PerformanceMonitor;
+ /// <summary>The total number of event handlers registered for this events, regardless of whether they're still registered.</summary>
+ private int RegistrationIndex;
+
+ /// <summary>Whether any registered event handlers have a custom priority value.</summary>
+ private bool HasCustomPriorities;
+
+ /// <summary>Whether event handlers should be sorted before the next invocation.</summary>
+ private bool NeedsSort;
+
/*********
** Accessors
@@ -46,14 +48,12 @@ namespace StardewModdingAPI.Framework.Events
*********/
/// <summary>Construct an instance.</summary>
/// <param name="eventName">A human-readable name for the event.</param>
- /// <param name="monitor">Writes messages to the log.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
/// <param name="performanceMonitor">Tracks performance metrics.</param>
/// <param name="isPerformanceCritical">Whether the event is typically called at least once per second.</param>
- 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;
@@ -62,14 +62,7 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>Get whether anything is listening to the event.</summary>
public bool HasListeners()
{
- return this.CachedInvocationList?.Length > 0;
- }
-
- /// <summary>Add an event handler.</summary>
- /// <param name="handler">The event handler.</param>
- public void Add(EventHandler<TEventArgs> handler)
- {
- this.Add(handler, this.ModRegistry.GetFromStack());
+ return this.EventHandlers.Count > 0;
}
/// <summary>Add an event handler.</summary>
@@ -77,64 +70,59 @@ namespace StardewModdingAPI.Framework.Events
/// <param name="mod">The mod which added the event handler.</param>
public void Add(EventHandler<TEventArgs> handler, IModMetadata mod)
{
- this.Event += handler;
- this.AddTracking(mod, handler, this.Event?.GetInvocationList().Cast<EventHandler<TEventArgs>>());
+ EventPriority priority = handler.Method.GetCustomAttribute<EventPriorityAttribute>()?.Priority ?? EventPriority.Normal;
+ var managedHandler = new ManagedEventHandler<TEventArgs>(handler, this.RegistrationIndex++, priority, mod);
+
+ this.EventHandlers.Add(managedHandler);
+ this.HasCustomPriorities = this.HasCustomPriorities || managedHandler.HasCustomPriority();
+
+ if (this.HasCustomPriorities)
+ this.NeedsSort = true;
}
/// <summary>Remove an event handler.</summary>
/// <param name="handler">The event handler.</param>
public void Remove(EventHandler<TEventArgs> handler)
{
- this.Event -= handler;
- this.RemoveTracking(handler, this.Event?.GetInvocationList().Cast<EventHandler<TEventArgs>>());
+ this.EventHandlers.RemoveAll(p => p.Handler == handler);
+ this.HasCustomPriorities = this.HasCustomPriorities && this.EventHandlers.Any(p => p.HasCustomPriority());
}
/// <summary>Raise the event and notify all handlers.</summary>
/// <param name="args">The event arguments to pass.</param>
- public void Raise(TEventArgs args)
+ /// <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)
{
- if (this.Event == 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)
+ {
+ this.NeedsSort = false;
+ this.EventHandlers.Sort();
+ }
+ // raise event
this.PerformanceMonitor.Track(this.EventName, () =>
{
- foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
+ foreach (ManagedEventHandler<TEventArgs> handler in this.EventHandlers)
{
- try
- {
- this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Invoke(null, args));
- }
- catch (Exception ex)
- {
- this.LogError(handler, ex);
- }
- }
- });
- }
-
- /// <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 RaiseForMods(TEventArgs args, Func<IModMetadata, bool> match)
- {
- if (this.Event == null)
- return;
+ if (match != null && !match(handler.SourceMod))
+ continue;
- foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
- {
- if (match(this.GetSourceMod(handler)))
- {
try
{
- handler.Invoke(null, args);
+ this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Handler.Invoke(null, args));
}
catch (Exception ex)
{
this.LogError(handler, ex);
}
}
- }
+ });
}
@@ -143,56 +131,21 @@ namespace StardewModdingAPI.Framework.Events
*********/
/// <summary>Get the mod name for a given event handler to display in performance monitoring reports.</summary>
/// <param name="handler">The event handler.</param>
- private string GetModNameForPerformanceCounters(EventHandler<TEventArgs> handler)
+ private string GetModNameForPerformanceCounters(ManagedEventHandler<TEventArgs> handler)
{
- IModMetadata mod = this.GetSourceMod(handler);
- if (mod == null)
- return Constants.GamePerformanceCounterName;
+ IModMetadata mod = handler.SourceMod;
return mod.HasManifest()
? mod.Manifest.UniqueID
: mod.DisplayName;
}
- /// <summary>Track an event handler.</summary>
- /// <param name="mod">The mod which added the handler.</param>
- /// <param name="handler">The event handler.</param>
- /// <param name="invocationList">The updated event invocation list.</param>
- protected void AddTracking(IModMetadata mod, EventHandler<TEventArgs> handler, IEnumerable<EventHandler<TEventArgs>> invocationList)
- {
- this.SourceMods[handler] = mod;
- this.CachedInvocationList = invocationList?.ToArray() ?? new EventHandler<TEventArgs>[0];
- }
-
- /// <summary>Remove tracking for an event handler.</summary>
- /// <param name="handler">The event handler.</param>
- /// <param name="invocationList">The updated event invocation list.</param>
- protected void RemoveTracking(EventHandler<TEventArgs> handler, IEnumerable<EventHandler<TEventArgs>> invocationList)
- {
- this.CachedInvocationList = invocationList?.ToArray() ?? new EventHandler<TEventArgs>[0];
- 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);
- }
-
- /// <summary>Get the mod which registered the given event handler, if available.</summary>
- /// <param name="handler">The event handler.</param>
- protected IModMetadata GetSourceMod(EventHandler<TEventArgs> handler)
- {
- return this.SourceMods.TryGetValue(handler, out IModMetadata mod)
- ? mod
- : null;
- }
-
/// <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(EventHandler<TEventArgs> handler, Exception ex)
+ protected void LogError(ManagedEventHandler<TEventArgs> 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);
}
}
}