using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using StardewModdingAPI.Events; namespace StardewModdingAPI.Framework { /// Provides extension methods for SMAPI's internal use. internal static class InternalExtensions { /********* ** Properties *********/ /// Tracks the installed mods. private static ModRegistry ModRegistry; /********* ** Public methods *********/ /// Injects types required for backwards compatibility. /// Tracks the installed mods. internal static void Shim(ModRegistry modRegistry) { InternalExtensions.ModRegistry = modRegistry; } /**** ** IMonitor ****/ /// Safely raise an event, and intercept any exceptions thrown by its handlers. /// Encapsulates monitoring and logging. /// The event name for error messages. /// The event handlers. /// The event sender. /// The event arguments (or null to pass ). public static void SafelyRaisePlainEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender = null, EventArgs args = null) { if (handlers == null) return; foreach (EventHandler handler in Enumerable.Cast(handlers)) { try { handler.Invoke(sender, args ?? EventArgs.Empty); } catch (Exception ex) { monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error); } } } /// Safely raise an event, and intercept any exceptions thrown by its handlers. /// The event argument object type. /// Encapsulates monitoring and logging. /// The event name for error messages. /// The event handlers. /// The event sender. /// The event arguments. public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender, TEventArgs args) where TEventArgs : EventArgs { if (handlers == null) return; foreach (EventHandler handler in handlers.Cast>()) { try { handler.Invoke(sender, args); } catch (Exception ex) { monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error); } } } /// Safely raise an event, and intercept any exceptions thrown by its handlers. /// Encapsulates monitoring and logging. /// The event name for error messages. /// The event handlers. /// The event sender. /// The event arguments. public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable handlers, object sender, IContentEventHelper args) { if (handlers == null) return; foreach (ContentEventHandler handler in handlers.Cast()) { try { handler.Invoke(sender, args); } catch (Exception ex) { monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error); } } } /**** ** Exceptions ****/ /// Get a string representation of an exception suitable for writing to the error log. /// The error to summarise. public static string GetLogSummary(this Exception exception) { // type load exception if (exception is TypeLoadException) return $"Failed loading type: {((TypeLoadException)exception).TypeName}: {exception}"; // reflection type load exception if (exception is ReflectionTypeLoadException) { string summary = exception.ToString(); foreach (Exception childEx in ((ReflectionTypeLoadException)exception).LoaderExceptions) summary += $"\n\n{childEx.GetLogSummary()}"; return summary; } // anything else return exception.ToString(); } /**** ** Deprecation ****/ /// Log a deprecation warning for mods using an event. /// The deprecation manager to extend. /// The event handlers. /// A noun phrase describing what is deprecated. /// The SMAPI version which deprecated it. /// How deprecated the code is. public static void WarnForEvent(this DeprecationManager deprecationManager, Delegate[] handlers, string nounPhrase, string version, DeprecationLevel severity) { if (handlers == null || !handlers.Any()) return; foreach (Delegate handler in handlers) { string modName = InternalExtensions.ModRegistry.GetModFrom(handler) ?? "an unknown mod"; // suppress stack trace for unknown mods, not helpful here deprecationManager.Warn(modName, nounPhrase, version, severity); } } } }