using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Events; using StardewModdingAPI.Framework.Reflection; using StardewValley; namespace StardewModdingAPI.Framework { /// Provides extension methods for SMAPI's internal use. internal static class InternalExtensions { /**** ** IMonitor ****/ /// Log a message for the player or developer the first time it occurs. /// The monitor through which to log the message. /// The hash of logged messages. /// The message to log. /// The log severity level. public static void LogOnce(this IMonitor monitor, HashSet hash, string message, LogLevel level = LogLevel.Trace) { if (!hash.Contains(message)) { monitor.Log(message, level); hash.Add(message); } } /**** ** IModMetadata ****/ /// Log a message using the mod's monitor. /// The mod whose monitor to use. /// The message to log. /// The log severity level. public static void LogAsMod(this IModMetadata metadata, string message, LogLevel level = LogLevel.Trace) { metadata.Monitor.Log(message, level); } /**** ** ManagedEvent ****/ /// Raise the event using the default event args and notify all handlers. /// The event args type to construct. /// The event to raise. public static void RaiseEmpty(this ManagedEvent @event) where TEventArgs : new() { @event.Raise(Singleton.Instance); } /**** ** Exceptions ****/ /// Get a string representation of an exception suitable for writing to the error log. /// The error to summarize. public static string GetLogSummary(this Exception exception) { switch (exception) { case TypeLoadException ex: return $"Failed loading type '{ex.TypeName}': {exception}"; case ReflectionTypeLoadException ex: string summary = exception.ToString(); foreach (Exception childEx in ex.LoaderExceptions) summary += $"\n\n{childEx.GetLogSummary()}"; return summary; default: return exception.ToString(); } } /// Get the lowest exception in an exception stack. /// The exception from which to search. public static Exception GetInnermostException(this Exception exception) { while (exception.InnerException != null) exception = exception.InnerException; return exception; } /**** ** ReaderWriterLockSlim ****/ /// Run code within a read lock. /// The lock to set. /// The action to perform. public static void InReadLock(this ReaderWriterLockSlim @lock, Action action) { @lock.EnterReadLock(); try { action(); } finally { @lock.ExitReadLock(); } } /// Run code within a read lock. /// The action's return value. /// The lock to set. /// The action to perform. public static TReturn InReadLock(this ReaderWriterLockSlim @lock, Func action) { @lock.EnterReadLock(); try { return action(); } finally { @lock.ExitReadLock(); } } /// Run code within a write lock. /// The lock to set. /// The action to perform. public static void InWriteLock(this ReaderWriterLockSlim @lock, Action action) { @lock.EnterWriteLock(); try { action(); } finally { @lock.ExitWriteLock(); } } /// Run code within a write lock. /// The action's return value. /// The lock to set. /// The action to perform. public static TReturn InWriteLock(this ReaderWriterLockSlim @lock, Func action) { @lock.EnterWriteLock(); try { return action(); } finally { @lock.ExitWriteLock(); } } /**** ** Sprite batch ****/ /// Get whether the sprite batch is between a begin and end pair. /// The sprite batch to check. /// The reflection helper with which to access private fields. public static bool IsOpen(this SpriteBatch spriteBatch, Reflector reflection) { // get field name const string fieldName = #if SMAPI_FOR_WINDOWS "inBeginEndPair"; #else "_beginCalled"; #endif // get result return reflection.GetField(Game1.spriteBatch, fieldName).GetValue(); } } }