1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using StardewModdingAPI.Events;
namespace StardewModdingAPI.Framework
{
/// <summary>Provides extension methods for SMAPI's internal use.</summary>
internal static class InternalExtensions
{
/*********
** Properties
*********/
/// <summary>Tracks the installed mods.</summary>
private static ModRegistry ModRegistry;
/*********
** Public methods
*********/
/// <summary>Injects types required for backwards compatibility.</summary>
/// <param name="modRegistry">Tracks the installed mods.</param>
internal static void Shim(ModRegistry modRegistry)
{
InternalExtensions.ModRegistry = modRegistry;
}
/****
** IMonitor
****/
/// <summary>Safely raise an <see cref="EventHandler"/> event, and intercept any exceptions thrown by its handlers.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="name">The event name for error messages.</param>
/// <param name="handlers">The event handlers.</param>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments (or <c>null</c> to pass <see cref="EventArgs.Empty"/>).</param>
public static void SafelyRaisePlainEvent(this IMonitor monitor, string name, IEnumerable<Delegate> handlers, object sender = null, EventArgs args = null)
{
if (handlers == null)
return;
foreach (EventHandler handler in Enumerable.Cast<EventHandler>(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);
}
}
}
/// <summary>Safely raise an <see cref="EventHandler{TEventArgs}"/> event, and intercept any exceptions thrown by its handlers.</summary>
/// <typeparam name="TEventArgs">The event argument object type.</typeparam>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="name">The event name for error messages.</param>
/// <param name="handlers">The event handlers.</param>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public static void SafelyRaiseGenericEvent<TEventArgs>(this IMonitor monitor, string name, IEnumerable<Delegate> handlers, object sender, TEventArgs args) where TEventArgs : EventArgs
{
if (handlers == null)
return;
foreach (EventHandler<TEventArgs> handler in handlers.Cast<EventHandler<TEventArgs>>())
{
try
{
handler.Invoke(sender, args);
}
catch (Exception ex)
{
monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error);
}
}
}
/// <summary>Safely raise an <see cref="EventHandler{TEventArgs}"/> event, and intercept any exceptions thrown by its handlers.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="name">The event name for error messages.</param>
/// <param name="handlers">The event handlers.</param>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public static void SafelyRaiseGenericEvent(this IMonitor monitor, string name, IEnumerable<Delegate> handlers, object sender, IContentEventHelper args)
{
if (handlers == null)
return;
foreach (ContentEventHandler handler in handlers.Cast<ContentEventHandler>())
{
try
{
handler.Invoke(sender, args);
}
catch (Exception ex)
{
monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error);
}
}
}
/****
** Exceptions
****/
/// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
/// <param name="exception">The error to summarise.</param>
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
****/
/// <summary>Log a deprecation warning for mods using an event.</summary>
/// <param name="deprecationManager">The deprecation manager to extend.</param>
/// <param name="handlers">The event handlers.</param>
/// <param name="nounPhrase">A noun phrase describing what is deprecated.</param>
/// <param name="version">The SMAPI version which deprecated it.</param>
/// <param name="severity">How deprecated the code is.</param>
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);
}
}
}
}
|