using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.PerformanceMonitoring;
namespace StardewModdingAPI.Framework.Events
{
/// An event wrapper which intercepts and logs errors in handler code.
/// The event arguments type.
internal class ManagedEvent : IManagedEvent
{
/*********
** Fields
*********/
/// The underlying event handlers.
private readonly List> EventHandlers = new List>();
/// The mod registry with which to identify mods.
protected readonly ModRegistry ModRegistry;
/// 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
*********/
/// A human-readable name for the event.
public string EventName { get; }
/// Whether the event is typically called at least once per second.
public bool IsPerformanceCritical { get; }
/*********
** Public methods
*********/
/// Construct an instance.
/// A human-readable name for the event.
/// 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, ModRegistry modRegistry, PerformanceMonitor performanceMonitor, bool isPerformanceCritical = false)
{
this.EventName = eventName;
this.ModRegistry = modRegistry;
this.PerformanceMonitor = performanceMonitor;
this.IsPerformanceCritical = isPerformanceCritical;
}
/// Get whether anything is listening to the event.
public bool HasListeners()
{
return this.EventHandlers.Count > 0;
}
/// Add an event handler.
/// The event handler.
/// The mod which added the event handler.
public void Add(EventHandler handler, IModMetadata mod)
{
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.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 (ManagedEventHandler handler in this.EventHandlers)
{
try
{
this.PerformanceMonitor.Track(this.EventName, this.GetModNameForPerformanceCounters(handler), () => handler.Handler.Invoke(null, args));
}
catch (Exception ex)
{
this.LogError(handler, ex);
}
}
});
}
/// 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
*********/
/// Get the mod name for a given event handler to display in performance monitoring reports.
/// The event handler.
private string GetModNameForPerformanceCounters(ManagedEventHandler handler)
{
IModMetadata mod = handler.SourceMod;
return mod.HasManifest()
? mod.Manifest.UniqueID
: mod.DisplayName;
}
/// Log an exception from an event handler.
/// The event handler instance.
/// The exception that was raised.
protected void LogError(ManagedEventHandler handler, Exception ex)
{
handler.SourceMod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error);
}
}
}