summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI')
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Context.cs5
-rw-r--r--src/SMAPI/Framework/Content/AssetInfo.cs4
-rw-r--r--src/SMAPI/Framework/Content/AssetInterceptorChange.cs10
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs18
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs12
-rw-r--r--src/SMAPI/Framework/Deprecations/DeprecationManager.cs41
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs12
-rw-r--r--src/SMAPI/Framework/ModHelpers/CommandHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModRegistry.cs9
-rw-r--r--src/SMAPI/Framework/SCore.cs18
-rw-r--r--src/SMAPI/Utilities/PerScreen.cs2
13 files changed, 100 insertions, 37 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index ef729f4f..6c9a56cc 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -84,7 +84,7 @@ namespace StardewModdingAPI
get
{
SCore.DeprecationManager.Warn(
- source: SCore.DeprecationManager.GetModFromStack(),
+ source: null,
nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}",
version: "3.14.0",
severity: DeprecationLevel.Notice
diff --git a/src/SMAPI/Context.cs b/src/SMAPI/Context.cs
index aa4ecf35..c822908e 100644
--- a/src/SMAPI/Context.cs
+++ b/src/SMAPI/Context.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Events;
+using StardewModdingAPI.Framework;
using StardewModdingAPI.Utilities;
using StardewValley;
using StardewValley.Menus;
@@ -41,6 +42,10 @@ namespace StardewModdingAPI
/// <summary>Whether the in-game world is completely unloaded and not in the process of being loaded. The world may still exist in memory at this point, but should be ignored.</summary>
internal static bool IsWorldFullyUnloaded => Context.LoadStage is LoadStage.ReturningToTitle or LoadStage.None;
+ /// <summary>If SMAPI is currently waiting for mod code, the mods to which it belongs (with the most recent at the top of the stack).</summary>
+ /// <remarks><strong>This is heuristic only.</strong> It provides a quick way to identify the most likely mod for deprecation warnings, but it should be followed with a more accurate check if needed.</remarks>
+ internal static Stack<IModMetadata> HeuristicModsRunningCode { get; } = new();
+
/*********
** Accessors
diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs
index a4f0a408..773e3126 100644
--- a/src/SMAPI/Framework/Content/AssetInfo.cs
+++ b/src/SMAPI/Framework/Content/AssetInfo.cs
@@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.Content
get
{
SCore.DeprecationManager.Warn(
- source: SCore.DeprecationManager.GetModFromStack(),
+ source: null,
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}",
version: "3.14.0",
severity: DeprecationLevel.Notice,
@@ -76,7 +76,7 @@ namespace StardewModdingAPI.Framework.Content
public bool AssetNameEquals(string path)
{
SCore.DeprecationManager.Warn(
- source: SCore.DeprecationManager.GetModFromStack(),
+ source: null,
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}",
version: "3.14.0",
severity: DeprecationLevel.Notice,
diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs
index fc8199e8..f3d4f3f4 100644
--- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs
+++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs
@@ -64,6 +64,7 @@ namespace StardewModdingAPI.Framework.Content
// check edit
if (this.Instance is IAssetEditor editor)
{
+ Context.HeuristicModsRunningCode.Push(this.Mod);
try
{
if (editor.CanEdit<TAsset>(asset))
@@ -73,11 +74,16 @@ namespace StardewModdingAPI.Framework.Content
{
this.Mod.LogAsMod($"Mod failed when checking whether it could edit asset '{asset.Name}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
}
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
}
// check load
if (this.Instance is IAssetLoader loader)
{
+ Context.HeuristicModsRunningCode.Push(this.Mod);
try
{
if (loader.CanLoad<TAsset>(asset))
@@ -87,6 +93,10 @@ namespace StardewModdingAPI.Framework.Content
{
this.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{asset.Name}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
}
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
}
return false;
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index a24581a0..2b13f57a 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -595,6 +595,7 @@ namespace StardewModdingAPI.Framework
foreach (ModLinked<IAssetLoader> loader in this.Loaders)
{
// check if loader applies
+ Context.HeuristicModsRunningCode.Push(loader.Mod);
try
{
if (!loader.Data.CanLoad<T>(legacyInfo))
@@ -605,6 +606,10 @@ namespace StardewModdingAPI.Framework
loader.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{legacyInfo.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
continue;
}
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
// add operation
group ??= new AssetOperationGroup(new List<AssetLoadOperation>(), new List<AssetEditOperation>());
@@ -617,9 +622,7 @@ namespace StardewModdingAPI.Framework
Mod: loader.Mod,
OnBehalfOf: null,
Priority: AssetLoadPriority.Exclusive,
- GetData: assetInfo => loader.Data.Load<T>(
- this.GetLegacyAssetInfo(assetInfo)
- )
+ GetData: assetInfo => loader.Data.Load<T>(this.GetLegacyAssetInfo(assetInfo))
)
)
);
@@ -629,6 +632,7 @@ namespace StardewModdingAPI.Framework
foreach (var editor in this.Editors)
{
// check if editor applies
+ Context.HeuristicModsRunningCode.Push(editor.Mod);
try
{
if (!editor.Data.CanEdit<T>(legacyInfo))
@@ -639,6 +643,10 @@ namespace StardewModdingAPI.Framework
editor.Mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{legacyInfo.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
continue;
}
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
// HACK
//
@@ -672,9 +680,7 @@ namespace StardewModdingAPI.Framework
Mod: editor.Mod,
OnBehalfOf: null,
Priority: priority,
- ApplyEdit: assetData => editor.Data.Edit<T>(
- this.GetLegacyAssetData(assetData)
- )
+ ApplyEdit: assetData => editor.Data.Edit<T>(this.GetLegacyAssetData(assetData))
)
)
);
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 2aa50542..1c603f85 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -177,6 +177,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// fetch asset from loader
IModMetadata mod = loader.Mod;
T data;
+ Context.HeuristicModsRunningCode.Push(loader.Mod);
try
{
data = (T)loader.GetData(info);
@@ -187,6 +188,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
mod.LogAsMod($"Mod crashed when loading asset '{info.Name}'{this.GetOnBehalfOfLabel(loader.OnBehalfOf)}. SMAPI will use the default asset instead. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
return null;
}
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
// return matched asset
return this.TryFixAndValidateLoadedAsset(info, data, loader)
@@ -229,6 +234,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// try edit
object prevAsset = asset.Data;
+ Context.HeuristicModsRunningCode.Push(editor.Mod);
try
{
editor.ApplyEdit(asset);
@@ -238,9 +244,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
mod.LogAsMod($"Mod crashed when editing asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)}, which may cause errors in-game. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
}
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
// validate edit
- // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- it's only guaranteed non-null after this method
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- it's only guaranteed non-null after this method
if (asset.Data == null)
{
mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn);
diff --git a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs
index 288abde2..3bbbd7b8 100644
--- a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs
+++ b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs
@@ -37,13 +37,6 @@ namespace StardewModdingAPI.Framework.Deprecations
this.ModRegistry = modRegistry;
}
- /// <summary>Get a mod for the closest assembly registered as a source of deprecation warnings.</summary>
- /// <returns>Returns the source name, or <c>null</c> if no registered assemblies were found.</returns>
- public IModMetadata? GetModFromStack()
- {
- return this.ModRegistry.GetFromStack();
- }
-
/// <summary>Get a mod from its unique ID.</summary>
/// <param name="modId">The mod's unique ID.</param>
public IModMetadata? GetMod(string modId)
@@ -52,7 +45,7 @@ namespace StardewModdingAPI.Framework.Deprecations
}
/// <summary>Log a deprecation warning.</summary>
- /// <param name="source">The mod which used the deprecated code, if known.</param>
+ /// <param name="source">The mod which used the deprecated code, or <c>null</c> to get it heuristically. Note that getting it heuristically is very slow in some cases, and should be avoided if at all possible.</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>
@@ -60,18 +53,38 @@ namespace StardewModdingAPI.Framework.Deprecations
/// <param name="logStackTrace">Whether to log a stack trace showing where the deprecated code is in the mod.</param>
public void Warn(IModMetadata? source, string nounPhrase, string version, DeprecationLevel severity, string[]? unlessStackIncludes = null, bool logStackTrace = true)
{
+ // get heuristic source
+ // The call stack is usually the most reliable way to get the source if it's unknown. This is *very* slow
+ // though, especially before we check whether this is a duplicate warning. The initial cache check uses a
+ // quick heuristic method if at all possible to avoid that.
+ IModMetadata? heuristicSource = source;
+ ImmutableStackTrace? stack = null;
+ if (heuristicSource is null)
+ Context.HeuristicModsRunningCode.TryPeek(out heuristicSource);
+ if (heuristicSource is null)
+ {
+ stack = ImmutableStackTrace.Get(skipFrames: 1);
+ heuristicSource = this.ModRegistry.GetFromStack(stack.GetFrames());
+ }
+
// skip if already warned
- string cacheKey = $"{source?.DisplayName ?? "<unknown>"}::{nounPhrase}::{version}";
+ string cacheKey = $"{heuristicSource?.Manifest.UniqueID ?? "<unknown>"}::{nounPhrase}::{version}";
if (this.LoggedDeprecations.Contains(cacheKey))
return;
+ this.LoggedDeprecations.Add(cacheKey);
- // warn if valid
- ImmutableStackTrace stack = ImmutableStackTrace.Get(skipFrames: 1);
- if (!this.ShouldSuppress(stack, unlessStackIncludes))
+ // get more accurate source
+ if (stack is not null)
+ source ??= heuristicSource!;
+ else
{
- this.LoggedDeprecations.Add(cacheKey);
- this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, stack, logStackTrace));
+ stack ??= ImmutableStackTrace.Get(skipFrames: 1);
+ source ??= this.ModRegistry.GetFromStack(stack.GetFrames());
}
+
+ // log unless suppressed
+ if (!this.ShouldSuppress(stack, unlessStackIncludes))
+ this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, stack, logStackTrace));
}
/// <summary>A placeholder method used to track deprecated code for which a separate warning will be shown.</summary>
diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs
index 8a3ca839..a72d8d04 100644
--- a/src/SMAPI/Framework/Events/ManagedEvent.cs
+++ b/src/SMAPI/Framework/Events/ManagedEvent.cs
@@ -104,6 +104,8 @@ namespace StardewModdingAPI.Framework.Events
// raise event
foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
{
+ Context.HeuristicModsRunningCode.Push(handler.SourceMod);
+
try
{
handler.Handler(null, args);
@@ -112,6 +114,10 @@ namespace StardewModdingAPI.Framework.Events
{
this.LogError(handler, ex);
}
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
}
}
@@ -126,6 +132,8 @@ namespace StardewModdingAPI.Framework.Events
// raise event
foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
{
+ Context.HeuristicModsRunningCode.Push(handler.SourceMod);
+
try
{
invoke(handler.SourceMod, args => handler.Handler(null, args));
@@ -134,6 +142,10 @@ namespace StardewModdingAPI.Framework.Events
{
this.LogError(handler, ex);
}
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
}
}
diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
index f39ed42e..ddbd618a 100644
--- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
@@ -37,7 +37,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
public bool Trigger(string name, string[] arguments)
{
SCore.DeprecationManager.Warn(
- source: SCore.DeprecationManager.GetMod(this.ModID),
+ source: this.Mod,
nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}",
version: "3.8.1",
severity: DeprecationLevel.Notice
diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
index 008195d9..48973691 100644
--- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
@@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
get
{
SCore.DeprecationManager.Warn(
- source: SCore.DeprecationManager.GetMod(this.ModID),
+ source: this.Mod,
nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}",
version: "3.14.0",
severity: DeprecationLevel.Notice
diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs
index 1ae5643f..1b0a076f 100644
--- a/src/SMAPI/Framework/ModRegistry.cs
+++ b/src/SMAPI/Framework/ModRegistry.cs
@@ -99,14 +99,10 @@ namespace StardewModdingAPI.Framework
}
/// <summary>Get the mod metadata from the closest assembly registered as a source of deprecation warnings.</summary>
+ /// <param name="frames">The call stack to analyze.</param>
/// <returns>Returns the mod's metadata, or <c>null</c> if no registered assemblies were found.</returns>
- public IModMetadata? GetFromStack()
+ public IModMetadata? GetFromStack(StackFrame[] frames)
{
- // get stack frames
- StackTrace stack = new();
- StackFrame[] frames = stack.GetFrames();
-
- // search stack for a source assembly
foreach (StackFrame frame in frames)
{
IModMetadata? mod = this.GetFrom(frame);
@@ -114,7 +110,6 @@ namespace StardewModdingAPI.Framework
return mod;
}
- // no known assembly found
return null;
}
}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 5ae4fdbb..7042e83a 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -1677,6 +1677,7 @@ namespace StardewModdingAPI.Framework
#pragma warning restore CS0612, CS0618
// call entry method
+ Context.HeuristicModsRunningCode.Push(metadata);
try
{
IMod mod = metadata.Mod!;
@@ -1705,6 +1706,7 @@ namespace StardewModdingAPI.Framework
{
this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error);
}
+ Context.HeuristicModsRunningCode.TryPop(out _);
}
// unlock mod integrations
@@ -1852,7 +1854,7 @@ namespace StardewModdingAPI.Framework
try
{
// get mod instance
- if (!this.TryLoadModEntry(modAssembly, out Mod? modEntry, out errorReasonPhrase))
+ if (!this.TryLoadModEntry(mod, modAssembly, out Mod? modEntry, out errorReasonPhrase))
{
failReason = ModFailReason.LoadFailed;
return false;
@@ -1954,11 +1956,12 @@ namespace StardewModdingAPI.Framework
}
/// <summary>Load a mod's entry class.</summary>
+ /// <param name="metadata">The mod metadata whose entry class is being loaded.</param>
/// <param name="modAssembly">The mod assembly.</param>
/// <param name="mod">The loaded instance.</param>
/// <param name="error">The error indicating why loading failed (if applicable).</param>
/// <returns>Returns whether the mod entry class was successfully loaded.</returns>
- private bool TryLoadModEntry(Assembly modAssembly, [NotNullWhen(true)] out Mod? mod, [NotNullWhen(false)] out string? error)
+ private bool TryLoadModEntry(IModMetadata metadata, Assembly modAssembly, [NotNullWhen(true)] out Mod? mod, [NotNullWhen(false)] out string? error)
{
mod = null;
@@ -1976,7 +1979,16 @@ namespace StardewModdingAPI.Framework
}
// get implementation
- mod = (Mod?)modAssembly.CreateInstance(modEntries[0].ToString());
+ Context.HeuristicModsRunningCode.Push(metadata);
+ try
+ {
+ mod = (Mod?)modAssembly.CreateInstance(modEntries[0].ToString());
+ }
+ finally
+ {
+ Context.HeuristicModsRunningCode.TryPop(out _);
+ }
+
if (mod == null)
{
error = "its entry class couldn't be instantiated.";
diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs
index 1c4c56fe..b86310b8 100644
--- a/src/SMAPI/Utilities/PerScreen.cs
+++ b/src/SMAPI/Utilities/PerScreen.cs
@@ -97,7 +97,7 @@ namespace StardewModdingAPI.Utilities
if (!nullExpected)
{
SCore.DeprecationManager.Warn(
- SCore.DeprecationManager.GetModFromStack(),
+ null,
$"calling the {nameof(PerScreen<T>)} constructor with null",
"3.14.0",
DeprecationLevel.Notice