using System; using System.Collections.Generic; using System.Linq; namespace StardewModdingAPI.Framework.Events { /// The base implementation for an event wrapper which intercepts and logs errors in handler code. internal abstract class ManagedEventBase { /********* ** Fields *********/ /// A human-readable name for the event. private readonly string EventName; /// Writes messages to the log. private readonly IMonitor Monitor; /// 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 SourceMods = new Dictionary(); /// The cached invocation list. protected TEventHandler[] CachedInvocationList { get; private set; } /********* ** Public methods *********/ /// Get whether anything is listening to the event. public bool HasListeners() { return this.CachedInvocationList?.Length > 0; } /********* ** Protected methods *********/ /// Construct an instance. /// A human-readable name for the event. /// Writes messages to the log. /// The mod registry with which to identify mods. protected ManagedEventBase(string eventName, IMonitor monitor, ModRegistry modRegistry) { this.EventName = eventName; this.Monitor = monitor; this.ModRegistry = modRegistry; } /// Track an event handler. /// The mod which added the handler. /// The event handler. /// The updated event invocation list. protected void AddTracking(IModMetadata mod, TEventHandler handler, IEnumerable invocationList) { this.SourceMods[handler] = mod; this.CachedInvocationList = invocationList?.ToArray() ?? new TEventHandler[0]; } /// Remove tracking for an event handler. /// The event handler. /// The updated event invocation list. protected void RemoveTracking(TEventHandler handler, IEnumerable invocationList) { this.CachedInvocationList = invocationList?.ToArray() ?? new TEventHandler[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); } /// Get the mod which registered the given event handler, if available. /// The event handler. protected IModMetadata GetSourceMod(TEventHandler 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(TEventHandler 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); } } }