From 3707f481a567df5149aea00ffb14cddb7b14fccb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 25 Mar 2022 23:53:30 -0400 Subject: extend load conflict resolution into load priority (#766) --- src/SMAPI/Events/AssetLoadPriority.cs | 19 +++++++++++++++++++ src/SMAPI/Events/AssetRequestedEventArgs.cs | 14 +++++++------- src/SMAPI/Framework/Content/AssetLoadOperation.cs | 11 ++++++----- src/SMAPI/Framework/ContentCoordinator.cs | 2 +- .../Framework/ContentManagers/GameContentManager.cs | 10 +++++----- 5 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 src/SMAPI/Events/AssetLoadPriority.cs (limited to 'src') diff --git a/src/SMAPI/Events/AssetLoadPriority.cs b/src/SMAPI/Events/AssetLoadPriority.cs new file mode 100644 index 00000000..e07b5a40 --- /dev/null +++ b/src/SMAPI/Events/AssetLoadPriority.cs @@ -0,0 +1,19 @@ +namespace StardewModdingAPI.Events +{ + /// The priority for an asset load when multiple apply for the same asset. + /// If multiple non- loads have the same priority, the one registered first will be selected. You can also specify arbitrary intermediate values, like AssetLoadPriority.Low + 5. + public enum AssetLoadPriority + { + /// This load is optional and can safely be skipped if there are higher-priority loads. + Low = -1000, + + /// The load is optional and can safely be skipped if there are higher-priority loads, but it should still be preferred over any -priority loads. + Medium = 0, + + /// The load is optional and can safely be skipped if there are higher-priority loads, but it should still be preferred over any - or -priority loads. + High = 1000, + + /// The load is not optional. If more than one loader has priority, SMAPI will log an error and ignore all of them. + Exclusive = int.MaxValue + } +} diff --git a/src/SMAPI/Events/AssetRequestedEventArgs.cs b/src/SMAPI/Events/AssetRequestedEventArgs.cs index 9942079b..d022a4de 100644 --- a/src/SMAPI/Events/AssetRequestedEventArgs.cs +++ b/src/SMAPI/Events/AssetRequestedEventArgs.cs @@ -49,21 +49,21 @@ namespace StardewModdingAPI.Events /// Provide the initial instance for the asset, instead of trying to load it from the game's Content folder. /// Get the initial instance of an asset. + /// If there are multiple loads that apply to the same asset, the priority with which this one should be applied. /// The content pack ID on whose behalf you're applying the change. This is only valid for content packs for your mod. - /// When there are multiple loads that apply to the same asset, this indicates whether this one can be skipped to resolve the conflict. If all loads allow skipping, the first one that was registered will be applied. If this is false, SMAPI will raise an error and apply none of them. /// /// Usage notes: /// /// The asset doesn't need to exist in the game's Content folder. If any mod loads the asset, the game will see it as an existing asset as if it was in that folder. - /// Each asset can logically only have one initial instance. If multiple loads apply at the same time, SMAPI will use the parameter to decide what happens. If you're making changes to the existing asset instead of replacing it, you should use instead to avoid those limitations and improve mod compatibility. + /// Each asset can logically only have one initial instance. If multiple loads apply at the same time, SMAPI will use the parameter to decide what happens. If you're making changes to the existing asset instead of replacing it, you should use instead to avoid those limitations and improve mod compatibility. /// /// - public void LoadFrom(Func load, string onBehalfOf = null, bool allowSkipOnConflict = false) + public void LoadFrom(Func load, AssetLoadPriority priority, string onBehalfOf = null) { this.LoadOperations.Add( new AssetLoadOperation( mod: this.Mod, - allowSkipOnConflict: allowSkipOnConflict, + priority: priority, onBehalfOf: this.GetOnBehalfOf(this.Mod, onBehalfOf, "load assets"), getData: _ => load() ) @@ -73,7 +73,7 @@ namespace StardewModdingAPI.Events /// Provide the initial instance for the asset from a file in your mod folder, instead of trying to load it from the game's Content folder. /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. /// The relative path to the file in your mod folder. - /// When there are multiple loads that apply to the same asset, this indicates whether this one can be skipped to resolve the conflict. If all loads allow skipping, the first one that was registered will be applied. If this is false, SMAPI will raise an error and apply none of them. + /// If there are multiple loads that apply to the same asset, the priority with which this one should be applied. /// /// Usage notes: /// @@ -81,12 +81,12 @@ namespace StardewModdingAPI.Events /// Each asset can logically only have one initial instance. If multiple loads apply at the same time, SMAPI will raise an error and ignore all of them. If you're making changes to the existing asset instead of replacing it, you should use instead to avoid those limitations and improve mod compatibility. /// /// - public void LoadFromModFile(string relativePath, bool allowSkipOnConflict = false) + public void LoadFromModFile(string relativePath, AssetLoadPriority priority) { this.LoadOperations.Add( new AssetLoadOperation( mod: this.Mod, - allowSkipOnConflict: allowSkipOnConflict, + priority: priority, onBehalfOf: null, _ => this.Mod.Mod.Helper.Content.Load(relativePath)) ); diff --git a/src/SMAPI/Framework/Content/AssetLoadOperation.cs b/src/SMAPI/Framework/Content/AssetLoadOperation.cs index 36baf1aa..b12958d6 100644 --- a/src/SMAPI/Framework/Content/AssetLoadOperation.cs +++ b/src/SMAPI/Framework/Content/AssetLoadOperation.cs @@ -1,4 +1,5 @@ using System; +using StardewModdingAPI.Events; namespace StardewModdingAPI.Framework.Content { @@ -14,8 +15,8 @@ namespace StardewModdingAPI.Framework.Content /// The content pack on whose behalf the asset is being loaded, if any. public IModMetadata OnBehalfOf { get; } - /// Whether to allow skipping this operation to resolve a load conflict. - public bool AllowSkipOnConflict { get; } + /// If there are multiple loads that apply to the same asset, the priority with which this one should be applied. + public AssetLoadPriority Priority { get; } /// Load the initial value for an asset. public Func GetData { get; } @@ -26,13 +27,13 @@ namespace StardewModdingAPI.Framework.Content *********/ /// Construct an instance. /// The mod applying the edit. - /// Whether to allow skipping this operation to resolve a load conflict. + /// If there are multiple loads that apply to the same asset, the priority with which this one should be applied. /// The content pack on whose behalf the asset is being loaded, if any. /// Load the initial value for an asset. - public AssetLoadOperation(IModMetadata mod, bool allowSkipOnConflict, IModMetadata onBehalfOf, Func getData) + public AssetLoadOperation(IModMetadata mod, AssetLoadPriority priority, IModMetadata onBehalfOf, Func getData) { this.Mod = mod; - this.AllowSkipOnConflict = allowSkipOnConflict; + this.Priority = priority; this.OnBehalfOf = onBehalfOf; this.GetData = getData; } diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 43cebcbe..144832b2 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -608,7 +608,7 @@ namespace StardewModdingAPI.Framework { new AssetLoadOperation( mod: loader.Mod, - allowSkipOnConflict: false, + priority: AssetLoadPriority.Exclusive, onBehalfOf: null, getData: assetInfo => loader.Data.Load(assetInfo) ) diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index b3e98648..16eddb00 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; @@ -275,7 +276,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // find matching loader AssetLoadOperation loader; { - AssetLoadOperation[] loaders = this.GetLoaders(info).ToArray(); + AssetLoadOperation[] loaders = this.GetLoaders(info).OrderByDescending(p => p.Priority).ToArray(); if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error)) { @@ -283,9 +284,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return null; } - loader = - loaders.FirstOrDefault(p => !p.AllowSkipOnConflict) - ?? loaders.FirstOrDefault(); + loader = loaders.FirstOrDefault(); } // no loader found @@ -396,7 +395,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Returns true if only one loader will apply, else false. private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, out string error) { - AssetLoadOperation[] required = loaders.Where(p => !p.AllowSkipOnConflict).ToArray(); + AssetLoadOperation[] required = loaders.Where(p => p.Priority == AssetLoadPriority.Exclusive).ToArray(); if (required.Length <= 1) { error = null; @@ -405,6 +404,7 @@ namespace StardewModdingAPI.Framework.ContentManagers string[] loaderNames = required .Select(p => p.Mod.DisplayName + this.GetOnBehalfOfLabel(p.OnBehalfOf)) + .OrderBy(p => p) .Distinct() .ToArray(); string errorPhrase = loaderNames.Length > 1 -- cgit