add experimental image load rewrite
This commit is contained in:
parent
cb6fcb0450
commit
a546fd113f
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
# Release notes
|
# Release notes
|
||||||
## Upcoming release
|
## Upcoming release
|
||||||
|
* For players:
|
||||||
|
* Added experimental image load rewrite (disabled by default).
|
||||||
|
_If you have many content mods installed, enabling `UseExperimentalImageLoading` in `smapi-internal/config.json` may reduce load times or stutters when they load many image files at once._
|
||||||
|
|
||||||
* For mod authors:
|
* For mod authors:
|
||||||
* Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players.
|
* Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players.
|
||||||
* In `smapi-internal/config.json`, you can now enable verbose logging for specific mods (instead of all or nothing).
|
* In `smapi-internal/config.json`, you can now enable verbose logging for specific mods (instead of all or nothing).
|
||||||
|
|
|
@ -32,6 +32,9 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <summary>An asset key prefix for assets from SMAPI mod folders.</summary>
|
/// <summary>An asset key prefix for assets from SMAPI mod folders.</summary>
|
||||||
private readonly string ManagedPrefix = "SMAPI";
|
private readonly string ManagedPrefix = "SMAPI";
|
||||||
|
|
||||||
|
/// <summary>Whether to use a newer approach when loading image files from mod folder which may be faster.</summary>
|
||||||
|
private readonly bool UseExperimentalImageLoading;
|
||||||
|
|
||||||
/// <summary>Get a file lookup for the given directory.</summary>
|
/// <summary>Get a file lookup for the given directory.</summary>
|
||||||
private readonly Func<string, IFileLookup> GetFileLookup;
|
private readonly Func<string, IFileLookup> GetFileLookup;
|
||||||
|
|
||||||
|
@ -130,7 +133,8 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <param name="getFileLookup">Get a file lookup for the given directory.</param>
|
/// <param name="getFileLookup">Get a file lookup for the given directory.</param>
|
||||||
/// <param name="onAssetsInvalidated">A callback to invoke when any asset names have been invalidated from the cache.</param>
|
/// <param name="onAssetsInvalidated">A callback to invoke when any asset names have been invalidated from the cache.</param>
|
||||||
/// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param>
|
/// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param>
|
||||||
public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, Func<string, IFileLookup> getFileLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, AssetOperationGroup?> requestAssetOperations)
|
/// <param name="useExperimentalImageLoading">Whether to use a newer approach when loading image files from mod folder which may be faster.</param>
|
||||||
|
public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, Func<string, IFileLookup> getFileLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, AssetOperationGroup?> requestAssetOperations, bool useExperimentalImageLoading)
|
||||||
{
|
{
|
||||||
this.GetFileLookup = getFileLookup;
|
this.GetFileLookup = getFileLookup;
|
||||||
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
|
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
|
||||||
|
@ -141,6 +145,7 @@ namespace StardewModdingAPI.Framework
|
||||||
this.OnAssetsInvalidated = onAssetsInvalidated;
|
this.OnAssetsInvalidated = onAssetsInvalidated;
|
||||||
this.RequestAssetOperations = requestAssetOperations;
|
this.RequestAssetOperations = requestAssetOperations;
|
||||||
this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory);
|
this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory);
|
||||||
|
this.UseExperimentalImageLoading = useExperimentalImageLoading;
|
||||||
this.ContentManagers.Add(
|
this.ContentManagers.Add(
|
||||||
this.MainContentManager = new GameContentManager(
|
this.MainContentManager = new GameContentManager(
|
||||||
name: "Game1.content",
|
name: "Game1.content",
|
||||||
|
@ -219,7 +224,8 @@ namespace StardewModdingAPI.Framework
|
||||||
reflection: this.Reflection,
|
reflection: this.Reflection,
|
||||||
jsonHelper: this.JsonHelper,
|
jsonHelper: this.JsonHelper,
|
||||||
onDisposing: this.OnDisposing,
|
onDisposing: this.OnDisposing,
|
||||||
fileLookup: this.GetFileLookup(rootDirectory)
|
fileLookup: this.GetFileLookup(rootDirectory),
|
||||||
|
useExperimentalImageLoading: this.UseExperimentalImageLoading
|
||||||
);
|
);
|
||||||
this.ContentManagers.Add(manager);
|
this.ContentManagers.Add(manager);
|
||||||
return manager;
|
return manager;
|
||||||
|
|
|
@ -7,6 +7,7 @@ using BmFont;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Content;
|
using Microsoft.Xna.Framework.Content;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using SkiaSharp;
|
||||||
using StardewModdingAPI.Framework.Exceptions;
|
using StardewModdingAPI.Framework.Exceptions;
|
||||||
using StardewModdingAPI.Framework.Reflection;
|
using StardewModdingAPI.Framework.Reflection;
|
||||||
using StardewModdingAPI.Toolkit.Serialization;
|
using StardewModdingAPI.Toolkit.Serialization;
|
||||||
|
@ -25,6 +26,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
/*********
|
/*********
|
||||||
** Fields
|
** Fields
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>Whether to use a newer approach when loading image files from mod folder which may be faster.</summary>
|
||||||
|
private readonly bool UseExperimentalImageLoading;
|
||||||
|
|
||||||
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
|
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
|
||||||
private readonly JsonHelper JsonHelper;
|
private readonly JsonHelper JsonHelper;
|
||||||
|
|
||||||
|
@ -57,13 +61,15 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
|
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
|
||||||
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
|
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
|
||||||
/// <param name="fileLookup">A lookup for files within the <paramref name="rootDirectory"/>.</param>
|
/// <param name="fileLookup">A lookup for files within the <paramref name="rootDirectory"/>.</param>
|
||||||
public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, IFileLookup fileLookup)
|
/// <param name="useExperimentalImageLoading">Whether to use a newer approach when loading image files from mod folder which may be faster.</param>
|
||||||
|
public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, IFileLookup fileLookup, bool useExperimentalImageLoading)
|
||||||
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true)
|
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true)
|
||||||
{
|
{
|
||||||
this.GameContentManager = gameContentManager;
|
this.GameContentManager = gameContentManager;
|
||||||
this.FileLookup = fileLookup;
|
this.FileLookup = fileLookup;
|
||||||
this.JsonHelper = jsonHelper;
|
this.JsonHelper = jsonHelper;
|
||||||
this.ModName = modName;
|
this.ModName = modName;
|
||||||
|
this.UseExperimentalImageLoading = useExperimentalImageLoading;
|
||||||
|
|
||||||
this.TryLocalizeKeys = false;
|
this.TryLocalizeKeys = false;
|
||||||
}
|
}
|
||||||
|
@ -187,11 +193,36 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'.");
|
throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'.");
|
||||||
|
|
||||||
// load
|
// load
|
||||||
|
if (this.UseExperimentalImageLoading)
|
||||||
|
{
|
||||||
|
// load raw data
|
||||||
|
using FileStream stream = File.OpenRead(file.FullName);
|
||||||
|
using SKBitmap bitmap = SKBitmap.Decode(stream);
|
||||||
|
SKPMColor[] rawPixels = SKPMColor.PreMultiply(bitmap.Pixels);
|
||||||
|
|
||||||
|
// convert to XNA pixel format
|
||||||
|
Color[] pixels = new Color[rawPixels.Length];
|
||||||
|
for (int i = pixels.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
SKPMColor pixel = rawPixels[i];
|
||||||
|
pixels[i] = pixel.Alpha == 0
|
||||||
|
? Color.Transparent
|
||||||
|
: new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create texture
|
||||||
|
Texture2D texture = new(Game1.graphics.GraphicsDevice, bitmap.Width, bitmap.Height);
|
||||||
|
texture.SetData(pixels);
|
||||||
|
return (T)(object)texture;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
using FileStream stream = File.OpenRead(file.FullName);
|
using FileStream stream = File.OpenRead(file.FullName);
|
||||||
Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
|
Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
|
||||||
texture = this.PremultiplyTransparency(texture);
|
texture = this.PremultiplyTransparency(texture);
|
||||||
return (T)(object)texture;
|
return (T)(object)texture;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Load an unpacked image file (<c>.tbin</c> or <c>.tmx</c>).</summary>
|
/// <summary>Load an unpacked image file (<c>.tbin</c> or <c>.tmx</c>).</summary>
|
||||||
/// <typeparam name="T">The type of asset to load.</typeparam>
|
/// <typeparam name="T">The type of asset to load.</typeparam>
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
[nameof(LogNetworkTraffic)] = false,
|
[nameof(LogNetworkTraffic)] = false,
|
||||||
[nameof(RewriteMods)] = true,
|
[nameof(RewriteMods)] = true,
|
||||||
[nameof(UsePintail)] = true,
|
[nameof(UsePintail)] = true,
|
||||||
|
[nameof(UseExperimentalImageLoading)] = false,
|
||||||
[nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux
|
[nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,6 +67,9 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
/// <summary>Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</summary>
|
/// <summary>Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</summary>
|
||||||
public bool UsePintail { get; }
|
public bool UsePintail { get; }
|
||||||
|
|
||||||
|
/// <summary>Whether to use a newer approach when loading image files from mod folder which may be faster.</summary>
|
||||||
|
public bool UseExperimentalImageLoading { get; }
|
||||||
|
|
||||||
/// <summary>Whether to make SMAPI file APIs case-insensitive, even on Linux.</summary>
|
/// <summary>Whether to make SMAPI file APIs case-insensitive, even on Linux.</summary>
|
||||||
public bool UseCaseInsensitivePaths { get; }
|
public bool UseCaseInsensitivePaths { get; }
|
||||||
|
|
||||||
|
@ -92,11 +96,12 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
|
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
|
||||||
/// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param>
|
/// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param>
|
||||||
/// <param name="usePintail">Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</param>
|
/// <param name="usePintail">Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</param>
|
||||||
|
/// <param name="useExperimentalImageLoading">Whether to use a newer approach when loading image files from mod folder which may be faster.</param>
|
||||||
/// <param name="useCaseInsensitivePaths">>Whether to make SMAPI file APIs case-insensitive, even on Linux.</param>
|
/// <param name="useCaseInsensitivePaths">>Whether to make SMAPI file APIs case-insensitive, even on Linux.</param>
|
||||||
/// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
|
/// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
|
||||||
/// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
|
/// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
|
||||||
/// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
|
/// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
|
||||||
public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? usePintail, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
|
public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? usePintail, bool? useExperimentalImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
|
||||||
{
|
{
|
||||||
this.DeveloperMode = developerMode;
|
this.DeveloperMode = developerMode;
|
||||||
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
|
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
|
||||||
|
@ -107,6 +112,7 @@ namespace StardewModdingAPI.Framework.Models
|
||||||
this.VerboseLogging = new HashSet<string>(verboseLogging ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
this.VerboseLogging = new HashSet<string>(verboseLogging ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||||
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)];
|
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)];
|
||||||
this.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(this.UsePintail)];
|
this.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(this.UsePintail)];
|
||||||
|
this.UseExperimentalImageLoading = useExperimentalImageLoading ?? (bool)SConfig.DefaultValues[nameof(this.UseExperimentalImageLoading)];
|
||||||
this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)];
|
this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)];
|
||||||
this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)];
|
this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)];
|
||||||
this.ConsoleColors = consoleColors;
|
this.ConsoleColors = consoleColors;
|
||||||
|
|
|
@ -1301,7 +1301,8 @@ namespace StardewModdingAPI.Framework
|
||||||
onAssetLoaded: this.OnAssetLoaded,
|
onAssetLoaded: this.OnAssetLoaded,
|
||||||
onAssetsInvalidated: this.OnAssetsInvalidated,
|
onAssetsInvalidated: this.OnAssetsInvalidated,
|
||||||
getFileLookup: this.GetFileLookup,
|
getFileLookup: this.GetFileLookup,
|
||||||
requestAssetOperations: this.RequestAssetOperations
|
requestAssetOperations: this.RequestAssetOperations,
|
||||||
|
useExperimentalImageLoading: this.Settings.UseExperimentalImageLoading
|
||||||
);
|
);
|
||||||
if (this.ContentCore.Language != this.Translator.LocaleEnum)
|
if (this.ContentCore.Language != this.Translator.LocaleEnum)
|
||||||
this.Translator.SetLocale(this.ContentCore.GetLocale(), this.ContentCore.Language);
|
this.Translator.SetLocale(this.ContentCore.GetLocale(), this.ContentCore.Language);
|
||||||
|
|
|
@ -60,6 +60,11 @@ copy all the settings, or you may cause bugs due to overridden changes in future
|
||||||
*/
|
*/
|
||||||
"UsePintail": true,
|
"UsePintail": true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use a newer approach when loading image files from mod folder which may be faster.
|
||||||
|
*/
|
||||||
|
"UseExperimentalImageLoading": false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
|
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
|
||||||
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as
|
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
<Reference Include="GalaxyCSharp" HintPath="$(GamePath)\GalaxyCSharp.dll" Private="False" />
|
<Reference Include="GalaxyCSharp" HintPath="$(GamePath)\GalaxyCSharp.dll" Private="False" />
|
||||||
<Reference Include="Lidgren.Network" HintPath="$(GamePath)\Lidgren.Network.dll" Private="False" />
|
<Reference Include="Lidgren.Network" HintPath="$(GamePath)\Lidgren.Network.dll" Private="False" />
|
||||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
||||||
|
<Reference Include="SkiaSharp" HintPath="$(GamePath)\SkiaSharp.dll" Private="False" />
|
||||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
|
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue