From 0629f19698c9920e2988d96f316d227f97932df8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 11 Nov 2022 01:22:45 -0500 Subject: [PATCH] change new fields to hash sets & simplify sorting This makes the mod IDs case-insensitive (like the 'SuppressUpdateChecks' field), fixes a build error in unit tests, and avoids re-scanning the mod list multiple times. --- src/SMAPI/Framework/ModLoading/ModResolver.cs | 50 +++++++++---------- src/SMAPI/Framework/Models/SConfig.cs | 16 +++--- src/SMAPI/Framework/SCore.cs | 28 +++++++---- 3 files changed, 51 insertions(+), 43 deletions(-) diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 8ef5e4a8..1080a888 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -242,12 +242,32 @@ namespace StardewModdingAPI.Framework.ModLoading } } + /// Apply preliminary overrides to the load order based on the SMAPI configuration. + /// The mods to process. + /// The mod IDs SMAPI should load before any other mods (except those needed to load them). + /// The mod IDs SMAPI should load after any other mods. + public IModMetadata[] ApplyLoadOrderOverrides(IModMetadata[] mods, HashSet modIdsToLoadEarly, HashSet modIdsToLoadLate) + { + if (!modIdsToLoadEarly.Any() && !modIdsToLoadLate.Any()) + return mods; + + return mods + .OrderBy(mod => + { + string id = mod.Manifest.UniqueID; + if (modIdsToLoadEarly.Contains(id)) + return -1; + if (modIdsToLoadLate.Contains(id)) + return 1; + return 0; + }) + .ToArray(); + } + /// Sort the given mods by the order they should be loaded. /// The mods to process. /// Handles access to SMAPI's internal mod metadata list. - /// The mod IDs SMAPI should try to load early, before any other mods not included in this list. - /// The mod IDs SMAPI should try to load late, after all other mods not included in this list. - public IEnumerable ProcessDependencies(IEnumerable mods, IReadOnlyList modIdsToLoadEarly, IReadOnlyList modIdsToLoadLate, ModDatabase modDatabase) + public IEnumerable ProcessDependencies(IReadOnlyList mods, ModDatabase modDatabase) { // initialize metadata mods = mods.ToArray(); @@ -262,28 +282,8 @@ namespace StardewModdingAPI.Framework.ModLoading } // sort mods - IModMetadata[] allMods = mods.ToArray(); - IModMetadata[] modsToLoadEarly = modIdsToLoadEarly - .Where(modId => !modIdsToLoadLate.Contains(modId)) - .Select(modId => allMods.FirstOrDefault(m => m.Manifest.UniqueID == modId)) - .Where(m => m != null) - .Select(m => m!) - .ToArray(); - IModMetadata[] modsToLoadLate = modIdsToLoadLate - .Where(modId => !modIdsToLoadEarly.Contains(modId)) - .Select(modId => allMods.FirstOrDefault(m => m.Manifest.UniqueID == modId)) - .Where(m => m != null) - .Select(m => m!) - .ToArray(); - IModMetadata[] modsToLoadAsUsual = allMods.Where(m => !modsToLoadEarly.Contains(m) && !modsToLoadLate.Contains(m)).ToArray(); - - List orderSortedMods = new(); - orderSortedMods.AddRange(modsToLoadEarly); - orderSortedMods.AddRange(modsToLoadAsUsual); - orderSortedMods.AddRange(modsToLoadLate); - - foreach (IModMetadata mod in orderSortedMods) - this.ProcessDependencies(orderSortedMods, modDatabase, mod, states, sortedMods, new List()); + foreach (IModMetadata mod in mods) + this.ProcessDependencies(mods, modDatabase, mod, states, sortedMods, new List()); return sortedMods.Reverse(); } diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 40d3450f..ee2dc18d 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -82,11 +82,11 @@ namespace StardewModdingAPI.Framework.Models /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. public HashSet SuppressUpdateChecks { get; set; } - /// The mod IDs SMAPI should try to load early, before any other mods not included in this list. - public List ModsToLoadEarly { get; set; } + /// The mod IDs SMAPI should load before any other mods (except those needed to load them). + public HashSet ModsToLoadEarly { get; set; } - /// The mod IDs SMAPI should try to load late, after all other mods not included in this list. - public List ModsToLoadLate { get; set; } + /// The mod IDs SMAPI should load after any other mods. + public HashSet ModsToLoadLate { get; set; } /******** @@ -106,8 +106,8 @@ namespace StardewModdingAPI.Framework.Models /// The colors to use for text written to the SMAPI console. /// Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and creates a file on your desktop. Debug mode should never be enabled by a released mod. /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - /// The mod IDs SMAPI should try to load early, before any other mods not included in this list. - /// The mod IDs SMAPI should try to load late, after all other mods not included in this list. + /// The mod IDs SMAPI should load before any other mods (except those needed to load them). + /// The mod IDs SMAPI should load after any other mods. public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate) { this.DeveloperMode = developerMode; @@ -123,8 +123,8 @@ namespace StardewModdingAPI.Framework.Models this.ConsoleColors = consoleColors; this.SuppressHarmonyDebugMode = suppressHarmonyDebugMode ?? (bool)SConfig.DefaultValues[nameof(this.SuppressHarmonyDebugMode)]; this.SuppressUpdateChecks = new HashSet(suppressUpdateChecks ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); - this.ModsToLoadEarly = new List(modsToLoadEarly ?? Array.Empty()); - this.ModsToLoadLate = new List(modsToLoadLate ?? Array.Empty()); + this.ModsToLoadEarly = new HashSet(modsToLoadEarly ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); + this.ModsToLoadLate = new HashSet(modsToLoadLate ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); } /// Override the value of . diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 4d1eb959..fb835002 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -424,19 +424,27 @@ namespace StardewModdingAPI.Framework mods = mods.Where(p => !p.IsIgnored).ToArray(); // warn about mods that should load early or late which are not found at all, or both - foreach (string modId in this.Settings.ModsToLoadEarly) - if (!mods.Any(m => m.Manifest.UniqueID == modId)) - this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load early, but it could not be found or was skipped.", LogLevel.Warn); - foreach (string modId in this.Settings.ModsToLoadLate) - if (!mods.Any(m => m.Manifest.UniqueID == modId)) - this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load late, but it could not be found or was skipped.", LogLevel.Warn); - foreach (string modId in this.Settings.ModsToLoadEarly) - if (this.Settings.ModsToLoadLate.Contains(modId)) - this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load both early and late - this will be ignored.", LogLevel.Warn); + { + HashSet installedIds = new HashSet(mods.Select(p => p.Manifest.UniqueID), StringComparer.OrdinalIgnoreCase); + + foreach (string modId in this.Settings.ModsToLoadEarly) + { + if (!installedIds.Contains(modId)) + this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load early, but it could not be found or was skipped.", LogLevel.Warn); + } + foreach (string modId in this.Settings.ModsToLoadLate) + { + if (this.Settings.ModsToLoadEarly.Contains(modId)) + this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load both early and late - this will be ignored.", LogLevel.Warn); + else if (!installedIds.Contains(modId)) + this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load late, but it could not be found or was skipped.", LogLevel.Warn); + } + } // load mods resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl, getFileLookup: this.GetFileLookup); - mods = resolver.ProcessDependencies(mods, this.Settings.ModsToLoadEarly, this.Settings.ModsToLoadLate, modDatabase).ToArray(); + mods = resolver.ApplyLoadOrderOverrides(mods, this.Settings.ModsToLoadEarly, this.Settings.ModsToLoadLate); + mods = resolver.ProcessDependencies(mods, modDatabase).ToArray(); this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase); // check for software likely to cause issues