From 7c90385d8df7bbf9469fc468480b26ebb134abd8 Mon Sep 17 00:00:00 2001
From: atravita-mods <94934860+atravita-mods@users.noreply.github.com>
Date: Mon, 15 Aug 2022 19:13:24 -0400
Subject: Pre-calculate the strings for log levels.
---
src/SMAPI/Framework/Monitor.cs | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
(limited to 'src/SMAPI/Framework/Monitor.cs')
diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs
index 6b53daff..8ba175e6 100644
--- a/src/SMAPI/Framework/Monitor.cs
+++ b/src/SMAPI/Framework/Monitor.cs
@@ -25,7 +25,9 @@ namespace StardewModdingAPI.Framework
private readonly LogFileManager LogFile;
/// The maximum length of the values.
- private static readonly int MaxLevelLength = (from level in Enum.GetValues(typeof(LogLevel)).Cast() select level.ToString().Length).Max();
+ private static readonly int MaxLevelLength = (from level in Enum.GetValues() select level.ToString().Length).Max();
+
+ private static readonly Dictionary LogStrings = Enum.GetValues().ToDictionary(k => k, v => v.ToString().ToUpper().PadRight(MaxLevelLength));
/// A cache of messages that should only be logged once.
private readonly HashSet LogOnceCache = new();
@@ -147,7 +149,7 @@ namespace StardewModdingAPI.Framework
/// The log level.
private string GenerateMessagePrefix(string source, ConsoleLogLevel level)
{
- string levelStr = level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength);
+ string levelStr = LogStrings[level];
int? playerIndex = this.GetScreenIdForLog();
return $"[{DateTime.Now:HH:mm:ss} {levelStr}{(playerIndex != null ? $" screen_{playerIndex}" : "")} {source}]";
--
cgit
From 4a1055e573e9d8b0aa654238889596be07c29193 Mon Sep 17 00:00:00 2001
From: atravita-mods <94934860+atravita-mods@users.noreply.github.com>
Date: Tue, 16 Aug 2022 15:30:21 -0400
Subject: arraypool in the modcontentmanager, a bit of fussing
---
src/SMAPI/Framework/Content/AssetDataForImage.cs | 15 ++++----
.../Framework/ContentManagers/ModContentManager.cs | 40 ++++++++++++++--------
src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 2 +-
src/SMAPI/Framework/Monitor.cs | 7 ++--
4 files changed, 38 insertions(+), 26 deletions(-)
(limited to 'src/SMAPI/Framework/Monitor.cs')
diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs
index 98d6725a..46c2a22e 100644
--- a/src/SMAPI/Framework/Content/AssetDataForImage.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs
@@ -33,12 +33,12 @@ namespace StardewModdingAPI.Framework.Content
///
public void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
{
- this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
-
- // validate source data
+ // nullcheck
if (source == null)
throw new ArgumentNullException(nameof(source), "Can't patch from null source data.");
+ this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
+
// get the pixels for the source area
Color[] sourceData;
{
@@ -59,7 +59,6 @@ namespace StardewModdingAPI.Framework.Content
for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++)
{
- // avoiding an variable that increments allows the processor to re-arrange here.
int sourceIndex = (y * source.Width) + areaX;
int targetIndex = (y - areaY) * areaWidth;
Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth);
@@ -77,13 +76,13 @@ namespace StardewModdingAPI.Framework.Content
///
public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
{
- // validate
+ // nullcheck
if (source == null)
throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
- // validate source texture
+ // validate source bounds
if (!source.Bounds.Contains(sourceArea.Value))
throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
@@ -161,8 +160,8 @@ namespace StardewModdingAPI.Framework.Content
// merge pixels
for (int i = 0; i < pixelCount; i++)
{
- Color above = sourceData[i];
- Color below = mergedData[i];
+ ref Color above = ref sourceData[i];
+ ref Color below = ref mergedData[i];
// shortcut transparency
if (above.A < MinOpacity)
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index cc6f8372..dd30c225 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -1,9 +1,11 @@
using System;
+using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Runtime.CompilerServices;
using BmFont;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
@@ -111,7 +113,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath))
{
if (contentManagerID != this.Name)
- throw this.GetLoadError(assetName, ContentLoadErrorType.AccessDenied, "can't load a different mod's managed asset key through this mod content manager.");
+ this.ThrowLoadError(assetName, ContentLoadErrorType.AccessDenied, "can't load a different mod's managed asset key through this mod content manager.");
assetName = relativePath;
}
}
@@ -123,7 +125,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// get file
FileInfo file = this.GetModFile(assetName.Name);
if (!file.Exists)
- throw this.GetLoadError(assetName, ContentLoadErrorType.AssetDoesNotExist, "the specified path doesn't exist.");
+ this.ThrowLoadError(assetName, ContentLoadErrorType.AssetDoesNotExist, "the specified path doesn't exist.");
// load content
asset = file.Extension.ToLower() switch
@@ -141,7 +143,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
if (ex is SContentLoadException)
throw;
- throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex);
+ this.ThrowLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex);
+ return default;
}
// track & return asset
@@ -189,7 +192,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
private T LoadDataFile(IAssetName assetName, FileInfo file)
{
if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T? asset))
- throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method
+ this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method
return asset;
}
@@ -301,7 +304,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
private T LoadXnbFile(IAssetName assetName)
{
if (typeof(IRawTextureData).IsAssignableFrom(typeof(T)))
- throw this.GetLoadError(assetName, ContentLoadErrorType.Other, $"can't read XNB file as type {typeof(IRawTextureData)}; that type can only be read from a PNG file.");
+ this.ThrowLoadError(assetName, ContentLoadErrorType.Other, $"can't read XNB file as type {typeof(IRawTextureData)}; that type can only be read from a PNG file.");
// the underlying content manager adds a .xnb extension implicitly, so
// we need to strip it here to avoid trying to load a '.xnb.xnb' file.
@@ -326,7 +329,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// The file to load.
private T HandleUnknownFileType(IAssetName assetName, FileInfo file)
{
- throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'.");
+ this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'.");
+ return default;
}
/// Assert that the asset type is compatible with one of the allowed types.
@@ -338,18 +342,20 @@ namespace StardewModdingAPI.Framework.ContentManagers
private void AssertValidType(IAssetName assetName, FileInfo file, params Type[] validTypes)
{
if (!validTypes.Any(validType => validType.IsAssignableFrom(typeof(TAsset))))
- throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'.");
+ this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'.");
}
- /// Get an error which indicates that an asset couldn't be loaded.
+ /// Throws an error which indicates that an asset couldn't be loaded.
/// Why loading an asset through the content pipeline failed.
/// The asset name that failed to load.
/// The reason the file couldn't be loaded.
/// The underlying exception, if applicable.
+ [DoesNotReturn]
[DebuggerStepThrough, DebuggerHidden]
- private SContentLoadException GetLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null)
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void ThrowLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null)
{
- return new(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception);
+ throw new SContentLoadException(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception);
}
/// Get a file from the mod folder.
@@ -384,12 +390,14 @@ namespace StardewModdingAPI.Framework.ContentManagers
private Texture2D PremultiplyTransparency(Texture2D texture)
{
// premultiply pixels
- Color[] data = GC.AllocateUninitializedArray(texture.Width * texture.Height);
- texture.GetData(data);
+ int count = texture.Width * texture.Height;
+ Color[] data = ArrayPool.Shared.Rent(count);
+ texture.GetData(data, 0, count);
+
bool changed = false;
- for (int i = 0; i < data.Length; i++)
+ for (int i = 0; i < count; i++)
{
- Color pixel = data[i];
+ ref Color pixel = ref data[i];
if (pixel.A is (byte.MinValue or byte.MaxValue))
continue; // no need to change fully transparent/opaque pixels
@@ -398,8 +406,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
if (changed)
- texture.SetData(data);
+ texture.SetData(data, 0, count);
+ // return
+ ArrayPool.Shared.Return(data);
return texture;
}
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index 01037870..ae08d972 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -221,7 +221,7 @@ namespace StardewModdingAPI.Framework.ModLoading
///
public static Assembly? ResolveAssembly(string name)
{
- string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture)
+ string shortName = name.Split(',', 2).First(); // get simple name (without version and culture)
return AppDomain.CurrentDomain
.GetAssemblies()
.FirstOrDefault(p => p.GetName().Name == shortName);
diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs
index 8ba175e6..d33bf259 100644
--- a/src/SMAPI/Framework/Monitor.cs
+++ b/src/SMAPI/Framework/Monitor.cs
@@ -27,10 +27,13 @@ namespace StardewModdingAPI.Framework
/// The maximum length of the values.
private static readonly int MaxLevelLength = (from level in Enum.GetValues() select level.ToString().Length).Max();
+ /// A mapping of console log levels to their string form.
private static readonly Dictionary LogStrings = Enum.GetValues().ToDictionary(k => k, v => v.ToString().ToUpper().PadRight(MaxLevelLength));
+ private readonly record struct LogOnceCacheEntry(string message, LogLevel level);
+
/// A cache of messages that should only be logged once.
- private readonly HashSet LogOnceCache = new();
+ private readonly HashSet LogOnceCache = new();
/// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.
private readonly Func GetScreenIdForLog;
@@ -86,7 +89,7 @@ namespace StardewModdingAPI.Framework
///
public void LogOnce(string message, LogLevel level = LogLevel.Trace)
{
- if (this.LogOnceCache.Add($"{message}|{level}"))
+ if (this.LogOnceCache.Add(new LogOnceCacheEntry(message, level)))
this.LogImpl(this.Source, message, (ConsoleLogLevel)level);
}
--
cgit
From 2e0bc5ddfe90102fe5adbc90b2d53c5cbb8405fe Mon Sep 17 00:00:00 2001
From: Jesse Plamondon-Willard
Date: Sat, 8 Oct 2022 17:45:50 -0400
Subject: tweak new code
---
src/SMAPI/Framework/Content/AssetDataForImage.cs | 183 ++++++++++-----------
.../Framework/ContentManagers/ModContentManager.cs | 13 +-
src/SMAPI/Framework/Logging/LogOnceCacheKey.cs | 10 ++
src/SMAPI/Framework/Monitor.cs | 14 +-
4 files changed, 104 insertions(+), 116 deletions(-)
create mode 100644 src/SMAPI/Framework/Logging/LogOnceCacheKey.cs
(limited to 'src/SMAPI/Framework/Monitor.cs')
diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs
index 3abcd328..0380dd9e 100644
--- a/src/SMAPI/Framework/Content/AssetDataForImage.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs
@@ -33,71 +33,59 @@ namespace StardewModdingAPI.Framework.Content
///
public void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
{
- // nullcheck
if (source == null)
throw new ArgumentNullException(nameof(source), "Can't patch from null source data.");
+ // get normalized bounds
this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
-
- // check to see if the Data is sufficiently long.
- // while SMAPI's impl is going to be, it's not necessarily the case for mod impl.
if (source.Data.Length < (sourceArea.Value.Bottom - 1) * source.Width + sourceArea.Value.Right)
- throw new ArgumentException("Source data insufficiently long for this operation.");
-
- // get the pixels for the source area
- Color[] sourceData;
+ throw new ArgumentException("Can't apply image patch because the source image is smaller than the source area.", nameof(source));
+ int areaX = sourceArea.Value.X;
+ int areaY = sourceArea.Value.Y;
+ int areaWidth = sourceArea.Value.Width;
+ int areaHeight = sourceArea.Value.Height;
+
+ // shortcut: if the area width matches the source image, we can apply the image as-is without needing
+ // to copy the pixels into a smaller subset. It's fine if the source is taller than the area, since we'll
+ // just ignore the extra data at the end of the pixel array.
+ if (areaWidth == source.Width)
{
- int areaX = sourceArea.Value.X;
- int areaY = sourceArea.Value.Y;
- int areaWidth = sourceArea.Value.Width;
- int areaHeight = sourceArea.Value.Height;
+ this.PatchImageImpl(source.Data, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode, areaY);
+ return;
+ }
- if (areaWidth == source.Width)
+ // else copy the pixels within the smaller area & apply that
+ int pixelCount = areaWidth * areaHeight;
+ Color[] sourceData = ArrayPool.Shared.Rent(pixelCount);
+ try
+ {
+ for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++)
{
- // It's actually fine if the source is taller than the sourceArea
- // the "extra" bits on the end of the array can just be ignored.
- sourceData = source.Data;
- this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode, areaY);
+ int sourceIndex = (y * source.Width) + areaX;
+ int targetIndex = (y - areaY) * areaWidth;
+ Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth);
}
- else
- {
- int pixelCount = areaWidth * areaHeight;
- sourceData = ArrayPool.Shared.Rent(pixelCount);
- try
- {
- // slower copying, line by line
- for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++)
- {
- int sourceIndex = (y * source.Width) + areaX;
- int targetIndex = (y - areaY) * areaWidth;
- Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth);
- }
- // apply
- this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
- }
- finally
- {
- ArrayPool.Shared.Return(sourceData);
- }
- }
+ this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(sourceData);
}
}
///
public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
{
- // nullcheck
if (source == null)
throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
+ // get normalized bounds
this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
-
- // validate source bounds
if (!source.Bounds.Contains(sourceArea.Value))
throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
- // get source data
+ // get source data & apply
int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height;
Color[] sourceData = ArrayPool.Shared.Rent(pixelCount);
try
@@ -164,94 +152,91 @@ namespace StardewModdingAPI.Framework.Content
if (sourceArea.Size != targetArea.Size)
throw new InvalidOperationException("The source and target areas must be the same size.");
+ // shortcut: replace the entire area
if (patchMode == PatchMode.Replace)
- target.SetData(0, targetArea, sourceData, startRow * sourceArea.Width, pixelCount);
- else
{
- // merge data
-
- // Content packs have a habit of using large amounts of blank space.
- // Adjusting bounds to ignore transparent pixels at the start and end.
+ target.SetData(0, targetArea, sourceData, startRow * sourceArea.Width, pixelCount);
+ return;
+ }
- int startIndex = -1;
+ // skip transparent pixels at the start & end (e.g. large spritesheet with a few sprites replaced)
+ int startIndex = -1;
+ int endIndex = -1;
+ {
for (int i = startRow * sourceArea.Width; i < pixelCount; i++)
{
- if (sourceData[i].A >= MinOpacity)
+ if (sourceData[i].A >= AssetDataForImage.MinOpacity)
{
startIndex = i;
break;
}
}
-
if (startIndex == -1)
- return; // apparently a completely blank texture?
+ return; // blank texture
- int endIndex = -1;
for (int i = startRow * sourceArea.Width + pixelCount - 1; i >= startIndex; i--)
{
- if (sourceData[i].A >= MinOpacity)
+ if (sourceData[i].A >= AssetDataForImage.MinOpacity)
{
endIndex = i;
break;
}
}
-
if (endIndex == -1)
- return; // should never happen
+ return; // ???
+ }
- // Calculate new Y bounds
- int topoffset = startIndex / sourceArea.Width;
- int bottomoffset = endIndex / sourceArea.Width;
+ // update target rectangle
+ int sourceOffset;
+ {
+ int topOffset = startIndex / sourceArea.Width;
+ int bottomOffset = endIndex / sourceArea.Width;
- // Update target rectangle
- targetArea = new(targetArea.X, targetArea.Y + topoffset, targetArea.Width, bottomoffset - topoffset + 1);
+ targetArea = new(targetArea.X, targetArea.Y + topOffset, targetArea.Width, bottomOffset - topOffset + 1);
pixelCount = targetArea.Width * targetArea.Height;
+ sourceOffset = topOffset * sourceArea.Width;
+ }
- int sourceoffset = topoffset * sourceArea.Width;
+ // apply
+ Color[] mergedData = ArrayPool.Shared.Rent(pixelCount);
+ try
+ {
+ target.GetData(0, targetArea, mergedData, 0, pixelCount);
- // get target data
- Color[] mergedData = ArrayPool.Shared.Rent(pixelCount);
- try
+ for (int i = startIndex; i <= endIndex; i++)
{
- target.GetData(0, targetArea, mergedData, 0, pixelCount);
+ int targetIndex = i - sourceOffset;
- // merge pixels
- for (int i = startIndex; i <= endIndex; i++)
- {
- int targetIndex = i - sourceoffset;
+ Color above = sourceData[i];
+ Color below = mergedData[targetIndex];
- // ref locals here? Not sure.
- Color above = sourceData[i];
- Color below = mergedData[targetIndex];
+ // shortcut transparency
+ if (above.A < AssetDataForImage.MinOpacity)
+ continue;
+ if (below.A < AssetDataForImage.MinOpacity || above.A == byte.MaxValue)
+ mergedData[targetIndex] = above;
- // shortcut transparency
- if (above.A < MinOpacity)
- continue;
- if (below.A < MinOpacity || above.A == byte.MaxValue)
- mergedData[targetIndex] = above;
-
- // merge pixels
- else
- {
- // This performs a conventional alpha blend for the pixels, which are already
- // premultiplied by the content pipeline. The formula is derived from
- // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
- float alphaBelow = 1 - (above.A / 255f);
- mergedData[targetIndex] = new Color(
- r: (int)(above.R + (below.R * alphaBelow)),
- g: (int)(above.G + (below.G * alphaBelow)),
- b: (int)(above.B + (below.B * alphaBelow)),
- alpha: Math.Max(above.A, below.A)
- );
- }
+ // merge pixels
+ else
+ {
+ // This performs a conventional alpha blend for the pixels, which are already
+ // premultiplied by the content pipeline. The formula is derived from
+ // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
+ float alphaBelow = 1 - (above.A / 255f);
+ mergedData[targetIndex] = new Color(
+ r: (int)(above.R + (below.R * alphaBelow)),
+ g: (int)(above.G + (below.G * alphaBelow)),
+ b: (int)(above.B + (below.B * alphaBelow)),
+ alpha: Math.Max(above.A, below.A)
+ );
}
-
- target.SetData(0, targetArea, mergedData, 0, pixelCount);
- }
- finally
- {
- ArrayPool.Shared.Return(mergedData);
}
+
+ target.SetData(0, targetArea, mergedData, 0, pixelCount);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(mergedData);
}
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 6b8a5874..72dcf6e1 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -241,7 +241,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
using FileStream stream = File.OpenRead(file.FullName);
Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
- texture = this.PremultiplyTransparency(texture);
+ this.PremultiplyTransparency(texture);
return (T)(object)texture;
}
}
@@ -345,17 +345,15 @@ namespace StardewModdingAPI.Framework.ContentManagers
this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'.");
}
- /// Throws an error which indicates that an asset couldn't be loaded.
+ /// Throw an error which indicates that an asset couldn't be loaded.
/// Why loading an asset through the content pipeline failed.
/// The asset name that failed to load.
/// The reason the file couldn't be loaded.
/// The underlying exception, if applicable.
+ ///
[DoesNotReturn]
[DebuggerStepThrough, DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
-#if NET6_0_OR_GREATER
- [StackTraceHidden]
-#endif
private void ThrowLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null)
{
throw new SContentLoadException(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception);
@@ -390,9 +388,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// The texture to premultiply.
/// Returns a premultiplied texture.
/// Based on code by David Gouveia.
- private Texture2D PremultiplyTransparency(Texture2D texture)
+ private void PremultiplyTransparency(Texture2D texture)
{
- // premultiply pixels
int count = texture.Width * texture.Height;
Color[] data = ArrayPool.Shared.Rent(count);
try
@@ -412,8 +409,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
if (changed)
texture.SetData(data, 0, count);
-
- return texture;
}
finally
{
diff --git a/src/SMAPI/Framework/Logging/LogOnceCacheKey.cs b/src/SMAPI/Framework/Logging/LogOnceCacheKey.cs
new file mode 100644
index 00000000..4d31ffeb
--- /dev/null
+++ b/src/SMAPI/Framework/Logging/LogOnceCacheKey.cs
@@ -0,0 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace StardewModdingAPI.Framework.Logging
+{
+ /// The cache key for the .
+ /// The log message.
+ /// The log level.
+ [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local", Justification = "This is only used as a lookup key.")]
+ internal readonly record struct LogOnceCacheKey(string Message, LogLevel Level);
+}
diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs
index d33bf259..4ed2c9bb 100644
--- a/src/SMAPI/Framework/Monitor.cs
+++ b/src/SMAPI/Framework/Monitor.cs
@@ -25,15 +25,13 @@ namespace StardewModdingAPI.Framework
private readonly LogFileManager LogFile;
/// The maximum length of the values.
- private static readonly int MaxLevelLength = (from level in Enum.GetValues() select level.ToString().Length).Max();
+ private static readonly int MaxLevelLength = Enum.GetValues().Max(level => level.ToString().Length);
- /// A mapping of console log levels to their string form.
- private static readonly Dictionary LogStrings = Enum.GetValues().ToDictionary(k => k, v => v.ToString().ToUpper().PadRight(MaxLevelLength));
-
- private readonly record struct LogOnceCacheEntry(string message, LogLevel level);
+ /// The cached representation for each level when added to a log header.
+ private static readonly Dictionary LogStrings = Enum.GetValues().ToDictionary(level => level, level => level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength));
/// A cache of messages that should only be logged once.
- private readonly HashSet LogOnceCache = new();
+ private readonly HashSet LogOnceCache = new();
/// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.
private readonly Func GetScreenIdForLog;
@@ -89,7 +87,7 @@ namespace StardewModdingAPI.Framework
///
public void LogOnce(string message, LogLevel level = LogLevel.Trace)
{
- if (this.LogOnceCache.Add(new LogOnceCacheEntry(message, level)))
+ if (this.LogOnceCache.Add(new LogOnceCacheKey(message, level)))
this.LogImpl(this.Source, message, (ConsoleLogLevel)level);
}
@@ -152,7 +150,7 @@ namespace StardewModdingAPI.Framework
/// The log level.
private string GenerateMessagePrefix(string source, ConsoleLogLevel level)
{
- string levelStr = LogStrings[level];
+ string levelStr = Monitor.LogStrings[level];
int? playerIndex = this.GetScreenIdForLog();
return $"[{DateTime.Now:HH:mm:ss} {levelStr}{(playerIndex != null ? $" screen_{playerIndex}" : "")} {source}]";
--
cgit