From fdb74df8a4c899b81009c7e04659be9007545788 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 6 Jun 2022 21:28:57 -0400 Subject: simplify repeated hash set creation --- src/SMAPI/Framework/Models/SConfig.cs | 7 +++---- src/SMAPI/Framework/SCore.cs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 1c7cd3ed..316f7ac3 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -76,7 +76,7 @@ namespace StardewModdingAPI.Framework.Models public ColorSchemeConfig ConsoleColors { get; } /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public string[] SuppressUpdateChecks { get; } + public HashSet SuppressUpdateChecks { get; } /******** @@ -110,7 +110,7 @@ namespace StardewModdingAPI.Framework.Models this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)]; this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)]; this.ConsoleColors = consoleColors; - this.SuppressUpdateChecks = suppressUpdateChecks ?? Array.Empty(); + this.SuppressUpdateChecks = new HashSet(suppressUpdateChecks ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); } /// Override the value of . @@ -132,8 +132,7 @@ namespace StardewModdingAPI.Framework.Models custom[name] = value; } - HashSet curSuppressUpdateChecks = new(this.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); - if (SConfig.DefaultSuppressUpdateChecks.Count != curSuppressUpdateChecks.Count || SConfig.DefaultSuppressUpdateChecks.Any(p => !curSuppressUpdateChecks.Contains(p))) + if (!this.SuppressUpdateChecks.SetEquals(SConfig.DefaultSuppressUpdateChecks)) custom[nameof(this.SuppressUpdateChecks)] = "[" + string.Join(", ", this.SuppressUpdateChecks) + "]"; return custom; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 731731d4..fa217f20 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1507,7 +1507,7 @@ namespace StardewModdingAPI.Framework { try { - HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); + HashSet suppressUpdateChecks = this.Settings.SuppressUpdateChecks; // prepare search model List searchMods = new List(); @@ -1608,7 +1608,7 @@ namespace StardewModdingAPI.Framework using (AssemblyLoader modAssemblyLoader = new(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings, this.Settings.RewriteMods)) { // init - HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); + HashSet suppressUpdateChecks = this.Settings.SuppressUpdateChecks; IInterfaceProxyFactory proxyFactory = this.Settings.UsePintail ? new InterfaceProxyFactory() : new OriginalInterfaceProxyFactory(); -- cgit From 8713914a1af3b5ac7e4a2cdce7e084055ac9cd33 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Jun 2022 23:33:09 -0400 Subject: avoid NPC pathfinding rebuild if reachable locations didn't change --- src/SMAPI/Framework/ContentCoordinator.cs | 6 +++--- src/SMAPI/Metadata/CoreAssetPropagator.cs | 26 +++++++++++++------------- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index cfeb35c8..69a39ac7 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -465,7 +465,7 @@ namespace StardewModdingAPI.Framework assets: invalidatedAssets.ToDictionary(p => p.Key, p => p.Value), ignoreWorld: Context.IsWorldFullyUnloaded, out IDictionary propagated, - out bool updatedNpcWarps + out bool updatedWarpRoutes ); // log summary @@ -481,8 +481,8 @@ namespace StardewModdingAPI.Framework ? $"Propagated {propagatedKeys.Length} core assets ({FormatKeyList(propagatedKeys)})." : "Propagated 0 core assets." ); - if (updatedNpcWarps) - report.AppendLine("Updated NPC pathfinding cache."); + if (updatedWarpRoutes) + report.AppendLine("Updated NPC warp route cache."); } this.Monitor.Log(report.ToString().TrimEnd()); } diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 8ed6b591..b783b2b9 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -85,8 +85,8 @@ namespace StardewModdingAPI.Metadata /// The asset keys and types to reload. /// Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world. /// A lookup of asset names to whether they've been propagated. - /// Whether the NPC pathfinding cache was reloaded. - public void Propagate(IDictionary assets, bool ignoreWorld, out IDictionary propagatedAssets, out bool updatedNpcWarps) + /// Whether the NPC pathfinding warp route cache was reloaded. + public void Propagate(IDictionary assets, bool ignoreWorld, out IDictionary propagatedAssets, out bool changedWarpRoutes) { // get base name lookup propagatedAssets = assets @@ -107,7 +107,7 @@ namespace StardewModdingAPI.Metadata }); // reload assets - updatedNpcWarps = false; + changedWarpRoutes = false; foreach (var bucket in buckets) { switch (bucket.Key) @@ -126,10 +126,10 @@ namespace StardewModdingAPI.Metadata foreach (var entry in bucket) { bool changed = false; - bool curChangedMapWarps = false; + bool curChangedMapRoutes = false; try { - changed = this.PropagateOther(entry.Key, entry.Value, ignoreWorld, out curChangedMapWarps); + changed = this.PropagateOther(entry.Key, entry.Value, ignoreWorld, out curChangedMapRoutes); } catch (Exception ex) { @@ -137,14 +137,14 @@ namespace StardewModdingAPI.Metadata } propagatedAssets[entry.Key] = changed; - updatedNpcWarps = updatedNpcWarps || curChangedMapWarps; + changedWarpRoutes = changedWarpRoutes || curChangedMapRoutes; } break; } } - // reload NPC pathfinding cache if any map changed - if (updatedNpcWarps) + // reload NPC pathfinding cache if any map routes changed + if (changedWarpRoutes) NPC.populateRoutesFromLocationToLocationList(); } @@ -156,14 +156,14 @@ namespace StardewModdingAPI.Metadata /// The asset name to reload. /// The asset type to reload. /// Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world. - /// Whether any map warps were changed as part of this propagation. + /// Whether the locations reachable by warps from this location changed as part of this propagation. /// Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true. [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These deliberately match the asset names.")] - private bool PropagateOther(IAssetName assetName, Type type, bool ignoreWorld, out bool changedWarps) + private bool PropagateOther(IAssetName assetName, Type type, bool ignoreWorld, out bool changedWarpRoutes) { var content = this.MainContentManager; string key = assetName.BaseName; - changedWarps = false; + changedWarpRoutes = false; /**** ** Special case: current map tilesheet @@ -197,7 +197,7 @@ namespace StardewModdingAPI.Metadata static ISet GetWarpSet(GameLocation location) { return new HashSet( - location.warps.Select(p => $"{p.X} {p.Y} {p.TargetName} {p.TargetX} {p.TargetY}") + location.warps.Select(p => p.TargetName) ); } @@ -205,7 +205,7 @@ namespace StardewModdingAPI.Metadata this.UpdateMap(info); var newWarps = GetWarpSet(location); - changedWarps = changedWarps || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p)); + changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p)); anyChanged = true; } } -- cgit From cb6fcb0450da28607e1a7307f5638cccbd6ce9f7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 6 Jun 2022 21:46:21 -0400 Subject: rework VerboseLogging option to allow enabling for specific mods --- src/SMAPI/Framework/Logging/LogManager.cs | 19 ++++++++++--------- src/SMAPI/Framework/Models/SConfig.cs | 17 ++++++++++------- src/SMAPI/Framework/SCore.cs | 8 ++++---- src/SMAPI/SMAPI.config.json | 12 ++++++++++-- 4 files changed, 34 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index ed5b6959..d811ed5c 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -31,8 +31,8 @@ namespace StardewModdingAPI.Framework.Logging /// Prefixing a low-level message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.) private const char IgnoreChar = InterceptingTextWriter.IgnoreChar; - /// Get a named monitor instance. - private readonly Func GetMonitorImpl; + /// Create a monitor instance given the ID and name. + private readonly Func GetMonitorImpl; /// Regex patterns which match console non-error messages to suppress from the console and log. private readonly Regex[] SuppressConsolePatterns = @@ -88,23 +88,23 @@ namespace StardewModdingAPI.Framework.Logging /// The log file path to write. /// The colors to use for text written to the SMAPI console. /// Whether to output log messages to the console. - /// Whether verbose logging is enabled. This enables more detailed diagnostic messages than are normally needed. + /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. /// Whether to enable full console output for developers. /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. - public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode, Func getScreenIdForLog) + public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, HashSet verboseLogging, bool isDeveloperMode, Func getScreenIdForLog) { // init log file this.LogFile = new LogFileManager(logPath); // init monitor - this.GetMonitorImpl = name => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog) + this.GetMonitorImpl = (id, name) => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, verboseLogging.Contains("*") || verboseLogging.Contains(id), getScreenIdForLog) { WriteToConsole = writeToConsole, ShowTraceInConsole = isDeveloperMode, ShowFullStampInConsole = isDeveloperMode }; - this.Monitor = this.GetMonitor("SMAPI"); - this.MonitorForGame = this.GetMonitor("game"); + this.Monitor = this.GetMonitor("SMAPI", "SMAPI"); + this.MonitorForGame = this.GetMonitor("game", "game"); // redirect direct console output this.ConsoleInterceptor = new InterceptingTextWriter( @@ -124,10 +124,11 @@ namespace StardewModdingAPI.Framework.Logging } /// Get a monitor instance derived from SMAPI's current settings. + /// The unique ID for the mod context. /// The name of the module which will log messages with this instance. - public Monitor GetMonitor(string name) + public Monitor GetMonitor(string id, string name) { - return this.GetMonitorImpl(name); + return this.GetMonitorImpl(id, name); } /// Set the title of the SMAPI console window. diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 316f7ac3..240af002 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -20,7 +20,6 @@ namespace StardewModdingAPI.Framework.Models [nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(), [nameof(GitHubProjectName)] = "Pathoschild/SMAPI", [nameof(WebApiBaseUrl)] = "https://smapi.io/api/", - [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, [nameof(UsePintail)] = true, @@ -57,8 +56,9 @@ namespace StardewModdingAPI.Framework.Models /// The base URL for SMAPI's web API, used to perform update checks. public string WebApiBaseUrl { get; } - /// Whether SMAPI should log more information about the game context. - public bool VerboseLogging { get; } + /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. + /// The possible values are "*" (everything is verbose), "SMAPI", (SMAPI itself), or mod IDs. + public HashSet VerboseLogging { get; } /// Whether SMAPI should rewrite mods for compatibility. public bool RewriteMods { get; } @@ -89,14 +89,14 @@ namespace StardewModdingAPI.Framework.Models /// Whether to show beta versions as valid updates. /// SMAPI's GitHub project name, used to perform update checks. /// The base URL for SMAPI's web API, used to perform update checks. - /// Whether SMAPI should log more information about the game context. + /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. /// Whether SMAPI should rewrite mods for compatibility. /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. /// >Whether to make SMAPI file APIs case-insensitive, even on Linux. /// Whether SMAPI should log network traffic. /// The colors to use for text written to the SMAPI console. /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool? 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? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -104,7 +104,7 @@ namespace StardewModdingAPI.Framework.Models this.UseBetaChannel = useBetaChannel ?? (bool)SConfig.DefaultValues[nameof(this.UseBetaChannel)]; this.GitHubProjectName = gitHubProjectName; this.WebApiBaseUrl = webApiBaseUrl; - this.VerboseLogging = verboseLogging ?? (bool)SConfig.DefaultValues[nameof(this.VerboseLogging)]; + this.VerboseLogging = new HashSet(verboseLogging ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)]; this.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(this.UsePintail)]; this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)]; @@ -133,7 +133,10 @@ namespace StardewModdingAPI.Framework.Models } if (!this.SuppressUpdateChecks.SetEquals(SConfig.DefaultSuppressUpdateChecks)) - custom[nameof(this.SuppressUpdateChecks)] = "[" + string.Join(", ", this.SuppressUpdateChecks) + "]"; + custom[nameof(this.SuppressUpdateChecks)] = $"[{string.Join(", ", this.SuppressUpdateChecks)}]"; + + if (this.VerboseLogging.Any()) + custom[nameof(this.VerboseLogging)] = $"[{string.Join(", ", this.VerboseLogging)}]"; return custom; } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index fa217f20..4f4212dc 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -194,7 +194,7 @@ namespace StardewModdingAPI.Framework if (developerMode.HasValue) this.Settings.OverrideDeveloperMode(developerMode.Value); - this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, isVerbose: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog); + this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, verboseLogging: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog); this.CommandManager = new CommandManager(this.Monitor); this.EventManager = new EventManager(this.ModRegistry); SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); @@ -1827,7 +1827,7 @@ namespace StardewModdingAPI.Framework // load as content pack if (mod.IsContentPack) { - IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); + IMonitor monitor = this.LogManager.GetMonitor(manifest.UniqueID, mod.DisplayName); IFileLookup fileLookup = this.GetFileLookup(mod.DirectoryPath); GameContentHelper gameContentHelper = new(this.ContentCore, mod, mod.DisplayName, monitor, this.Reflection); IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), this.Reflection); @@ -1902,7 +1902,7 @@ namespace StardewModdingAPI.Framework } // init mod helpers - IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); + IMonitor monitor = this.LogManager.GetMonitor(manifest.UniqueID, mod.DisplayName); TranslationHelper translationHelper = new(mod, contentCore.GetLocale(), contentCore.Language); IModHelper modHelper; { @@ -1965,7 +1965,7 @@ namespace StardewModdingAPI.Framework ); // create mod helpers - IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.Name); + IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.UniqueID, packManifest.Name); GameContentHelper gameContentHelper = new(contentCore, fakeMod, packManifest.Name, packMonitor, this.Reflection); IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, fakeMod, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), this.Reflection); TranslationHelper packTranslationHelper = new(fakeMod, contentCore.GetLocale(), contentCore.Language); diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 8324f45b..a6ec42d3 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -16,9 +16,17 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ { /** - * Whether SMAPI should log more information about the game context. + * The logs for which to enable verbose logging, which may show a lot more information to + * simplify troubleshooting. + * + * The possible values are: + * - "*" for everything (not recommended); + * - "SMAPI" for messages from SMAPI itself; + * - mod IDs from their manifest.json files. + * + * For example: [ "SMAPI", "Pathoschild.ContentPatcher" ] */ - "VerboseLogging": false, + "VerboseLogging": [], /** * Whether SMAPI should check for newer versions of SMAPI and mods when you load the game. If new -- cgit From a546fd113f431bd8888da50aad087213193c937e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 25 May 2022 18:02:48 -0400 Subject: add experimental image load rewrite --- src/SMAPI/Framework/ContentCoordinator.cs | 10 ++++-- .../Framework/ContentManagers/ModContentManager.cs | 41 +++++++++++++++++++--- src/SMAPI/Framework/Models/SConfig.cs | 8 ++++- src/SMAPI/Framework/SCore.cs | 3 +- src/SMAPI/SMAPI.config.json | 5 +++ src/SMAPI/SMAPI.csproj | 1 + 6 files changed, 59 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 69a39ac7..3ad112cd 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -32,6 +32,9 @@ namespace StardewModdingAPI.Framework /// An asset key prefix for assets from SMAPI mod folders. private readonly string ManagedPrefix = "SMAPI"; + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + private readonly bool UseExperimentalImageLoading; + /// Get a file lookup for the given directory. private readonly Func GetFileLookup; @@ -130,7 +133,8 @@ namespace StardewModdingAPI.Framework /// Get a file lookup for the given directory. /// A callback to invoke when any asset names have been invalidated from the cache. /// Get the load/edit operations to apply to an asset by querying registered event handlers. - public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action onAssetLoaded, Func getFileLookup, Action> onAssetsInvalidated, Func requestAssetOperations) + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action onAssetLoaded, Func getFileLookup, Action> onAssetsInvalidated, Func requestAssetOperations, bool useExperimentalImageLoading) { this.GetFileLookup = getFileLookup; this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); @@ -141,6 +145,7 @@ namespace StardewModdingAPI.Framework this.OnAssetsInvalidated = onAssetsInvalidated; this.RequestAssetOperations = requestAssetOperations; this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory); + this.UseExperimentalImageLoading = useExperimentalImageLoading; this.ContentManagers.Add( this.MainContentManager = new GameContentManager( name: "Game1.content", @@ -219,7 +224,8 @@ namespace StardewModdingAPI.Framework reflection: this.Reflection, jsonHelper: this.JsonHelper, onDisposing: this.OnDisposing, - fileLookup: this.GetFileLookup(rootDirectory) + fileLookup: this.GetFileLookup(rootDirectory), + useExperimentalImageLoading: this.UseExperimentalImageLoading ); this.ContentManagers.Add(manager); return manager; diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 1b94b8c6..055dcc5f 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -7,6 +7,7 @@ using BmFont; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using SkiaSharp; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Serialization; @@ -25,6 +26,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Fields *********/ + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + private readonly bool UseExperimentalImageLoading; + /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; @@ -57,13 +61,15 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke when the content manager is being disposed. /// A lookup for files within the . - public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, IFileLookup fileLookup) + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, IFileLookup fileLookup, bool useExperimentalImageLoading) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) { this.GameContentManager = gameContentManager; this.FileLookup = fileLookup; this.JsonHelper = jsonHelper; this.ModName = modName; + this.UseExperimentalImageLoading = useExperimentalImageLoading; this.TryLocalizeKeys = false; } @@ -187,10 +193,35 @@ 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)}'."); // load - using FileStream stream = File.OpenRead(file.FullName); - Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); - texture = this.PremultiplyTransparency(texture); - return (T)(object)texture; + 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); + Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); + texture = this.PremultiplyTransparency(texture); + return (T)(object)texture; + } } /// Load an unpacked image file (.tbin or .tmx). diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 240af002..f12da0a7 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -23,6 +23,7 @@ namespace StardewModdingAPI.Framework.Models [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, [nameof(UsePintail)] = true, + [nameof(UseExperimentalImageLoading)] = false, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux }; @@ -66,6 +67,9 @@ namespace StardewModdingAPI.Framework.Models /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. public bool UsePintail { get; } + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + public bool UseExperimentalImageLoading { get; } + /// Whether to make SMAPI file APIs case-insensitive, even on Linux. public bool UseCaseInsensitivePaths { get; } @@ -92,11 +96,12 @@ namespace StardewModdingAPI.Framework.Models /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. /// Whether SMAPI should rewrite mods for compatibility. /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. + /// Whether to use a newer approach when loading image files from mod folder which may be faster. /// >Whether to make SMAPI file APIs case-insensitive, even on Linux. /// Whether SMAPI should log network traffic. /// The colors to use for text written to the SMAPI console. /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - 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.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -107,6 +112,7 @@ namespace StardewModdingAPI.Framework.Models this.VerboseLogging = new HashSet(verboseLogging ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)]; 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.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)]; this.ConsoleColors = consoleColors; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 4f4212dc..242776b3 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1301,7 +1301,8 @@ namespace StardewModdingAPI.Framework onAssetLoaded: this.OnAssetLoaded, onAssetsInvalidated: this.OnAssetsInvalidated, getFileLookup: this.GetFileLookup, - requestAssetOperations: this.RequestAssetOperations + requestAssetOperations: this.RequestAssetOperations, + useExperimentalImageLoading: this.Settings.UseExperimentalImageLoading ); if (this.ContentCore.Language != this.Translator.LocaleEnum) this.Translator.SetLocale(this.ContentCore.GetLocale(), this.ContentCore.Language); diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index a6ec42d3..8e710435 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -60,6 +60,11 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ "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 * sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index a0ca54cc..91e4c668 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -41,6 +41,7 @@ + -- cgit From 4708385f696d2e47d24e795f210752f4b0224bff Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 25 May 2022 22:46:51 -0400 Subject: add IRawTextureData asset type --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 173 ++++++++++++++------- src/SMAPI/Framework/Content/RawTextureData.cs | 10 ++ .../ContentManagers/GameContentManager.cs | 4 + .../Framework/ContentManagers/ModContentManager.cs | 23 ++- src/SMAPI/IAssetDataForImage.cs | 10 ++ src/SMAPI/IContentHelper.cs | 2 +- src/SMAPI/IContentPack.cs | 2 +- src/SMAPI/IModContentHelper.cs | 2 +- src/SMAPI/IRawTextureData.cs | 17 ++ 9 files changed, 182 insertions(+), 61 deletions(-) create mode 100644 src/SMAPI/Framework/Content/RawTextureData.cs create mode 100644 src/SMAPI/IRawTextureData.cs (limited to 'src') diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 97729c95..3393b22f 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewValley; @@ -29,86 +30,154 @@ namespace StardewModdingAPI.Framework.Content : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } /// - public void PatchImage(Texture2D 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) { - // get texture + this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); + + // validate source data if (source == null) - throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); - Texture2D target = this.Data; + throw new ArgumentNullException(nameof(source), "Can't patch from null source data."); - // get areas - sourceArea ??= new Rectangle(0, 0, source.Width, source.Height); - targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); + // get the pixels for the source area + Color[] sourceData; + { + int areaX = sourceArea.Value.X; + int areaY = sourceArea.Value.Y; + int areaWidth = sourceArea.Value.Width; + int areaHeight = sourceArea.Value.Height; - // validate + if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight == source.Height) + sourceData = source.Data; + else + { + sourceData = new Color[areaWidth * areaHeight]; + int i = 0; + for (int y = areaY, maxY = areaY + areaHeight - 1; y <= maxY; y++) + { + for (int x = areaX, maxX = areaX + areaWidth - 1; x <= maxX; x++) + { + int targetIndex = (y * source.Width) + x; + sourceData[i++] = source.Data[targetIndex]; + } + } + } + } + + // apply + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + } + + /// + public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) + { + this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); + + // validate source texture + if (source == null) + throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); if (!source.Bounds.Contains(sourceArea.Value)) throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); - if (!target.Bounds.Contains(targetArea.Value)) - throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); - if (sourceArea.Value.Size != targetArea.Value.Size) - throw new InvalidOperationException("The source and target areas must be the same size."); // get source data int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; Color[] sourceData = GC.AllocateUninitializedArray(pixelCount); source.GetData(0, sourceArea, sourceData, 0, pixelCount); - // merge data in overlay mode + // apply + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + } + + /// + public bool ExtendImage(int minWidth, int minHeight) + { + if (this.Data.Width >= minWidth && this.Data.Height >= minHeight) + return false; + + Texture2D original = this.Data; + Texture2D texture = new(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight)); + this.ReplaceWith(texture); + this.PatchImage(original); + return true; + } + + + /********* + ** Private methods + *********/ + /// Get the bounds for an image patch. + /// The source area to set if needed. + /// The target area to set if needed. + /// The width of the full source image. + /// The height of the full source image. + private void GetPatchBounds([NotNull] ref Rectangle? sourceArea, [NotNull] ref Rectangle? targetArea, int sourceWidth, int sourceHeight) + { + sourceArea ??= new Rectangle(0, 0, sourceWidth, sourceHeight); + targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, this.Data.Width), Math.Min(sourceArea.Value.Height, this.Data.Height)); + } + + /// Overwrite part of the image. + /// The image data to patch into the content. + /// The pixel width of the source image. + /// The pixel height of the source image. + /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. + /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. + /// Indicates how an image should be patched. + /// One of the arguments is null. + /// The is outside the bounds of the spritesheet. + /// The content being read isn't an image. + private void PatchImageImpl(Color[] sourceData, int sourceWidth, int sourceHeight, Rectangle sourceArea, Rectangle targetArea, PatchMode patchMode) + { + // get texture + Texture2D target = this.Data; + int pixelCount = sourceArea.Width * sourceArea.Height; + + // validate + if (sourceArea.X < 0 || sourceArea.Y < 0 || sourceArea.Right > sourceWidth || sourceArea.Bottom > sourceHeight) + throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); + if (!target.Bounds.Contains(targetArea)) + throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); + if (sourceArea.Size != targetArea.Size) + throw new InvalidOperationException("The source and target areas must be the same size."); + + // merge data if (patchMode == PatchMode.Overlay) { // get target data - Color[] targetData = GC.AllocateUninitializedArray(pixelCount); - target.GetData(0, targetArea, targetData, 0, pixelCount); + Color[] mergedData = GC.AllocateUninitializedArray(pixelCount); + target.GetData(0, targetArea, mergedData, 0, pixelCount); // merge pixels - for (int i = 0; i < sourceData.Length; i++) + for (int i = 0; i < pixelCount; i++) { Color above = sourceData[i]; - Color below = targetData[i]; + Color below = mergedData[i]; // shortcut transparency if (above.A < MinOpacity) - { - sourceData[i] = below; continue; - } if (below.A < MinOpacity) - { - sourceData[i] = above; - continue; - } + mergedData[i] = above; // merge pixels - // 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/. - // Note: don't use named arguments here since they're different between - // Linux/macOS and Windows. - float alphaBelow = 1 - (above.A / 255f); - sourceData[i] = new Color( - (int)(above.R + (below.R * alphaBelow)), // r - (int)(above.G + (below.G * alphaBelow)), // g - (int)(above.B + (below.B * alphaBelow)), // b - Math.Max(above.A, below.A) // a - ); + 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[i] = 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) + ); + } } - } - - // patch target texture - target.SetData(0, targetArea, sourceData, 0, pixelCount); - } - - /// - public bool ExtendImage(int minWidth, int minHeight) - { - if (this.Data.Width >= minWidth && this.Data.Height >= minHeight) - return false; - Texture2D original = this.Data; - Texture2D texture = new(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight)); - this.ReplaceWith(texture); - this.PatchImage(original); - return true; + target.SetData(0, targetArea, mergedData, 0, pixelCount); + } + else + target.SetData(0, targetArea, sourceData, 0, pixelCount); } } } diff --git a/src/SMAPI/Framework/Content/RawTextureData.cs b/src/SMAPI/Framework/Content/RawTextureData.cs new file mode 100644 index 00000000..4a0835b0 --- /dev/null +++ b/src/SMAPI/Framework/Content/RawTextureData.cs @@ -0,0 +1,10 @@ +using Microsoft.Xna.Framework; + +namespace StardewModdingAPI.Framework.Content +{ + /// The raw data for an image read from the filesystem. + /// The image width. + /// The image height. + /// The loaded image data. + internal record RawTextureData(int Width, int Height, Color[] Data) : IRawTextureData; +} diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 4390d472..446f4a67 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -9,6 +9,7 @@ using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Deprecations; +using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; @@ -93,6 +94,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// public override T LoadExact(IAssetName assetName, bool useCache) { + if (typeof(IRawTextureData).IsAssignableFrom(typeof(T))) + throw new SContentLoadException(ContentLoadErrorType.Other, $"Can't load {nameof(IRawTextureData)} assets from the game content pipeline. This asset type is only available for mod files."); + // raise first-load callback if (GameContentManager.IsFirstLoad) { diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 055dcc5f..eb4f4555 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -8,6 +8,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using SkiaSharp; +using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Serialization; @@ -188,12 +189,17 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The file to load. private T LoadImageFile(IAssetName assetName, FileInfo file) { - // validate + // validate type + bool asRawData = false; if (typeof(T) != 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)}'."); + { + asRawData = typeof(T) == typeof(IRawTextureData); + if (!asRawData) + throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}' or '{typeof(IRawTextureData)}'."); + } // load - if (this.UseExperimentalImageLoading) + if (asRawData || this.UseExperimentalImageLoading) { // load raw data using FileStream stream = File.OpenRead(file.FullName); @@ -211,9 +217,14 @@ namespace StardewModdingAPI.Framework.ContentManagers } // create texture - Texture2D texture = new(Game1.graphics.GraphicsDevice, bitmap.Width, bitmap.Height); - texture.SetData(pixels); - return (T)(object)texture; + if (asRawData) + return (T)(object)new RawTextureData(bitmap.Width, bitmap.Height, pixels); + else + { + Texture2D texture = new(Game1.graphics.GraphicsDevice, bitmap.Width, bitmap.Height); + texture.SetData(pixels); + return (T)(object)texture; + } } else { diff --git a/src/SMAPI/IAssetDataForImage.cs b/src/SMAPI/IAssetDataForImage.cs index 6f8a4719..3e5b833d 100644 --- a/src/SMAPI/IAssetDataForImage.cs +++ b/src/SMAPI/IAssetDataForImage.cs @@ -10,6 +10,16 @@ namespace StardewModdingAPI /********* ** Public methods *********/ + /// Overwrite part of the image. + /// The image to patch into the content. + /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. + /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. + /// Indicates how an image should be patched. + /// One of the arguments is null. + /// The is outside the bounds of the spritesheet. + /// The content being read isn't an image. + void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); + /// Overwrite part of the image. /// The image to patch into the content. /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs index 2cd0c1fc..7637edf0 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -35,7 +35,7 @@ namespace StardewModdingAPI ** Public methods *********/ /// Load content from the game folder or mod folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. - /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. + /// The expected data type. The main supported types are , , (for mod content only), and data structures; other types may be supported by the game's content pipeline. /// The asset key to fetch (if the is ), or the local path to a content file relative to the mod folder. /// Where to search for a matching content asset. /// The is empty or contains invalid characters. diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index 1215fe0b..73b1a860 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -48,7 +48,7 @@ namespace StardewModdingAPI where TModel : class; /// Load content from the content pack folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. - /// The expected data type. The main supported types are , , and dictionaries; other types may be supported by the game's content pipeline. + /// The expected data type. The main supported types are , , , and data structures; other types may be supported by the game's content pipeline. /// The relative file path within the content pack (case-insensitive). /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). diff --git a/src/SMAPI/IModContentHelper.cs b/src/SMAPI/IModContentHelper.cs index f1f6ce94..1e2d82a8 100644 --- a/src/SMAPI/IModContentHelper.cs +++ b/src/SMAPI/IModContentHelper.cs @@ -12,7 +12,7 @@ namespace StardewModdingAPI ** Public methods *********/ /// Load content from the mod folder and return it. When loading a .png file, this must be called outside the game's draw loop. - /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. + /// The expected data type. The main supported types are , , , and data structures; other types may be supported by the game's content pipeline. /// The local path to a content file relative to the mod folder. /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). diff --git a/src/SMAPI/IRawTextureData.cs b/src/SMAPI/IRawTextureData.cs new file mode 100644 index 00000000..a4da52f3 --- /dev/null +++ b/src/SMAPI/IRawTextureData.cs @@ -0,0 +1,17 @@ +using Microsoft.Xna.Framework; + +namespace StardewModdingAPI +{ + /// The raw data for an image read from the filesystem. + public interface IRawTextureData + { + /// The image width. + int Width { get; } + + /// The image height. + int Height { get; } + + /// The loaded image data. + Color[] Data { get; } + } +} -- cgit From 769475166ab3b92cd3763bb86e364a8b2c7d914f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 26 May 2022 00:51:11 -0400 Subject: enable raw image loading by default, rename setting --- src/SMAPI/Framework/ContentCoordinator.cs | 12 ++++++------ src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 12 ++++++------ src/SMAPI/Framework/Models/SConfig.cs | 12 ++++++------ src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/SMAPI.config.json | 5 +++-- 5 files changed, 22 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 3ad112cd..3e09ac62 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -32,8 +32,8 @@ namespace StardewModdingAPI.Framework /// An asset key prefix for assets from SMAPI mod folders. private readonly string ManagedPrefix = "SMAPI"; - /// Whether to use a newer approach when loading image files from mod folder which may be faster. - private readonly bool UseExperimentalImageLoading; + /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. + private readonly bool UseRawImageLoading; /// Get a file lookup for the given directory. private readonly Func GetFileLookup; @@ -133,8 +133,8 @@ namespace StardewModdin