Skip to content

Commit

Permalink
Merge branch 'develop' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
Pathoschild committed May 15, 2022
2 parents a9cadc7 + cd843dc commit 5731b01
Show file tree
Hide file tree
Showing 25 changed files with 121 additions and 61 deletions.
2 changes: 1 addition & 1 deletion build/common.targets
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
<Version>3.14.3</Version>
<Version>3.14.4</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
Expand Down
10 changes: 10 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
[README](README.md)

# Release notes
## 3.14.4
Released 15 May 2022 for Stardew Valley 1.5.6 or later.

* 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._

## 3.14.3
Released 12 May 2022 for Stardew Valley 1.5.6 or later.

Expand Down
4 changes: 2 additions & 2 deletions src/SMAPI.Mods.ConsoleCommands/manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}
4 changes: 2 additions & 2 deletions src/SMAPI.Mods.ErrorHandler/manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}
4 changes: 2 additions & 2 deletions src/SMAPI.Mods.SaveBackup/manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}
1 change: 1 addition & 0 deletions src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum ModWarning
PatchesGame = 4,

/// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/macOS.</summary>
[Obsolete("This value is no longer used by SMAPI and will be removed in the upcoming SMAPI 4.0.0.")]
UsesDynamic = 8,

/// <summary>The mod references specialized 'unvalidated update tick' events which may impact stability.</summary>
Expand Down
2 changes: 1 addition & 1 deletion src/SMAPI.Toolkit/ModToolkit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public IEnumerable<DirectoryInfo> GetGameFolders()
/// <summary>Extract mod metadata from the wiki compatibility list.</summary>
public async Task<WikiModList> GetWikiCompatibilityListAsync()
{
WikiClient client = new(this.UserAgent);
using WikiClient client = new(this.UserAgent);
return await client.FetchModsAsync();
}

Expand Down
4 changes: 2 additions & 2 deletions src/SMAPI/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal static class EarlyConstants
internal static int? LogScreenId { get; set; }

/// <summary>SMAPI's current raw semantic version.</summary>
internal static string RawApiVersion = "3.14.3";
internal static string RawApiVersion = "3.14.4";
}

/// <summary>Contains SMAPI's constants and assumptions.</summary>
Expand Down Expand Up @@ -84,7 +84,7 @@ public static string ExecutionPath
get
{
SCore.DeprecationManager.Warn(
source: SCore.DeprecationManager.GetModFromStack(),
source: null,
nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}",
version: "3.14.0",
severity: DeprecationLevel.Notice
Expand Down
5 changes: 5 additions & 0 deletions src/SMAPI/Context.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -41,6 +42,10 @@ internal static LoadStage LoadStage
/// <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
Expand Down
4 changes: 2 additions & 2 deletions src/SMAPI/Framework/Content/AssetInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public string AssetName
get
{
SCore.DeprecationManager.Warn(
source: SCore.DeprecationManager.GetModFromStack(),
source: null,
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}",
version: "3.14.0",
severity: DeprecationLevel.Notice,
Expand Down Expand Up @@ -76,7 +76,7 @@ public AssetInfo(string? locale, IAssetName assetName, Type type, Func<string, s
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,
Expand Down
10 changes: 10 additions & 0 deletions src/SMAPI/Framework/Content/AssetInterceptorChange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ private bool CanInterceptImpl<TAsset>(IAssetInfo asset)
// check edit
if (this.Instance is IAssetEditor editor)
{
Context.HeuristicModsRunningCode.Push(this.Mod);
try
{
if (editor.CanEdit<TAsset>(asset))
Expand All @@ -73,11 +74,16 @@ private bool CanInterceptImpl<TAsset>(IAssetInfo asset)
{
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))
Expand All @@ -87,6 +93,10 @@ private bool CanInterceptImpl<TAsset>(IAssetInfo asset)
{
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;
Expand Down
18 changes: 12 additions & 6 deletions src/SMAPI/Framework/ContentCoordinator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ private bool TryLoadVanillaAsset<T>(string assetName, [NotNullWhen(true)] out T?
foreach (ModLinked<IAssetLoader> loader in this.Loaders)
{
// check if loader applies
Context.HeuristicModsRunningCode.Push(loader.Mod);
try
{
if (!loader.Data.CanLoad<T>(legacyInfo))
Expand All @@ -605,6 +606,10 @@ private bool TryLoadVanillaAsset<T>(string assetName, [NotNullWhen(true)] out T?
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>());
Expand All @@ -617,9 +622,7 @@ private bool TryLoadVanillaAsset<T>(string assetName, [NotNullWhen(true)] out T?
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))
)
)
);
Expand All @@ -629,6 +632,7 @@ private bool TryLoadVanillaAsset<T>(string assetName, [NotNullWhen(true)] out T?
foreach (var editor in this.Editors)
{
// check if editor applies
Context.HeuristicModsRunningCode.Push(editor.Mod);
try
{
if (!editor.Data.CanEdit<T>(legacyInfo))
Expand All @@ -639,6 +643,10 @@ private bool TryLoadVanillaAsset<T>(string assetName, [NotNullWhen(true)] out T?
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
//
Expand Down Expand Up @@ -672,9 +680,7 @@ private bool TryLoadVanillaAsset<T>(string assetName, [NotNullWhen(true)] out T?
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))
)
)
);
Expand Down
12 changes: 11 additions & 1 deletion src/SMAPI/Framework/ContentManagers/GameContentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ public override LocalizedContentManager CreateTemporary()
// fetch asset from loader
IModMetadata mod = loader.Mod;
T data;
Context.HeuristicModsRunningCode.Push(loader.Mod);
try
{
data = (T)loader.GetData(info);
Expand All @@ -187,6 +188,10 @@ public override LocalizedContentManager CreateTemporary()
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)
Expand Down Expand Up @@ -229,6 +234,7 @@ private IAssetData ApplyEditors<T>(IAssetInfo info, IAssetData asset, List<Asset

// try edit
object prevAsset = asset.Data;
Context.HeuristicModsRunningCode.Push(editor.Mod);
try
{
editor.ApplyEdit(asset);
Expand All @@ -238,9 +244,13 @@ private IAssetData ApplyEditors<T>(IAssetInfo info, IAssetData asset, List<Asset
{
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);
Expand Down
2 changes: 1 addition & 1 deletion src/SMAPI/Framework/ContentManagers/ModContentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public override T LoadExact<T>(IAssetName assetName, bool useCache)
}
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
Expand Down
41 changes: 27 additions & 14 deletions src/SMAPI/Framework/Deprecations/DeprecationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,6 @@ public DeprecationManager(IMonitor monitor, ModRegistry modRegistry)
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)
Expand All @@ -52,26 +45,46 @@ public DeprecationManager(IMonitor monitor, ModRegistry modRegistry)
}

/// <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>
/// <param name="unlessStackIncludes">A list of stack trace substrings which should suppress deprecation warnings if they appear in the stack trace.</param>
/// <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>
Expand Down
12 changes: 12 additions & 0 deletions src/SMAPI/Framework/Events/ManagedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ public void Raise(TEventArgs args)
// raise event
foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
{
Context.HeuristicModsRunningCode.Push(handler.SourceMod);

try
{
handler.Handler(null, args);
Expand All @@ -112,6 +114,10 @@ public void Raise(TEventArgs args)
{
this.LogError(handler, ex);
}
finally
{
Context.HeuristicModsRunningCode.TryPop(out _);
}
}
}

Expand All @@ -126,6 +132,8 @@ public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke)
// raise event
foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
{
Context.HeuristicModsRunningCode.Push(handler.SourceMod);

try
{
invoke(handler.SourceMod, args => handler.Handler(null, args));
Expand All @@ -134,6 +142,10 @@ public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke)
{
this.LogError(handler, ex);
}
finally
{
Context.HeuristicModsRunningCode.TryPop(out _);
}
}
}

Expand Down
Loading

0 comments on commit 5731b01

Please sign in to comment.