mirror of
https://github.com/Pathoschild/SMAPI.git
synced 2026-10-12 02:08:51 +00:00
migrate to extension blocks
This commit is contained in:
@@ -10,36 +10,39 @@ internal static class ExceptionHelper
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
|
||||
/// <param name="exception">The error to summarize.</param>
|
||||
public static string GetLogSummary(this Exception? exception)
|
||||
extension(Exception? exception)
|
||||
{
|
||||
try
|
||||
/// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
|
||||
public string GetLogSummary()
|
||||
{
|
||||
string message;
|
||||
switch (exception)
|
||||
try
|
||||
{
|
||||
case TypeLoadException ex:
|
||||
message = $"Failed loading type '{ex.TypeName}': {exception}";
|
||||
break;
|
||||
string message;
|
||||
switch (exception)
|
||||
{
|
||||
case TypeLoadException ex:
|
||||
message = $"Failed loading type '{ex.TypeName}': {exception}";
|
||||
break;
|
||||
|
||||
case ReflectionTypeLoadException ex:
|
||||
string summary = ex.ToString();
|
||||
foreach (Exception? childEx in ex.LoaderExceptions)
|
||||
summary += $"\n\n{childEx?.GetLogSummary()}";
|
||||
message = summary;
|
||||
break;
|
||||
case ReflectionTypeLoadException ex:
|
||||
string summary = ex.ToString();
|
||||
foreach (Exception? childEx in ex.LoaderExceptions)
|
||||
summary += $"\n\n{childEx?.GetLogSummary()}";
|
||||
message = summary;
|
||||
break;
|
||||
|
||||
default:
|
||||
message = exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}";
|
||||
break;
|
||||
default:
|
||||
message = exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}";
|
||||
break;
|
||||
}
|
||||
|
||||
return ExceptionHelper.SimplifyExtensionMessage(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed handling {exception?.GetType().FullName} (original message: {exception?.Message})", ex);
|
||||
}
|
||||
|
||||
return ExceptionHelper.SimplifyExtensionMessage(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed handling {exception?.GetType().FullName} (original message: {exception?.Message})", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,18 @@ namespace StardewModdingAPI.Toolkit.Serialization;
|
||||
/// <summary>Provides extension methods for parsing JSON.</summary>
|
||||
public static class JsonExtensions
|
||||
{
|
||||
/// <summary>Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity.</summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="obj">The JSON object to search.</param>
|
||||
/// <param name="fieldName">The field name.</param>
|
||||
public static T? ValueIgnoreCase<T>(this JObject obj, string fieldName)
|
||||
extension(JObject obj)
|
||||
{
|
||||
JToken? token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
|
||||
return token != null
|
||||
? token.Value<T>()
|
||||
: default;
|
||||
/// <summary>Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity.</summary>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <param name="fieldName">The field name.</param>
|
||||
public T? ValueIgnoreCase<T>(string fieldName)
|
||||
{
|
||||
JToken? token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
|
||||
return token != null
|
||||
? token.Value<T>()
|
||||
: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,54 +19,59 @@ public static class Extensions
|
||||
/****
|
||||
** View helpers
|
||||
****/
|
||||
/// <summary>Get a URL for an action method. Unlike <see cref="IUrlHelper.Action"/>, only the specified <paramref name="values"/> are added to the URL without merging values from the current HTTP request.</summary>
|
||||
/// <param name="helper">The URL helper to extend.</param>
|
||||
/// <param name="action">The name of the action method.</param>
|
||||
/// <param name="controller">The name of the controller.</param>
|
||||
/// <param name="values">An object that contains route values.</param>
|
||||
/// <param name="absoluteUrl">Get an absolute URL instead of a server-relative path/</param>
|
||||
/// <returns>The generated URL.</returns>
|
||||
public static string? PlainAction(this IUrlHelper helper, [AspMvcAction] string action, [AspMvcController] string controller, object? values = null, bool absoluteUrl = false)
|
||||
extension(IUrlHelper helper)
|
||||
{
|
||||
// get route values
|
||||
RouteValueDictionary valuesDict = new(values);
|
||||
foreach (var value in helper.ActionContext.RouteData.Values)
|
||||
valuesDict.TryAdd(value.Key, null); // explicitly remove it from the URL
|
||||
|
||||
// get relative URL
|
||||
string? url = helper.Action(action, controller, valuesDict);
|
||||
if (url == null && action.EndsWith("Async"))
|
||||
url = helper.Action(action[..^"Async".Length], controller, valuesDict);
|
||||
|
||||
// get absolute URL
|
||||
if (absoluteUrl)
|
||||
/// <summary>Get a URL for an action method. Unlike <see cref="IUrlHelper.Action"/>, only the specified <paramref name="values"/> are added to the URL without merging values from the current HTTP request.</summary>
|
||||
/// <param name="action">The name of the action method.</param>
|
||||
/// <param name="controller">The name of the controller.</param>
|
||||
/// <param name="values">An object that contains route values.</param>
|
||||
/// <param name="absoluteUrl">Get an absolute URL instead of a server-relative path/</param>
|
||||
/// <returns>The generated URL.</returns>
|
||||
public string? PlainAction([AspMvcAction] string action, [AspMvcController] string controller, object? values = null, bool absoluteUrl = false)
|
||||
{
|
||||
HttpRequest request = helper.ActionContext.HttpContext.Request;
|
||||
Uri baseUri = new($"{request.Scheme}://{request.Host}");
|
||||
url = new Uri(baseUri, url).ToString();
|
||||
// get route values
|
||||
RouteValueDictionary valuesDict = new(values);
|
||||
foreach (var value in helper.ActionContext.RouteData.Values)
|
||||
valuesDict.TryAdd(value.Key, null); // explicitly remove it from the URL
|
||||
|
||||
// get relative URL
|
||||
string? url = helper.Action(action, controller, valuesDict);
|
||||
if (url == null && action.EndsWith("Async"))
|
||||
url = helper.Action(action[..^"Async".Length], controller, valuesDict);
|
||||
|
||||
// get absolute URL
|
||||
if (absoluteUrl)
|
||||
{
|
||||
HttpRequest request = helper.ActionContext.HttpContext.Request;
|
||||
Uri baseUri = new($"{request.Scheme}://{request.Host}");
|
||||
url = new Uri(baseUri, url).ToString();
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
return url;
|
||||
/// <summary>Convert a virtual (relative, starting with ~/) path to an application absolute path, and append a query argument to force browsers to re-download the asset if needed.</summary>
|
||||
/// <param name="url">The virtual path of the content.</param>
|
||||
public string ContentWithCacheBust(string url)
|
||||
{
|
||||
char delimiter = url.Contains('?') ? '&' : '?';
|
||||
|
||||
return helper.Content($"{url}{delimiter}v={Program.CacheBustValue}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Convert a virtual (relative, starting with ~/) path to an application absolute path, and append a query argument to force browsers to re-download the asset if needed.</summary>
|
||||
/// <param name="helper">The URL helper to extend.</param>
|
||||
/// <param name="url">The virtual path of the content.</param>
|
||||
public static string ContentWithCacheBust(this IUrlHelper helper, string url)
|
||||
{
|
||||
char delimiter = url.Contains('?') ? '&' : '?';
|
||||
|
||||
return helper.Content($"{url}{delimiter}v={Program.CacheBustValue}");
|
||||
}
|
||||
|
||||
/// <summary>Get a serialized JSON representation of the value.</summary>
|
||||
/// <param name="page">The page to extend.</param>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <returns>The serialized JSON.</returns>
|
||||
/// <remarks>This bypasses unnecessary validation (e.g. not allowing null values) in <see cref="IJsonHelper.Serialize"/>.</remarks>
|
||||
public static IHtmlContent ForJson(this RazorPageBase page, object? value)
|
||||
extension(RazorPageBase page)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(value);
|
||||
return new HtmlString(json);
|
||||
/// <summary>Get a serialized JSON representation of the value.</summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <returns>The serialized JSON.</returns>
|
||||
/// <remarks>This bypasses unnecessary validation (e.g. not allowing null values) in <see cref="IJsonHelper.Serialize"/>.</remarks>
|
||||
public IHtmlContent ForJson(object? value)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(value);
|
||||
return new HtmlString(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,178 +19,193 @@ internal static class InternalExtensions
|
||||
/****
|
||||
** IMonitor
|
||||
****/
|
||||
/// <summary>Log a message for the player or developer the first time it occurs.</summary>
|
||||
/// <param name="monitor">The monitor through which to log the message.</param>
|
||||
/// <param name="hash">The hash of logged messages.</param>
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="level">The log severity level.</param>
|
||||
public static void LogOnce(this IMonitor monitor, HashSet<string> hash, string message, LogLevel level = LogLevel.Trace)
|
||||
/// <param name="monitor">The monitor to extend.</param>
|
||||
extension(IMonitor monitor)
|
||||
{
|
||||
if (!hash.Contains(message))
|
||||
/// <summary>Log a message for the player or developer the first time it occurs.</summary>
|
||||
/// <param name="hash">The hash of logged messages.</param>
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="level">The log severity level.</param>
|
||||
public void LogOnce(HashSet<string> hash, string message, LogLevel level = LogLevel.Trace)
|
||||
{
|
||||
monitor.Log(message, level);
|
||||
hash.Add(message);
|
||||
if (!hash.Contains(message))
|
||||
{
|
||||
monitor.Log(message, level);
|
||||
hash.Add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
** IModMetadata
|
||||
****/
|
||||
/// <summary>Log a message using the mod's monitor.</summary>
|
||||
/// <param name="metadata">The mod whose monitor to use.</param>
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="level">The log severity level.</param>
|
||||
public static void LogAsMod(this IModMetadata metadata, string message, LogLevel level = LogLevel.Trace)
|
||||
/// <param name="metadata">The mod metadata to extend.</param>
|
||||
extension(IModMetadata metadata)
|
||||
{
|
||||
if (metadata.Monitor is null)
|
||||
throw new InvalidOperationException($"Can't log as mod {metadata.DisplayName}: mod is broken or a content pack. Logged message:\n[{level}] {message}");
|
||||
/// <summary>Log a message using the mod's monitor.</summary>
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="level">The log severity level.</param>
|
||||
public void LogAsMod(string message, LogLevel level = LogLevel.Trace)
|
||||
{
|
||||
if (metadata.Monitor is null)
|
||||
throw new InvalidOperationException($"Can't log as mod {metadata.DisplayName}: mod is broken or a content pack. Logged message:\n[{level}] {message}");
|
||||
|
||||
metadata.Monitor.Log(message, level);
|
||||
}
|
||||
metadata.Monitor.Log(message, level);
|
||||
}
|
||||
|
||||
/// <summary>Log a message using the mod's monitor, but only if it hasn't already been logged since the last game launch.</summary>
|
||||
/// <param name="metadata">The mod whose monitor to use.</param>
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="level">The log severity level.</param>
|
||||
public static void LogAsModOnce(this IModMetadata metadata, string message, LogLevel level = LogLevel.Trace)
|
||||
{
|
||||
metadata.Monitor?.LogOnce(message, level);
|
||||
/// <summary>Log a message using the mod's monitor, but only if it hasn't already been logged since the last game launch.</summary>
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="level">The log severity level.</param>
|
||||
public void LogAsModOnce(string message, LogLevel level = LogLevel.Trace)
|
||||
{
|
||||
metadata.Monitor?.LogOnce(message, level);
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
** ManagedEvent
|
||||
****/
|
||||
/// <summary>Raise the event using the default event args and notify all handlers.</summary>
|
||||
/// <typeparam name="TEventArgs">The event args type to construct.</typeparam>
|
||||
/// <param name="event">The event to raise.</param>
|
||||
public static void RaiseEmpty<TEventArgs>(this ManagedEvent<TEventArgs> @event) where TEventArgs : new()
|
||||
/// <param name="event">The event to extend.</param>
|
||||
extension<TEventArgs>(ManagedEvent<TEventArgs> @event)
|
||||
where TEventArgs : new()
|
||||
{
|
||||
if (@event.HasListeners)
|
||||
@event.Raise(Singleton<TEventArgs>.Instance);
|
||||
/// <summary>Raise the event using the default event args and notify all handlers.</summary>
|
||||
public void RaiseEmpty()
|
||||
{
|
||||
if (@event.HasListeners)
|
||||
@event.Raise(Singleton<TEventArgs>.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
** ReaderWriterLockSlim
|
||||
****/
|
||||
/// <summary>Run code within a read lock.</summary>
|
||||
/// <param name="lock">The lock to set.</param>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public static void InReadLock(this ReaderWriterLockSlim @lock, Action action)
|
||||
/// <param name="lock">The lock to extend.</param>
|
||||
extension(ReaderWriterLockSlim @lock)
|
||||
{
|
||||
@lock.EnterReadLock();
|
||||
try
|
||||
/// <summary>Run code within a read lock.</summary>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public void InReadLock(Action action)
|
||||
{
|
||||
action();
|
||||
@lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Run code within a read lock.</summary>
|
||||
/// <typeparam name="TReturn">The action's return value.</typeparam>
|
||||
/// <param name="lock">The lock to set.</param>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public static TReturn InReadLock<TReturn>(this ReaderWriterLockSlim @lock, Func<TReturn> action)
|
||||
{
|
||||
@lock.EnterReadLock();
|
||||
try
|
||||
/// <summary>Run code within a read lock.</summary>
|
||||
/// <typeparam name="TReturn">The action's return value.</typeparam>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public TReturn InReadLock<TReturn>(Func<TReturn> action)
|
||||
{
|
||||
return action();
|
||||
@lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Run code within a write lock.</summary>
|
||||
/// <param name="lock">The lock to set.</param>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public static void InWriteLock(this ReaderWriterLockSlim @lock, Action action)
|
||||
{
|
||||
@lock.EnterWriteLock();
|
||||
try
|
||||
/// <summary>Run code within a write lock.</summary>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public void InWriteLock(Action action)
|
||||
{
|
||||
action();
|
||||
@lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Run code within a write lock.</summary>
|
||||
/// <typeparam name="TReturn">The action's return value.</typeparam>
|
||||
/// <param name="lock">The lock to set.</param>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public static TReturn InWriteLock<TReturn>(this ReaderWriterLockSlim @lock, Func<TReturn> action)
|
||||
{
|
||||
@lock.EnterWriteLock();
|
||||
try
|
||||
/// <summary>Run code within a write lock.</summary>
|
||||
/// <typeparam name="TReturn">The action's return value.</typeparam>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
public TReturn InWriteLock<TReturn>(Func<TReturn> action)
|
||||
{
|
||||
return action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@lock.ExitWriteLock();
|
||||
@lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
return action();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
** IActiveClickableMenu
|
||||
****/
|
||||
/// <summary>Get a string representation of the menu chain to the given menu (including the specified menu), in parent to child order.</summary>
|
||||
/// <param name="menu">The menu whose chain to get.</param>
|
||||
public static string GetMenuChainLabel(this IClickableMenu menu)
|
||||
/// <param name="menu">The clickable menu to extend.</param>
|
||||
extension(IClickableMenu menu)
|
||||
{
|
||||
static IEnumerable<IClickableMenu> GetAncestors(IClickableMenu menu)
|
||||
/// <summary>Get a string representation of the menu chain to the given menu (including the specified menu), in parent to child order.</summary>
|
||||
public string GetMenuChainLabel()
|
||||
{
|
||||
for (; menu != null; menu = menu.GetParentMenu())
|
||||
yield return menu;
|
||||
}
|
||||
static IEnumerable<IClickableMenu> GetAncestors(IClickableMenu menu)
|
||||
{
|
||||
for (; menu != null; menu = menu.GetParentMenu())
|
||||
yield return menu;
|
||||
}
|
||||
|
||||
return string.Join(" > ", GetAncestors(menu).Reverse().Select(p => p.GetType().FullName));
|
||||
return string.Join(" > ", GetAncestors(menu).Reverse().Select(p => p.GetType().FullName));
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
** Sprite batch
|
||||
****/
|
||||
/// <summary>Get whether the sprite batch is between a begin and end pair.</summary>
|
||||
/// <param name="spriteBatch">The sprite batch to check.</param>
|
||||
/// <param name="reflection">The reflection helper with which to access private fields.</param>
|
||||
public static bool IsOpen(this SpriteBatch spriteBatch, Reflector reflection)
|
||||
/// <param name="spriteBatch">The sprite batch to extend.</param>
|
||||
extension(SpriteBatch spriteBatch)
|
||||
{
|
||||
return reflection.GetField<bool>(spriteBatch, "_beginCalled").GetValue();
|
||||
/// <summary>Get whether the sprite batch is between a begin and end pair.</summary>
|
||||
/// <param name="reflection">The reflection helper with which to access private fields.</param>
|
||||
public bool IsOpen(Reflector reflection)
|
||||
{
|
||||
return reflection.GetField<bool>(spriteBatch, "_beginCalled").GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
** Texture2D
|
||||
****/
|
||||
/// <summary>Set the texture name field.</summary>
|
||||
/// <param name="texture">The texture whose name to set.</param>
|
||||
/// <param name="assetName">The asset name to set.</param>
|
||||
/// <returns>Returns the texture for chaining.</returns>
|
||||
[return: NotNullIfNotNull("texture")]
|
||||
public static Texture2D? SetName(this Texture2D? texture, IAssetName assetName)
|
||||
/// <param name="texture">The texture to extend.</param>
|
||||
extension(Texture2D? texture)
|
||||
{
|
||||
if (texture != null)
|
||||
texture.Name = assetName.Name;
|
||||
/// <summary>Set the texture name field.</summary>
|
||||
/// <param name="assetName">The asset name to set.</param>
|
||||
/// <returns>Returns the texture for chaining.</returns>
|
||||
[return: NotNullIfNotNull(nameof(texture))]
|
||||
public Texture2D? SetName(IAssetName assetName)
|
||||
{
|
||||
texture?.Name = assetName.Name;
|
||||
|
||||
return texture;
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>Set the texture name field.</summary>
|
||||
/// <param name="texture">The texture whose name to set.</param>
|
||||
/// <param name="assetName">The asset name to set.</param>
|
||||
/// <returns>Returns the texture for chaining.</returns>
|
||||
[return: NotNullIfNotNull("texture")]
|
||||
public static Texture2D? SetName(this Texture2D? texture, string assetName)
|
||||
{
|
||||
if (texture != null)
|
||||
texture.Name = assetName;
|
||||
/// <summary>Set the texture name field.</summary>
|
||||
/// <param name="assetName">The asset name to set.</param>
|
||||
/// <returns>Returns the texture for chaining.</returns>
|
||||
[return: NotNullIfNotNull(nameof(texture))]
|
||||
public Texture2D? SetName(string assetName)
|
||||
{
|
||||
texture?.Name = assetName;
|
||||
|
||||
return texture;
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace MonoMod.Utils;
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Temporary hotfix submitted by the MonoMod author.")]
|
||||
[SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "Temporary hotfix submitted by the MonoMod author.")]
|
||||
static class MiniMonoModHotfix
|
||||
internal static class MiniMonoModHotfix
|
||||
{
|
||||
// .NET Framework can break member ordering if using Module.Resolve* on certain members.
|
||||
|
||||
@@ -100,46 +100,52 @@ static class MiniMonoModHotfix
|
||||
}
|
||||
}
|
||||
|
||||
public static Type? GetRealDeclaringType(this MemberInfo member)
|
||||
extension(MemberInfo member)
|
||||
{
|
||||
return member.DeclaringType ?? member.Module.GetModuleType();
|
||||
public Type? GetRealDeclaringType()
|
||||
{
|
||||
return member.DeclaringType ?? member.Module.GetModuleType();
|
||||
}
|
||||
}
|
||||
|
||||
public static void FixReflectionCache(this Type? type)
|
||||
extension(Type? type)
|
||||
{
|
||||
if (t_RuntimeType == null || p_RuntimeType_Cache == null || m_RuntimeTypeCache_GetFieldList == null || m_RuntimeTypeCache_GetPropertyList == null)
|
||||
return;
|
||||
|
||||
for (; type != null; type = type.DeclaringType)
|
||||
public void FixReflectionCache()
|
||||
{
|
||||
// All types SHOULD inherit RuntimeType, including those built at runtime.
|
||||
// One might never know what awaits us in the depths of reflection hell though.
|
||||
if (!t_RuntimeType.IsInstanceOfType(type))
|
||||
continue;
|
||||
if (t_RuntimeType == null || p_RuntimeType_Cache == null || m_RuntimeTypeCache_GetFieldList == null || m_RuntimeTypeCache_GetPropertyList == null)
|
||||
return;
|
||||
|
||||
CacheFixEntry entry = _CacheFixed.GetValue(type, rt =>
|
||||
for (; type != null; type = type.DeclaringType)
|
||||
{
|
||||
// All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null.
|
||||
object cache = MiniMonoModHotfix.p_RuntimeType_Cache.GetValue(rt, MiniMonoModHotfix._NoArgs)!;
|
||||
Array properties = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetPropertyList);
|
||||
Array fields = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetFieldList);
|
||||
// All types SHOULD inherit RuntimeType, including those built at runtime.
|
||||
// One might never know what awaits us in the depths of reflection hell though.
|
||||
if (!t_RuntimeType.IsInstanceOfType(type))
|
||||
continue;
|
||||
|
||||
_FixReflectionCacheOrder<PropertyInfo>(properties);
|
||||
_FixReflectionCacheOrder<FieldInfo>(fields);
|
||||
|
||||
return new CacheFixEntry(cache, properties, fields, needsVerify: false);
|
||||
});
|
||||
|
||||
if (entry.NeedsVerify && !_Verify(entry, type))
|
||||
{
|
||||
lock (entry)
|
||||
CacheFixEntry entry = _CacheFixed.GetValue(type, rt =>
|
||||
{
|
||||
_FixReflectionCacheOrder<PropertyInfo>(entry.Properties);
|
||||
_FixReflectionCacheOrder<FieldInfo>(entry.Fields);
|
||||
}
|
||||
}
|
||||
// All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null.
|
||||
object cache = MiniMonoModHotfix.p_RuntimeType_Cache.GetValue(rt, MiniMonoModHotfix._NoArgs)!;
|
||||
Array properties = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetPropertyList);
|
||||
Array fields = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetFieldList);
|
||||
|
||||
entry.NeedsVerify = true;
|
||||
_FixReflectionCacheOrder<PropertyInfo>(properties);
|
||||
_FixReflectionCacheOrder<FieldInfo>(fields);
|
||||
|
||||
return new CacheFixEntry(cache, properties, fields, needsVerify: false);
|
||||
});
|
||||
|
||||
if (entry.NeedsVerify && !_Verify(entry, type))
|
||||
{
|
||||
lock (entry)
|
||||
{
|
||||
_FixReflectionCacheOrder<PropertyInfo>(entry.Properties);
|
||||
_FixReflectionCacheOrder<FieldInfo>(entry.Fields);
|
||||
}
|
||||
}
|
||||
|
||||
entry.NeedsVerify = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+78
-70
@@ -602,100 +602,108 @@ public static class SButtonExtensions
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
|
||||
/// <param name="key">The keyboard button to convert.</param>
|
||||
public static SButton ToSButton(this Keys key)
|
||||
extension(Keys key)
|
||||
{
|
||||
return (SButton)key;
|
||||
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
|
||||
public SButton ToSButton()
|
||||
{
|
||||
return (SButton)key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
|
||||
/// <param name="key">The controller button to convert.</param>
|
||||
public static SButton ToSButton(this Buttons key)
|
||||
extension(Buttons key)
|
||||
{
|
||||
return (SButton)(SButtonExtensions.ControllerOffset + key);
|
||||
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
|
||||
public SButton ToSButton()
|
||||
{
|
||||
return (SButton)(SButtonExtensions.ControllerOffset + key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
|
||||
/// <param name="input">The Stardew Valley button to convert.</param>
|
||||
public static SButton ToSButton(this InputButton input)
|
||||
extension(InputButton input)
|
||||
{
|
||||
// derived from InputButton constructors
|
||||
if (input.mouseLeft)
|
||||
return SButton.MouseLeft;
|
||||
if (input.mouseRight)
|
||||
return SButton.MouseRight;
|
||||
return input.key.ToSButton();
|
||||
/// <summary>Get the <see cref="SButton"/> equivalent for the given button.</summary>
|
||||
public SButton ToSButton()
|
||||
{
|
||||
// derived from InputButton constructors
|
||||
if (input.mouseLeft)
|
||||
return SButton.MouseLeft;
|
||||
if (input.mouseRight)
|
||||
return SButton.MouseRight;
|
||||
return input.key.ToSButton();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the <see cref="Keys"/> equivalent for the given button.</summary>
|
||||
/// <param name="input">The button to convert.</param>
|
||||
/// <param name="key">The keyboard equivalent.</param>
|
||||
/// <returns>Returns whether the value was converted successfully.</returns>
|
||||
public static bool TryGetKeyboard(this SButton input, out Keys key)
|
||||
/// <param name="input">The button to extend.</param>
|
||||
extension(SButton input)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(Keys), (int)input))
|
||||
/// <summary>Get the <see cref="Keys"/> equivalent for the given button.</summary>
|
||||
/// <param name="key">The keyboard equivalent.</param>
|
||||
/// <returns>Returns whether the value was converted successfully.</returns>
|
||||
public bool TryGetKeyboard(out Keys key)
|
||||
{
|
||||
key = (Keys)input;
|
||||
return true;
|
||||
if (Enum.IsDefined(typeof(Keys), (int)input))
|
||||
{
|
||||
key = (Keys)input;
|
||||
return true;
|
||||
}
|
||||
|
||||
key = Keys.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
key = Keys.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Get the <see cref="Buttons"/> equivalent for the given button.</summary>
|
||||
/// <param name="input">The button to convert.</param>
|
||||
/// <param name="button">The controller equivalent.</param>
|
||||
/// <returns>Returns whether the value was converted successfully.</returns>
|
||||
public static bool TryGetController(this SButton input, out Buttons button)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(Buttons), (int)input - SButtonExtensions.ControllerOffset))
|
||||
/// <summary>Get the <see cref="Buttons"/> equivalent for the given button.</summary>
|
||||
/// <param name="button">The controller equivalent.</param>
|
||||
/// <returns>Returns whether the value was converted successfully.</returns>
|
||||
public bool TryGetController(out Buttons button)
|
||||
{
|
||||
button = (Buttons)(input - SButtonExtensions.ControllerOffset);
|
||||
return true;
|
||||
if (Enum.IsDefined(typeof(Buttons), (int)input - SButtonExtensions.ControllerOffset))
|
||||
{
|
||||
button = (Buttons)(input - SButtonExtensions.ControllerOffset);
|
||||
return true;
|
||||
}
|
||||
|
||||
button = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
button = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Get the <see cref="InputButton"/> equivalent for the given button.</summary>
|
||||
/// <param name="input">The button to convert.</param>
|
||||
/// <param name="button">The Stardew Valley input button equivalent.</param>
|
||||
/// <returns>Returns whether the value was converted successfully.</returns>
|
||||
public static bool TryGetStardewInput(this SButton input, out InputButton button)
|
||||
{
|
||||
// keyboard
|
||||
if (input.TryGetKeyboard(out Keys key))
|
||||
/// <summary>Get the <see cref="InputButton"/> equivalent for the given button.</summary>
|
||||
/// <param name="button">The Stardew Valley input button equivalent.</param>
|
||||
/// <returns>Returns whether the value was converted successfully.</returns>
|
||||
public bool TryGetStardewInput(out InputButton button)
|
||||
{
|
||||
button = new InputButton(key);
|
||||
return true;
|
||||
// keyboard
|
||||
if (input.TryGetKeyboard(out Keys key))
|
||||
{
|
||||
button = new InputButton(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// mouse
|
||||
if (input is SButton.MouseLeft or SButton.MouseRight)
|
||||
{
|
||||
button = new InputButton(mouseLeft: input == SButton.MouseLeft);
|
||||
return true;
|
||||
}
|
||||
|
||||
// not valid
|
||||
button = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// mouse
|
||||
if (input is SButton.MouseLeft or SButton.MouseRight)
|
||||
/// <summary>Get whether the given button is equivalent to <see cref="Options.useToolButton"/>.</summary>
|
||||
public bool IsUseToolButton()
|
||||
{
|
||||
button = new InputButton(mouseLeft: input == SButton.MouseLeft);
|
||||
return true;
|
||||
return input == SButton.ControllerX || Game1.options.useToolButton.Any(p => p.ToSButton() == input);
|
||||
}
|
||||
|
||||
// not valid
|
||||
button = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Get whether the given button is equivalent to <see cref="Options.useToolButton"/>.</summary>
|
||||
/// <param name="input">The button.</param>
|
||||
public static bool IsUseToolButton(this SButton input)
|
||||
{
|
||||
return input == SButton.ControllerX || Game1.options.useToolButton.Any(p => p.ToSButton() == input);
|
||||
}
|
||||
|
||||
/// <summary>Get whether the given button is equivalent to <see cref="Options.actionButton"/>.</summary>
|
||||
/// <param name="input">The button.</param>
|
||||
public static bool IsActionButton(this SButton input)
|
||||
{
|
||||
return input == SButton.ControllerA || Game1.options.actionButton.Any(p => p.ToSButton() == input);
|
||||
/// <summary>Get whether the given button is equivalent to <see cref="Options.actionButton"/>.</summary>
|
||||
public bool IsActionButton()
|
||||
{
|
||||
return input == SButton.ControllerA || Game1.options.actionButton.Any(p => p.ToSButton() == input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,13 @@ public enum SButtonState
|
||||
/// <summary>Extension methods for <see cref="SButtonState"/>.</summary>
|
||||
public static class InputStatusExtensions
|
||||
{
|
||||
/// <summary>Whether the button was pressed or held.</summary>
|
||||
/// <param name="state">The button state.</param>
|
||||
public static bool IsDown(this SButtonState state)
|
||||
extension(SButtonState state)
|
||||
{
|
||||
return state is SButtonState.Held or SButtonState.Pressed;
|
||||
/// <summary>Whether the button was pressed or held.</summary>
|
||||
public bool IsDown()
|
||||
{
|
||||
return state is SButtonState.Held or SButtonState.Pressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user