summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-01-30 22:10:16 -0500
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-01-30 22:10:16 -0500
commite5d8acf240f923a09bdaad3fb14b2c34847860dc (patch)
treec86ac2e42dfd4eb058493b6f2b9050c5c51a6c4f /src/SMAPI/Framework
parent9f36b2b3d69ee0a45241bfcc45953df29f167aef (diff)
downloadSMAPI-e5d8acf240f923a09bdaad3fb14b2c34847860dc.tar.gz
SMAPI-e5d8acf240f923a09bdaad3fb14b2c34847860dc.tar.bz2
SMAPI-e5d8acf240f923a09bdaad3fb14b2c34847860dc.zip
rework asset editor/loader tracking so they're affected by load order
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs4
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs36
-rw-r--r--src/SMAPI/Framework/ModLinked.cs29
-rw-r--r--src/SMAPI/Framework/SCore.cs26
4 files changed, 64 insertions, 31 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index b60483f1..2fd31263 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -65,10 +65,10 @@ namespace StardewModdingAPI.Framework
public LocalizedContentManager.LanguageCode Language => this.MainContentManager.Language;
/// <summary>Interceptors which provide the initial versions of matching assets.</summary>
- public IDictionary<IModMetadata, IList<IAssetLoader>> Loaders { get; } = new Dictionary<IModMetadata, IList<IAssetLoader>>();
+ public IList<ModLinked<IAssetLoader>> Loaders { get; } = new List<ModLinked<IAssetLoader>>();
/// <summary>Interceptors which edit matching assets after they're loaded.</summary>
- public IDictionary<IModMetadata, IList<IAssetEditor>> Editors { get; } = new Dictionary<IModMetadata, IList<IAssetEditor>>();
+ public IList<ModLinked<IAssetEditor>> Editors { get; } = new List<ModLinked<IAssetEditor>>();
/// <summary>The absolute path to the <see cref="ContentManager.RootDirectory"/>.</summary>
public string FullRootDirectory { get; }
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 8930267d..eecdda74 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -21,10 +21,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
private readonly ContextHash<string> AssetsBeingLoaded = new ContextHash<string>();
/// <summary>Interceptors which provide the initial versions of matching assets.</summary>
- private IDictionary<IModMetadata, IList<IAssetLoader>> Loaders => this.Coordinator.Loaders;
+ private IList<ModLinked<IAssetLoader>> Loaders => this.Coordinator.Loaders;
/// <summary>Interceptors which edit matching assets after they're loaded.</summary>
- private IDictionary<IModMetadata, IList<IAssetEditor>> Editors => this.Coordinator.Editors;
+ private IList<ModLinked<IAssetEditor>> Editors => this.Coordinator.Editors;
/// <summary>A lookup which indicates whether the asset is localizable (i.e. the filename contains the locale), if previously loaded.</summary>
private readonly IDictionary<string, bool> IsLocalizableLookup;
@@ -278,16 +278,16 @@ namespace StardewModdingAPI.Framework.ContentManagers
private IAssetData ApplyLoader<T>(IAssetInfo info)
{
// find matching loaders
- var loaders = this.GetInterceptors(this.Loaders)
+ var loaders = this.Loaders
.Where(entry =>
{
try
{
- return entry.Value.CanLoad<T>(info);
+ return entry.Data.CanLoad<T>(info);
}
catch (Exception ex)
{
- entry.Key.LogAsMod($"Mod failed when checking whether it could load asset '{info.AssetName}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ entry.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.AssetName}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
return false;
}
})
@@ -298,14 +298,14 @@ namespace StardewModdingAPI.Framework.ContentManagers
return null;
if (loaders.Length > 1)
{
- string[] loaderNames = loaders.Select(p => p.Key.DisplayName).ToArray();
+ string[] loaderNames = loaders.Select(p => p.Mod.DisplayName).ToArray();
this.Monitor.Log($"Multiple mods want to provide the '{info.AssetName}' asset ({string.Join(", ", loaderNames)}), but an asset can't be loaded multiple times. SMAPI will use the default asset instead; uninstall one of the mods to fix this. (Message for modders: you should usually use {typeof(IAssetEditor)} instead to avoid conflicts.)", LogLevel.Warn);
return null;
}
// fetch asset from loader
- IModMetadata mod = loaders[0].Key;
- IAssetLoader loader = loaders[0].Value;
+ IModMetadata mod = loaders[0].Mod;
+ IAssetLoader loader = loaders[0].Data;
T data;
try
{
@@ -338,11 +338,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName);
// edit asset
- foreach (var entry in this.GetInterceptors(this.Editors))
+ foreach (var entry in this.Editors)
{
// check for match
- IModMetadata mod = entry.Key;
- IAssetEditor editor = entry.Value;
+ IModMetadata mod = entry.Mod;
+ IAssetEditor editor = entry.Data;
try
{
if (!editor.CanEdit<T>(info))
@@ -382,19 +382,5 @@ namespace StardewModdingAPI.Framework.ContentManagers
// return result
return asset;
}
-
- /// <summary>Get all registered interceptors from a list.</summary>
- private IEnumerable<KeyValuePair<IModMetadata, T>> GetInterceptors<T>(IDictionary<IModMetadata, IList<T>> entries)
- {
- foreach (var entry in entries)
- {
- IModMetadata mod = entry.Key;
- IList<T> interceptors = entry.Value;
-
- // registered editors
- foreach (T interceptor in interceptors)
- yield return new KeyValuePair<IModMetadata, T>(mod, interceptor);
- }
- }
}
}
diff --git a/src/SMAPI/Framework/ModLinked.cs b/src/SMAPI/Framework/ModLinked.cs
new file mode 100644
index 00000000..8cfe6f5f
--- /dev/null
+++ b/src/SMAPI/Framework/ModLinked.cs
@@ -0,0 +1,29 @@
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>A generic tuple which links something to a mod.</summary>
+ /// <typeparam name="T">The interceptor type.</typeparam>
+ internal class ModLinked<T>
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The mod metadata.</summary>
+ public IModMetadata Mod { get; }
+
+ /// <summary>The instance linked to the mod.</summary>
+ public T Data { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="mod">The mod metadata.</param>
+ /// <param name="data">The instance linked to the mod.</param>
+ public ModLinked(IModMetadata mod, T data)
+ {
+ this.Mod = mod;
+ this.Data = data;
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 9139b371..7e1f8770 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -807,13 +807,13 @@ namespace StardewModdingAPI.Framework
{
// ReSharper disable SuspiciousTypeConversion.Global
if (metadata.Mod is IAssetEditor editor)
- helper.ObservableAssetEditors.Add(editor);
+ this.ContentCore.Editors.Add(new ModLinked<IAssetEditor>(metadata, editor));
if (metadata.Mod is IAssetLoader loader)
- helper.ObservableAssetLoaders.Add(loader);
+ this.ContentCore.Loaders.Add(new ModLinked<IAssetLoader>(metadata, loader));
// ReSharper restore SuspiciousTypeConversion.Global
- this.ContentCore.Editors[metadata] = helper.ObservableAssetEditors;
- this.ContentCore.Loaders[metadata] = helper.ObservableAssetLoaders;
+ helper.ObservableAssetEditors.CollectionChanged += (sender, e) => this.OnInterceptorsChanged(metadata, e.NewItems?.Cast<IAssetEditor>(), e.OldItems?.Cast<IAssetEditor>(), this.ContentCore.Editors);
+ helper.ObservableAssetLoaders.CollectionChanged += (sender, e) => this.OnInterceptorsChanged(metadata, e.NewItems?.Cast<IAssetLoader>(), e.OldItems?.Cast<IAssetLoader>(), this.ContentCore.Loaders);
}
// call entry method
@@ -862,6 +862,24 @@ namespace StardewModdingAPI.Framework
this.ModRegistry.AreAllModsInitialized = true;
}
+ /// <summary>Handle a mod adding or removing asset interceptors.</summary>
+ /// <typeparam name="T">The asset interceptor type (one of <see cref="IAssetEditor"/> or <see cref="IAssetLoader"/>).</typeparam>
+ /// <param name="mod">The mod metadata.</param>
+ /// <param name="added">The interceptors that were added.</param>
+ /// <param name="removed">The interceptors that were removed.</param>
+ /// <param name="list">The list to update.</param>
+ private void OnInterceptorsChanged<T>(IModMetadata mod, IEnumerable<T> added, IEnumerable<T> removed, IList<ModLinked<T>> list)
+ {
+ foreach (T interceptor in added ?? new T[0])
+ list.Add(new ModLinked<T>(mod, interceptor));
+
+ foreach (T interceptor in removed ?? new T[0])
+ {
+ foreach (ModLinked<T> entry in list.Where(p => p.Mod == mod && object.ReferenceEquals(p.Data, interceptor)).ToArray())
+ list.Remove(entry);
+ }
+ }
+
/// <summary>Load a given mod.</summary>
/// <param name="mod">The mod to load.</param>
/// <param name="mods">The mods being loaded.</param>