From 3a4758dfa63f0f8e03166bffa15e16f7995499d7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 14 May 2022 13:32:13 -0400 Subject: [PATCH 1/5] remove warning for mods which use `dynamic` --- docs/release-notes.md | 5 +++++ src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs | 1 + src/SMAPI/Framework/Logging/LogManager.cs | 5 ----- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 5 ----- src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs | 3 --- src/SMAPI/Metadata/InstructionMetadata.cs | 1 - 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 6311d7dc..da3b666c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,11 @@ ← [README](README.md) # Release notes +## Upcoming release +* For mod authors: + * Removed warning for mods which use `dynamic`. + _This no longer causes errors on Linux/macOS after Stardew Valley 1.5.5._ + ## 3.14.3 Released 12 May 2022 for Stardew Valley 1.5.6 or later. diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs index afebba87..cf804df4 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs @@ -19,6 +19,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData PatchesGame = 4, /// The mod uses the dynamic keyword which won't work on Linux/macOS. + [Obsolete("This value is no longer used by SMAPI and will be removed in the upcoming SMAPI 4.0.0.")] UsesDynamic = 8, /// The mod references specialized 'unvalidated update tick' events which may impact stability. diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index b94807b5..ed5b6959 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -511,11 +511,6 @@ namespace StardewModdingAPI.Framework.Logging "These mods have no update keys in their manifest. SMAPI may not notify you about updates for these", "mods. Consider notifying the mod authors about this problem." ); - - // not crossplatform - this.LogModWarningGroup(modsWithWarnings, ModWarning.UsesDynamic, LogLevel.Debug, "Not crossplatform", - "These mods use the 'dynamic' keyword, and won't work on Linux/macOS." - ); } } diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index a6756e0e..fb5ebc01 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -414,11 +414,6 @@ namespace StardewModdingAPI.Framework.ModLoading mod.SetWarning(ModWarning.UsesUnvalidatedUpdateTick); break; - case InstructionHandleResult.DetectedDynamic: - template = $"{logPrefix}Detected 'dynamic' keyword ($phrase) in assembly {filename}."; - mod.SetWarning(ModWarning.UsesDynamic); - break; - case InstructionHandleResult.DetectedConsoleAccess: template = $"{logPrefix}Detected direct console access ($phrase) in assembly {filename}."; mod.SetWarning(ModWarning.AccessesConsole); diff --git a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs index baffc50e..e3f108cb 100644 --- a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs +++ b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs @@ -20,9 +20,6 @@ namespace StardewModdingAPI.Framework.ModLoading /// The instruction is compatible, but affects the save serializer in a way that may make saves unloadable without the mod. DetectedSaveSerializer, - /// The instruction is compatible, but uses the dynamic keyword which won't work on Linux/macOS. - DetectedDynamic, - /// The instruction is compatible, but references or which may impact stability. DetectedUnvalidatedUpdateTick, diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 9b56f963..4d512546 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -67,7 +67,6 @@ namespace StardewModdingAPI.Metadata /**** ** detect code which may impact game stability ****/ - yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic); yield return new FieldFinder(typeof(SaveGame).FullName!, new[] { nameof(SaveGame.serializer), nameof(SaveGame.farmerSerializer), nameof(SaveGame.locationSerializer) }, InstructionHandleResult.DetectedSaveSerializer); yield return new EventFinder(typeof(ISpecializedEvents).FullName!, new[] { nameof(ISpecializedEvents.UnvalidatedUpdateTicked), nameof(ISpecializedEvents.UnvalidatedUpdateTicking) }, InstructionHandleResult.DetectedUnvalidatedUpdateTick); From 048f41244f6b2c7f95ac5bf75be2b16f42b99169 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 15 May 2022 01:54:01 -0400 Subject: [PATCH 2/5] reduce performance impact of deprecation warnings Creating a stack is *very* slow, so it should be avoided if possible until after the duplicate-warning check. --- docs/release-notes.md | 3 ++ src/SMAPI/Constants.cs | 2 +- src/SMAPI/Context.cs | 5 +++ src/SMAPI/Framework/Content/AssetInfo.cs | 4 +- .../Content/AssetInterceptorChange.cs | 10 +++++ src/SMAPI/Framework/ContentCoordinator.cs | 18 +++++--- .../ContentManagers/GameContentManager.cs | 12 +++++- .../Deprecations/DeprecationManager.cs | 41 ++++++++++++------- src/SMAPI/Framework/Events/ManagedEvent.cs | 12 ++++++ .../Framework/ModHelpers/CommandHelper.cs | 2 +- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 2 +- src/SMAPI/Framework/ModRegistry.cs | 9 +--- src/SMAPI/Framework/SCore.cs | 18 ++++++-- src/SMAPI/Utilities/PerScreen.cs | 2 +- 14 files changed, 103 insertions(+), 37 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index da3b666c..1d1e3760 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,9 @@ # Release notes ## Upcoming release +* For players: + * Improved performance for mods using deprecated APIs. + * For mod authors: * Removed warning for mods which use `dynamic`. _This no longer causes errors on Linux/macOS after Stardew Valley 1.5.5._ 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 /// 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. internal static bool IsWorldFullyUnloaded => Context.LoadStage is LoadStage.ReturningToTitle or LoadStage.None; + /// If SMAPI is currently waiting for mod code, the mods to which it belongs (with the most recent at the top of the stack). + /// This is heuristic only. 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. + internal static Stack 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(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(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 loader in this.Loaders) { // check if loader applies + Context.HeuristicModsRunningCode.Push(loader.Mod); try { if (!loader.Data.CanLoad(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(), new List()); @@ -617,9 +622,7 @@ namespace StardewModdingAPI.Framework Mod: loader.Mod, OnBehalfOf: null, Priority: AssetLoadPriority.Exclusive, - GetData: assetInfo => loader.Data.Load( - this.GetLegacyAssetInfo(assetInfo) - ) + GetData: assetInfo => loader.Data.Load(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(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( - this.GetLegacyAssetData(assetData) - ) + ApplyEdit: assetData => editor.Data.Edit(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; } - /// Get a mod for the closest assembly registered as a source of deprecation warnings. - /// Returns the source name, or null if no registered assemblies were found. - public IModMetadata? GetModFromStack() - { - return this.ModRegistry.GetFromStack(); - } - /// Get a mod from its unique ID. /// The mod's unique ID. public IModMetadata? GetMod(string modId) @@ -52,7 +45,7 @@ namespace StardewModdingAPI.Framework.Deprecations } /// Log a deprecation warning. - /// The mod which used the deprecated code, if known. + /// The mod which used the deprecated code, or null to get it heuristically. Note that getting it heuristically is very slow in some cases, and should be avoided if at all possible. /// A noun phrase describing what is deprecated. /// The SMAPI version which deprecated it. /// How deprecated the code is. @@ -60,18 +53,38 @@ namespace StardewModdingAPI.Framework.Deprecations /// Whether to log a stack trace showing where the deprecated code is in the mod. 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 ?? ""}::{nounPhrase}::{version}"; + string cacheKey = $"{heuristicSource?.Manifest.UniqueID ?? ""}::{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)); } /// A placeholder method used to track deprecated code for which a separate warning will be shown. 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 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 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 } /// Get the mod metadata from the closest assembly registered as a source of deprecation warnings. + /// The call stack to analyze. /// Returns the mod's metadata, or null if no registered assemblies were found. - 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 } /// Load a mod's entry class. + /// The mod metadata whose entry class is being loaded. /// The mod assembly. /// The loaded instance. /// The error indicating why loading failed (if applicable). /// Returns whether the mod entry class was successfully loaded. - 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)} constructor with null", "3.14.0", DeprecationLevel.Notice From 8505f4ae4dc5c7bf64c1ea9ae4ea9fd38c4bd1c4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 15 May 2022 15:07:52 -0400 Subject: [PATCH 3/5] fix typo --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 85e109c8..38a21383 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -121,7 +121,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } catch (Exception ex) when (ex is not SContentLoadException) { - throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected occurred.", ex); + throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex); } // track & return asset From 3253797cbab1e49901b9da3701954f93282363fd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 15 May 2022 19:13:32 -0400 Subject: [PATCH 4/5] fix wiki client not being disposed --- src/SMAPI.Toolkit/ModToolkit.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index ce14b057..0df75a31 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -57,7 +57,7 @@ namespace StardewModdingAPI.Toolkit /// Extract mod metadata from the wiki compatibility list. public async Task GetWikiCompatibilityListAsync() { - WikiClient client = new(this.UserAgent); + using WikiClient client = new(this.UserAgent); return await client.FetchModsAsync(); } From cd843dc10b902adc32e96e34c5db3ebf98e9a159 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 15 May 2022 19:14:28 -0400 Subject: [PATCH 5/5] prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 4 +++- src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.ErrorHandler/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/build/common.targets b/build/common.targets index a7db917c..76343b8d 100644 --- a/build/common.targets +++ b/build/common.targets @@ -1,7 +1,7 @@ - 3.14.3 + 3.14.4 SMAPI latest $(AssemblySearchPaths);{GAC} diff --git a/docs/release-notes.md b/docs/release-notes.md index 1d1e3760..c29bc39c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,7 +1,9 @@ ← [README](README.md) # Release notes -## Upcoming release +## 3.14.4 +Released 15 May 2022 for Stardew Valley 1.5.6 or later. + * For players: * Improved performance for mods using deprecated APIs. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 0e2b023d..78d26802 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.14.3", + "Version": "3.14.4", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.14.3" + "MinimumApiVersion": "3.14.4" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index f449b3bd..be1030f4 100644 --- a/src/SMAPI.Mods.ErrorHandler/manifest.json +++ b/src/SMAPI.Mods.ErrorHandler/manifest.json @@ -1,9 +1,9 @@ { "Name": "Error Handler", "Author": "SMAPI", - "Version": "3.14.3", + "Version": "3.14.4", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.14.3" + "MinimumApiVersion": "3.14.4" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 23e241b5..8a50162e 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.14.3", + "Version": "3.14.4", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.14.3" + "MinimumApiVersion": "3.14.4" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 6c9a56cc..357b8db8 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -50,7 +50,7 @@ namespace StardewModdingAPI internal static int? LogScreenId { get; set; } /// SMAPI's current raw semantic version. - internal static string RawApiVersion = "3.14.3"; + internal static string RawApiVersion = "3.14.4"; } /// Contains SMAPI's constants and assumptions.