summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/Events
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/Events')
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs34
-rw-r--r--src/SMAPI/Framework/Events/IManagedEvent.cs4
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs101
3 files changed, 79 insertions, 60 deletions
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index 41540047..b21d5c7d 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -198,9 +198,9 @@ namespace StardewModdingAPI.Framework.Events
public EventManager(ModRegistry modRegistry)
{
// create shortcut initializers
- ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName, bool isPerformanceCritical = false)
+ ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName)
{
- return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", modRegistry, isPerformanceCritical);
+ return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", modRegistry);
}
// init events
@@ -210,21 +210,21 @@ namespace StardewModdingAPI.Framework.Events
this.LocaleChanged = ManageEventOf<LocaleChangedEventArgs>(nameof(IModEvents.Content), nameof(IContentEvents.LocaleChanged));
this.MenuChanged = ManageEventOf<MenuChangedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged));
- this.Rendering = ManageEventOf<RenderingEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering), isPerformanceCritical: true);
- this.Rendered = ManageEventOf<RenderedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered), isPerformanceCritical: true);
- this.RenderingWorld = ManageEventOf<RenderingWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingWorld), isPerformanceCritical: true);
- this.RenderedWorld = ManageEventOf<RenderedWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedWorld), isPerformanceCritical: true);
- this.RenderingActiveMenu = ManageEventOf<RenderingActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingActiveMenu), isPerformanceCritical: true);
- this.RenderedActiveMenu = ManageEventOf<RenderedActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedActiveMenu), isPerformanceCritical: true);
- this.RenderingHud = ManageEventOf<RenderingHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingHud), isPerformanceCritical: true);
- this.RenderedHud = ManageEventOf<RenderedHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedHud), isPerformanceCritical: true);
+ this.Rendering = ManageEventOf<RenderingEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering));
+ this.Rendered = ManageEventOf<RenderedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered));
+ this.RenderingWorld = ManageEventOf<RenderingWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingWorld));
+ this.RenderedWorld = ManageEventOf<RenderedWorldEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedWorld));
+ this.RenderingActiveMenu = ManageEventOf<RenderingActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingActiveMenu));
+ this.RenderedActiveMenu = ManageEventOf<RenderedActiveMenuEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedActiveMenu));
+ this.RenderingHud = ManageEventOf<RenderingHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderingHud));
+ this.RenderedHud = ManageEventOf<RenderedHudEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.RenderedHud));
this.WindowResized = ManageEventOf<WindowResizedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.WindowResized));
this.GameLaunched = ManageEventOf<GameLaunchedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.GameLaunched));
- this.UpdateTicking = ManageEventOf<UpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking), isPerformanceCritical: true);
- this.UpdateTicked = ManageEventOf<UpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked), isPerformanceCritical: true);
- this.OneSecondUpdateTicking = ManageEventOf<OneSecondUpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking), isPerformanceCritical: true);
- this.OneSecondUpdateTicked = ManageEventOf<OneSecondUpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked), isPerformanceCritical: true);
+ this.UpdateTicking = ManageEventOf<UpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking));
+ this.UpdateTicked = ManageEventOf<UpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked));
+ this.OneSecondUpdateTicking = ManageEventOf<OneSecondUpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking));
+ this.OneSecondUpdateTicked = ManageEventOf<OneSecondUpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked));
this.SaveCreating = ManageEventOf<SaveCreatingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreating));
this.SaveCreated = ManageEventOf<SaveCreatedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreated));
this.Saving = ManageEventOf<SavingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Saving));
@@ -238,7 +238,7 @@ namespace StardewModdingAPI.Framework.Events
this.ButtonsChanged = ManageEventOf<ButtonsChangedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonsChanged));
this.ButtonPressed = ManageEventOf<ButtonPressedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonPressed));
this.ButtonReleased = ManageEventOf<ButtonReleasedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased));
- this.CursorMoved = ManageEventOf<CursorMovedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved), isPerformanceCritical: true);
+ this.CursorMoved = ManageEventOf<CursorMovedEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved));
this.MouseWheelScrolled = ManageEventOf<MouseWheelScrolledEventArgs>(nameof(IModEvents.Input), nameof(IInputEvents.MouseWheelScrolled));
this.PeerContextReceived = ManageEventOf<PeerContextReceivedEventArgs>(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.PeerContextReceived));
@@ -261,8 +261,8 @@ namespace StardewModdingAPI.Framework.Events
this.FurnitureListChanged = ManageEventOf<FurnitureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.FurnitureListChanged));
this.LoadStageChanged = ManageEventOf<LoadStageChangedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.LoadStageChanged));
- this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking), isPerformanceCritical: true);
- this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicked), isPerformanceCritical: true);
+ this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking));
+ this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicked));
}
}
}
diff --git a/src/SMAPI/Framework/Events/IManagedEvent.cs b/src/SMAPI/Framework/Events/IManagedEvent.cs
index e4e3ca08..55994c04 100644
--- a/src/SMAPI/Framework/Events/IManagedEvent.cs
+++ b/src/SMAPI/Framework/Events/IManagedEvent.cs
@@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>A human-readable name for the event.</summary>
string EventName { get; }
- /// <summary>Whether the event is typically called at least once per second.</summary>
- bool IsPerformanceCritical { get; }
+ /// <summary>Whether any handlers are listening to the event.</summary>
+ bool HasListeners { get; }
}
}
diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs
index 4b8a770d..8a3ca839 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;
/*********
@@ -37,7 +40,7 @@ namespace StardewModdingAPI.Framework.Events
public string EventName { get; }
/// <inheritdoc />
- public bool IsPerformanceCritical { get; }
+ public bool HasListeners { get; private set; }
/*********
@@ -46,18 +49,10 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>Construct an instance.</summary>
/// <param name="eventName">A human-readable name for the event.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
- /// <param name="isPerformanceCritical">Whether the event is typically called at least once per second.</param>
- public ManagedEvent(string eventName, ModRegistry modRegistry, bool isPerformanceCritical = false)
+ public ManagedEvent(string eventName, ModRegistry modRegistry)
{
this.EventName = eventName;
this.ModRegistry = modRegistry;
- this.IsPerformanceCritical = isPerformanceCritical;
- }
-
- /// <summary>Get whether anything is listening to the event.</summary>
- public bool HasListeners()
- {
- return this.Handlers.Count > 0;
}
/// <summary>Add an event handler.</summary>
@@ -72,7 +67,8 @@ namespace StardewModdingAPI.Framework.Events
this.Handlers.Add(managedHandler);
this.CachedHandlers = null;
- this.HasNewHandlers = true;
+ this.HasListeners = true;
+ this.HasPriorities |= priority != EventPriority.Normal;
}
}
@@ -90,6 +86,8 @@ namespace StardewModdingAPI.Framework.Events
this.Handlers.RemoveAt(i);
this.CachedHandlers = null;
+ this.HasListeners = this.Handlers.Count != 0;
+ this.HasRemovedHandlers = true;
break;
}
}
@@ -97,46 +95,40 @@ 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)
- {
- this.Raise((_, invoke) => invoke(args), match);
- }
-
- /// <summary>Raise the event and notify all handlers.</summary>
- /// <param name="invoke">Invoke an event handler. This receives the mod which registered the handler, and should invoke the callback with the event arguments to pass it.</param>
- /// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param>
- public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke, Func<IModMetadata, bool>? match = null)
+ public void Raise(TEventArgs args)
{
// skip if no handlers
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)
+ // raise event
+ foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
{
- lock (this.Handlers)
+ try
{
- if (this.HasNewHandlers && this.Handlers.Any(p => p.Priority != EventPriority.Normal))
- this.Handlers.Sort();
-
- this.CachedHandlers = handlers = this.Handlers.ToArray();
- this.HasNewHandlers = false;
+ handler.Handler(null, args);
+ }
+ catch (Exception ex)
+ {
+ this.LogError(handler, ex);
}
}
+ }
+
+ /// <summary>Raise the event and notify all handlers.</summary>
+ /// <param name="invoke">Invoke an event handler. This receives the mod which registered the handler, and should invoke the callback with the event arguments to pass it.</param>
+ public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke)
+ {
+ // skip if no handlers
+ if (this.Handlers.Count == 0)
+ return;
// 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)
{
@@ -152,9 +144,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;
+ }
}
}