arraypool in the modcontentmanager, a bit of fussing
This commit is contained in:
parent
78643710ce
commit
4a1055e573
|
@ -33,12 +33,12 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
|
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);
|
// nullcheck
|
||||||
|
|
||||||
// validate source data
|
|
||||||
if (source == null)
|
if (source == null)
|
||||||
throw new ArgumentNullException(nameof(source), "Can't patch from null source data.");
|
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
|
// get the pixels for the source area
|
||||||
Color[] sourceData;
|
Color[] sourceData;
|
||||||
{
|
{
|
||||||
|
@ -59,7 +59,6 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
|
|
||||||
for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++)
|
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 sourceIndex = (y * source.Width) + areaX;
|
||||||
int targetIndex = (y - areaY) * areaWidth;
|
int targetIndex = (y - areaY) * areaWidth;
|
||||||
Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth);
|
Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth);
|
||||||
|
@ -77,13 +76,13 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
|
public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
|
||||||
{
|
{
|
||||||
// validate
|
// nullcheck
|
||||||
if (source == null)
|
if (source == null)
|
||||||
throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
|
throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
|
||||||
|
|
||||||
this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
|
this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
|
||||||
|
|
||||||
// validate source texture
|
// validate source bounds
|
||||||
if (!source.Bounds.Contains(sourceArea.Value))
|
if (!source.Bounds.Contains(sourceArea.Value))
|
||||||
throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
|
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
|
// merge pixels
|
||||||
for (int i = 0; i < pixelCount; i++)
|
for (int i = 0; i < pixelCount; i++)
|
||||||
{
|
{
|
||||||
Color above = sourceData[i];
|
ref Color above = ref sourceData[i];
|
||||||
Color below = mergedData[i];
|
ref Color below = ref mergedData[i];
|
||||||
|
|
||||||
// shortcut transparency
|
// shortcut transparency
|
||||||
if (above.A < MinOpacity)
|
if (above.A < MinOpacity)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using BmFont;
|
using BmFont;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Content;
|
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 (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath))
|
||||||
{
|
{
|
||||||
if (contentManagerID != this.Name)
|
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;
|
assetName = relativePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +125,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
// get file
|
// get file
|
||||||
FileInfo file = this.GetModFile<T>(assetName.Name);
|
FileInfo file = this.GetModFile<T>(assetName.Name);
|
||||||
if (!file.Exists)
|
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
|
// load content
|
||||||
asset = file.Extension.ToLower() switch
|
asset = file.Extension.ToLower() switch
|
||||||
|
@ -141,7 +143,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
if (ex is SContentLoadException)
|
if (ex is SContentLoadException)
|
||||||
throw;
|
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
|
// track & return asset
|
||||||
|
@ -189,7 +192,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
private T LoadDataFile<T>(IAssetName assetName, FileInfo file)
|
private T LoadDataFile<T>(IAssetName assetName, FileInfo file)
|
||||||
{
|
{
|
||||||
if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T? asset))
|
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;
|
return asset;
|
||||||
}
|
}
|
||||||
|
@ -301,7 +304,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
private T LoadXnbFile<T>(IAssetName assetName)
|
private T LoadXnbFile<T>(IAssetName assetName)
|
||||||
{
|
{
|
||||||
if (typeof(IRawTextureData).IsAssignableFrom(typeof(T)))
|
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
|
// 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.
|
// we need to strip it here to avoid trying to load a '.xnb.xnb' file.
|
||||||
|
@ -326,7 +329,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
/// <param name="file">The file to load.</param>
|
/// <param name="file">The file to load.</param>
|
||||||
private T HandleUnknownFileType<T>(IAssetName assetName, FileInfo file)
|
private T HandleUnknownFileType<T>(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that the asset type is compatible with one of the allowed types.</summary>
|
/// <summary>Assert that the asset type is compatible with one of the allowed types.</summary>
|
||||||
|
@ -338,18 +342,20 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
private void AssertValidType<TAsset>(IAssetName assetName, FileInfo file, params Type[] validTypes)
|
private void AssertValidType<TAsset>(IAssetName assetName, FileInfo file, params Type[] validTypes)
|
||||||
{
|
{
|
||||||
if (!validTypes.Any(validType => validType.IsAssignableFrom(typeof(TAsset))))
|
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))}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get an error which indicates that an asset couldn't be loaded.</summary>
|
/// <summary>Throws an error which indicates that an asset couldn't be loaded.</summary>
|
||||||
/// <param name="errorType">Why loading an asset through the content pipeline failed.</param>
|
/// <param name="errorType">Why loading an asset through the content pipeline failed.</param>
|
||||||
/// <param name="assetName">The asset name that failed to load.</param>
|
/// <param name="assetName">The asset name that failed to load.</param>
|
||||||
/// <param name="reasonPhrase">The reason the file couldn't be loaded.</param>
|
/// <param name="reasonPhrase">The reason the file couldn't be loaded.</param>
|
||||||
/// <param name="exception">The underlying exception, if applicable.</param>
|
/// <param name="exception">The underlying exception, if applicable.</param>
|
||||||
|
[DoesNotReturn]
|
||||||
[DebuggerStepThrough, DebuggerHidden]
|
[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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get a file from the mod folder.</summary>
|
/// <summary>Get a file from the mod folder.</summary>
|
||||||
|
@ -384,12 +390,14 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
private Texture2D PremultiplyTransparency(Texture2D texture)
|
private Texture2D PremultiplyTransparency(Texture2D texture)
|
||||||
{
|
{
|
||||||
// premultiply pixels
|
// premultiply pixels
|
||||||
Color[] data = GC.AllocateUninitializedArray<Color>(texture.Width * texture.Height);
|
int count = texture.Width * texture.Height;
|
||||||
texture.GetData(data);
|
Color[] data = ArrayPool<Color>.Shared.Rent(count);
|
||||||
|
texture.GetData(data, 0, count);
|
||||||
|
|
||||||
bool changed = false;
|
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))
|
if (pixel.A is (byte.MinValue or byte.MaxValue))
|
||||||
continue; // no need to change fully transparent/opaque pixels
|
continue; // no need to change fully transparent/opaque pixels
|
||||||
|
|
||||||
|
@ -398,8 +406,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
texture.SetData(data);
|
texture.SetData(data, 0, count);
|
||||||
|
|
||||||
|
// return
|
||||||
|
ArrayPool<Color>.Shared.Return(data);
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -221,7 +221,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static Assembly? ResolveAssembly(string name)
|
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
|
return AppDomain.CurrentDomain
|
||||||
.GetAssemblies()
|
.GetAssemblies()
|
||||||
.FirstOrDefault(p => p.GetName().Name == shortName);
|
.FirstOrDefault(p => p.GetName().Name == shortName);
|
||||||
|
|
|
@ -27,10 +27,13 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <summary>The maximum length of the <see cref="LogLevel"/> values.</summary>
|
/// <summary>The maximum length of the <see cref="LogLevel"/> values.</summary>
|
||||||
private static readonly int MaxLevelLength = (from level in Enum.GetValues<LogLevel>() select level.ToString().Length).Max();
|
private static readonly int MaxLevelLength = (from level in Enum.GetValues<LogLevel>() select level.ToString().Length).Max();
|
||||||
|
|
||||||
|
/// <summary>A mapping of console log levels to their string form.</summary>
|
||||||
private static readonly Dictionary<ConsoleLogLevel, string> LogStrings = Enum.GetValues<ConsoleLogLevel>().ToDictionary(k => k, v => v.ToString().ToUpper().PadRight(MaxLevelLength));
|
private static readonly Dictionary<ConsoleLogLevel, string> LogStrings = Enum.GetValues<ConsoleLogLevel>().ToDictionary(k => k, v => v.ToString().ToUpper().PadRight(MaxLevelLength));
|
||||||
|
|
||||||
|
private readonly record struct LogOnceCacheEntry(string message, LogLevel level);
|
||||||
|
|
||||||
/// <summary>A cache of messages that should only be logged once.</summary>
|
/// <summary>A cache of messages that should only be logged once.</summary>
|
||||||
private readonly HashSet<string> LogOnceCache = new();
|
private readonly HashSet<LogOnceCacheEntry> LogOnceCache = new();
|
||||||
|
|
||||||
/// <summary>Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</summary>
|
/// <summary>Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</summary>
|
||||||
private readonly Func<int?> GetScreenIdForLog;
|
private readonly Func<int?> GetScreenIdForLog;
|
||||||
|
@ -86,7 +89,7 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void LogOnce(string message, LogLevel level = LogLevel.Trace)
|
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);
|
this.LogImpl(this.Source, message, (ConsoleLogLevel)level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue