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 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Framework/Models/SConfig.cs') 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; -- 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 --- docs/release-notes.md | 1 + 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 ++++++++++-- 5 files changed, 35 insertions(+), 22 deletions(-) (limited to 'src/SMAPI/Framework/Models/SConfig.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index b7605bf6..e1aa47ab 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ## Upcoming release * For mod authors: * 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). ## 3.14.7 Released 01 June 2022 for Stardew Valley 1.5.6 or later. 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 --- docs/release-notes.md | 4 +++ 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 + 7 files changed, 63 insertions(+), 9 deletions(-) (limited to 'src/SMAPI/Framework/Models/SConfig.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index e1aa47ab..a1b5222e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ # Release notes ## 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: * 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). 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 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 --- docs/release-notes.md | 7 +++---- 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 +++-- 6 files changed, 25 insertions(+), 25 deletions(-) (limited to 'src/SMAPI/Framework/Models/SConfig.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index b22f4de9..7fff656a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,11 +3,10 @@ # Release notes ## 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._ + * Optimized mod image file loading. * For mod authors: - * Added specialized `IRawTextureData` asset type. - _When you're only loading a mod file to patch it into an asset, you can now load it using `helper.ModContent.Load(path)`. This reads the image data from disk without initializing a `Texture2D` instance through the GPU. You can then pass this to SMAPI APIs that accept `Texture2D` instances._ + * Added a new `IRawTextureData` asset type. + _You can now load image files through `helper.ModContent` as `IRawTextureData` instead of `Texture2D`. This provides the image size and raw pixel data, which you can pass into other SMAPI APIs like `asset.AsImage().PatchImage`. This is much more efficient when you don't need a full `Texture2D` instance, since it bypasses the GPU operations needed to create one._ * For mod authors: * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. 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 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. - /// 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) + /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. + 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 useRawImageLoading) { this.GetFileLookup = getFileLookup; this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); @@ -145,7 +145,7 @@ namespace StardewModdingAPI.Framework this.OnAssetsInvalidated = onAssetsInvalidated; this.RequestAssetOperations = requestAssetOperations; this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory); - this.UseExperimentalImageLoading = useExperimentalImageLoading; + this.UseRawImageLoading = useRawImageLoading; this.ContentManagers.Add( this.MainContentManager = new GameContentManager( name: "Game1.content", @@ -225,7 +225,7 @@ namespace StardewModdingAPI.Framework jsonHelper: this.JsonHelper, onDisposing: this.OnDisposing, fileLookup: this.GetFileLookup(rootDirectory), - useExperimentalImageLoading: this.UseExperimentalImageLoading + useRawImageLoading: this.UseRawImageLoading ); this.ContentManagers.Add(manager); return manager; diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index eb4f4555..f0e9b1b9 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -27,8 +27,8 @@ 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; + /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. + private readonly bool UseRawImageLoading; /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; @@ -62,15 +62,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 . - /// 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) + /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. + 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 useRawImageLoading) : 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.UseRawImageLoading = useRawImageLoading; this.TryLocalizeKeys = false; } @@ -199,7 +199,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // load - if (asRawData || this.UseExperimentalImageLoading) + if (asRawData || this.UseRawImageLoading) { // load raw data using FileStream stream = File.OpenRead(file.FullName); diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index f12da0a7..6edaa818 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.Models [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, [nameof(UsePintail)] = true, - [nameof(UseExperimentalImageLoading)] = false, + [nameof(UseRawImageLoading)] = true, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux }; @@ -67,8 +67,8 @@ 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 use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. + public bool UseRawImageLoading { get; } /// Whether to make SMAPI file APIs case-insensitive, even on Linux. public bool UseCaseInsensitivePaths { get; } @@ -96,12 +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 use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. /// >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? useExperimentalImageLoading, 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? useRawImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -112,7 +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.UseRawImageLoading = useRawImageLoading ?? (bool)SConfig.DefaultValues[nameof(this.UseRawImageLoading)]; 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 242776b3..f018acad 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1302,7 +1302,7 @@ namespace StardewModdingAPI.Framework onAssetsInvalidated: this.OnAssetsInvalidated, getFileLookup: this.GetFileLookup, requestAssetOperations: this.RequestAssetOperations, - useExperimentalImageLoading: this.Settings.UseExperimentalImageLoading + useRawImageLoading: this.Settings.UseRawImageLoading ); 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 8e710435..97e8e00c 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -61,9 +61,10 @@ 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. + * Whether to use raw image data when possible, instead of initializing an XNA Texture2D + * instance through the GPU. */ - "UseExperimentalImageLoading": false, + "UseRawImageLoading": true, /** * Whether to add a section to the 'mod issues' list for mods which directly use potentially -- cgit From db578c389e35ee026ed4ea12dfdcef99f8bc3b28 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 26 May 2022 00:57:13 -0400 Subject: drop support for pre-Pintail proxying --- docs/release-notes.md | 1 + src/SMAPI.Tests/Core/InterfaceProxyTests.cs | 7 +- src/SMAPI/Framework/Models/SConfig.cs | 6 -- .../Reflection/OriginalInterfaceProxyBuilder.cs | 118 --------------------- .../Reflection/OriginalInterfaceProxyFactory.cs | 57 ---------- src/SMAPI/Framework/SCore.cs | 4 +- 6 files changed, 4 insertions(+), 189 deletions(-) delete mode 100644 src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs delete mode 100644 src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs (limited to 'src/SMAPI/Framework/Models/SConfig.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 7fff656a..ddf97fe8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,7 @@ * For mod authors: * Added a new `IRawTextureData` asset type. _You can now load image files through `helper.ModContent` as `IRawTextureData` instead of `Texture2D`. This provides the image size and raw pixel data, which you can pass into other SMAPI APIs like `asset.AsImage().PatchImage`. This is much more efficient when you don't need a full `Texture2D` instance, since it bypasses the GPU operations needed to create one._ + * Removed transitional `UsePintail` option added in 3.14.0 (now always enabled). * For mod authors: * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. diff --git a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs index 6be97526..d14c116f 100644 --- a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs +++ b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs @@ -29,11 +29,8 @@ namespace SMAPI.Tests.Core /// The random number generator with which to create sample values. private readonly Random Random = new(); - /// Sample user inputs for season names. - private static readonly IInterfaceProxyFactory[] ProxyFactories = { - new InterfaceProxyFactory(), - new OriginalInterfaceProxyFactory() - }; + /// The proxy factory to use in unit tests. + private static readonly IInterfaceProxyFactory[] ProxyFactories = { new InterfaceProxyFactory() }; /********* diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 6edaa818..baef6144 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -22,7 +22,6 @@ namespace StardewModdingAPI.Framework.Models [nameof(WebApiBaseUrl)] = "https://smapi.io/api/", [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, - [nameof(UsePintail)] = true, [nameof(UseRawImageLoading)] = true, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux }; @@ -64,9 +63,6 @@ namespace StardewModdingAPI.Framework.Models /// Whether SMAPI should rewrite mods for compatibility. public bool RewriteMods { get; } - /// 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 raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. public bool UseRawImageLoading { get; } @@ -95,7 +91,6 @@ namespace StardewModdingAPI.Framework.Models /// The base URL for SMAPI's web API, used to perform update checks. /// 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 raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. /// >Whether to make SMAPI file APIs case-insensitive, even on Linux. /// Whether SMAPI should log network traffic. @@ -111,7 +106,6 @@ namespace StardewModdingAPI.Framework.Models this.WebApiBaseUrl = webApiBaseUrl; 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.UseRawImageLoading = useRawImageLoading ?? (bool)SConfig.DefaultValues[nameof(this.UseRawImageLoading)]; this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)]; this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)]; diff --git a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs deleted file mode 100644 index 9576f768..00000000 --- a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// Generates a proxy class to access a mod API through an arbitrary interface. - internal class OriginalInterfaceProxyBuilder - { - /********* - ** Fields - *********/ - /// The target class type. - private readonly Type TargetType; - - /// The generated proxy type. - private readonly Type ProxyType; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type name to generate. - /// The CLR module in which to create proxy classes. - /// The interface type to implement. - /// The target type. - public OriginalInterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType) - { - // validate - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (targetType == null) - throw new ArgumentNullException(nameof(targetType)); - - // define proxy type - TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class); - proxyBuilder.AddInterfaceImplementation(interfaceType); - - // create field to store target instance - FieldBuilder targetField = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private); - - // create constructor which accepts target instance and sets field - { - ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType }); - ILGenerator il = constructor.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); // this - // ReSharper disable once AssignNullToNotNullAttribute -- never null - il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)!); // call base constructor - il.Emit(OpCodes.Ldarg_0); // this - il.Emit(OpCodes.Ldarg_1); // load argument - il.Emit(OpCodes.Stfld, targetField); // set field to loaded argument - il.Emit(OpCodes.Ret); - } - - // proxy methods - foreach (MethodInfo proxyMethod in interfaceType.GetMethods()) - { - var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray()); - if (targetMethod == null) - throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); - - this.ProxyMethod(proxyBuilder, targetMethod, targetField); - } - - // save info - this.TargetType = targetType; - this.ProxyType = proxyBuilder.CreateType()!; - } - - /// Create an instance of the proxy for a target instance. - /// The target instance. - public object CreateInstance(object targetInstance) - { - ConstructorInfo? constructor = this.ProxyType.GetConstructor(new[] { this.TargetType }); - if (constructor == null) - throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen - return constructor.Invoke(new[] { targetInstance }); - } - - - /********* - ** Private methods - *********/ - /// Define a method which proxies access to a method on the target. - /// The proxy type being generated. - /// The target method. - /// The proxy field containing the API instance. - private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField) - { - Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray(); - - // create method - MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); - methodBuilder.SetParameters(argTypes); - methodBuilder.SetReturnType(target.ReturnType); - - // create method body - { - ILGenerator il = methodBuilder.GetILGenerator(); - - // load target instance - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, instanceField); - - // invoke target method on instance - for (int i = 0; i < argTypes.Length; i++) - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Call, target); - - // return result - il.Emit(OpCodes.Ret); - } - } - } -} diff --git a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs deleted file mode 100644 index d6966978..00000000 --- a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// - internal class OriginalInterfaceProxyFactory : IInterfaceProxyFactory - { - /********* - ** Fields - *********/ - /// The CLR module in which to create proxy classes. - private readonly ModuleBuilder ModuleBuilder; - - /// The generated proxy types. - private readonly IDictionary Builders = new Dictionary(); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public OriginalInterfaceProxyFactory() - { - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); - this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); - } - - /// - public TInterface CreateProxy(object instance, string sourceModID, string targetModID) - where TInterface : class - { - lock (this.Builders) - { - // validate - if (instance == null) - throw new InvalidOperationException("Can't proxy access to a null API."); - if (!typeof(TInterface).IsInterface) - throw new InvalidOperationException("The proxy type must be an interface, not a class."); - - // get proxy type - Type targetType = instance.GetType(); - string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>"; - if (!this.Builders.TryGetValue(proxyTypeName, out OriginalInterfaceProxyBuilder? builder)) - { - builder = new OriginalInterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType); - this.Builders[proxyTypeName] = builder; - } - - // create instance - return (TInterface)builder.CreateInstance(instance); - } - } - } -} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index f018acad..fa3f8778 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1610,9 +1610,7 @@ namespace StardewModdingAPI.Framework { // init HashSet suppressUpdateChecks = this.Settings.SuppressUpdateChecks; - IInterfaceProxyFactory proxyFactory = this.Settings.UsePintail - ? new InterfaceProxyFactory() - : new OriginalInterfaceProxyFactory(); + IInterfaceProxyFactory proxyFactory = new InterfaceProxyFactory(); // load mods foreach (IModMetadata mod in mods) -- cgit