summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-11 21:36:45 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-11 21:36:45 -0400
commitbbe5983acdd082d2185a69e2ad37d659a298223d (patch)
treead6fe68ebd0da25a832e9fad3832741dee50954f /src/SMAPI/Framework/ContentManagers/GameContentManager.cs
parent42a797a01240893e9a8e645253a269087b2d178d (diff)
downloadSMAPI-bbe5983acdd082d2185a69e2ad37d659a298223d.tar.gz
SMAPI-bbe5983acdd082d2185a69e2ad37d659a298223d.tar.bz2
SMAPI-bbe5983acdd082d2185a69e2ad37d659a298223d.zip
rewrite asset operations to reduce allocations
• When raising AssetRequested, SMAPI now creates a single event args model and reuses it for each handler. • There's now a single AssetOperationGroup per asset, which tracks the loaders/editors registered by every mod for that asset. • The operation group's loader/editor lists are now used directly instead of querying them.
Diffstat (limited to 'src/SMAPI/Framework/ContentManagers/GameContentManager.cs')
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs71
1 files changed, 28 insertions, 43 deletions
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index c53040e1..2aa50542 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -75,15 +75,19 @@ namespace StardewModdingAPI.Framework.ContentManagers
// custom asset from a loader
string locale = this.GetLocale();
IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
- AssetLoadOperation[] loaders = this.GetLoaders<object>(info).ToArray();
-
- if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error))
+ AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<object>(info);
+ if (operations?.LoadOperations.Count > 0)
{
- this.Monitor.Log(error, LogLevel.Warn);
- return false;
+ if (!this.AssertMaxOneRequiredLoader(info, operations.LoadOperations, out string? error))
+ {
+ this.Monitor.Log(error, LogLevel.Warn);
+ return false;
+ }
+
+ return true;
}
- return loaders.Any();
+ return false;
}
/// <inheritdoc />
@@ -121,10 +125,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
data = this.AssetsBeingLoaded.Track(assetName.Name, () =>
{
IAssetInfo info = new AssetInfo(assetName.LocaleCode, assetName, typeof(T), this.AssertAndNormalizeAssetName);
+ AssetOperationGroup? operations = this.Coordinator.GetAssetOperations<T>(info);
IAssetData asset =
- this.ApplyLoader<T>(info)
+ this.ApplyLoader<T>(info, operations?.LoadOperations)
?? new AssetDataForObject(info, this.RawLoad<T>(assetName, useCache), this.AssertAndNormalizeAssetName, this.Reflection);
- asset = this.ApplyEditors<T>(info, asset);
+ asset = this.ApplyEditors<T>(info, asset, operations?.EditOperations);
return (T)asset.Data;
});
}
@@ -149,25 +154,23 @@ namespace StardewModdingAPI.Framework.ContentManagers
*********/
/// <summary>Load the initial asset from the registered loaders.</summary>
/// <param name="info">The basic asset metadata.</param>
+ /// <param name="loadOperations">The load operations to apply to the asset.</param>
/// <returns>Returns the loaded asset metadata, or <c>null</c> if no loader matched.</returns>
- private IAssetData? ApplyLoader<T>(IAssetInfo info)
+ private IAssetData? ApplyLoader<T>(IAssetInfo info, List<AssetLoadOperation>? loadOperations)
where T : notnull
{
// find matching loader
- AssetLoadOperation? loader;
+ AssetLoadOperation? loader = null;
+ if (loadOperations?.Count > 0)
{
- AssetLoadOperation[] loaders = this.GetLoaders<T>(info).OrderByDescending(p => p.Priority).ToArray();
-
- if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error))
+ if (!this.AssertMaxOneRequiredLoader(info, loadOperations, out string? error))
{
this.Monitor.Log(error, LogLevel.Warn);
return null;
}
- loader = loaders.FirstOrDefault();
+ loader = loadOperations.OrderByDescending(p => p.Priority).FirstOrDefault();
}
-
- // no loader found
if (loader == null)
return null;
@@ -195,9 +198,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <typeparam name="T">The asset type.</typeparam>
/// <param name="info">The basic asset metadata.</param>
/// <param name="asset">The loaded asset.</param>
- private IAssetData ApplyEditors<T>(IAssetInfo info, IAssetData asset)
+ /// <param name="editOperations">The edit operations to apply to the asset.</param>
+ private IAssetData ApplyEditors<T>(IAssetInfo info, IAssetData asset, List<AssetEditOperation>? editOperations)
where T : notnull
{
+ if (editOperations?.Count is not > 0)
+ return asset;
+
IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection);
// special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead.
@@ -210,12 +217,12 @@ namespace StardewModdingAPI.Framework.ContentManagers
return (IAssetData)this.GetType()
.GetMethod(nameof(this.ApplyEditors), BindingFlags.NonPublic | BindingFlags.Instance)!
.MakeGenericMethod(actualType)
- .Invoke(this, new object[] { info, asset })!;
+ .Invoke(this, new object[] { info, asset, editOperations })!;
}
}
// edit asset
- AssetEditOperation[] editors = this.GetEditors<T>(info).OrderBy(p => p.Priority).ToArray();
+ AssetEditOperation[] editors = editOperations.OrderBy(p => p.Priority).ToArray();
foreach (AssetEditOperation editor in editors)
{
IModMetadata mod = editor.Mod;
@@ -250,34 +257,12 @@ namespace StardewModdingAPI.Framework.ContentManagers
return asset;
}
- /// <summary>Get the asset loaders which handle an asset.</summary>
- /// <typeparam name="T">The asset type.</typeparam>
- /// <param name="info">The basic asset metadata.</param>
- private IEnumerable<AssetLoadOperation> GetLoaders<T>(IAssetInfo info)
- where T : notnull
- {
- return this.Coordinator
- .GetAssetOperations<T>(info)
- .SelectMany(p => p.LoadOperations);
- }
-
- /// <summary>Get the asset editors to apply to an asset.</summary>
- /// <typeparam name="T">The asset type.</typeparam>
- /// <param name="info">The basic asset metadata.</param>
- private IEnumerable<AssetEditOperation> GetEditors<T>(IAssetInfo info)
- where T : notnull
- {
- return this.Coordinator
- .GetAssetOperations<T>(info)
- .SelectMany(p => p.EditOperations);
- }
-
/// <summary>Assert that at most one loader will be applied to an asset.</summary>
/// <param name="info">The basic asset metadata.</param>
/// <param name="loaders">The asset loaders to apply.</param>
/// <param name="error">The error message to show to the user, if the method returns false.</param>
/// <returns>Returns true if only one loader will apply, else false.</returns>
- private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, [NotNullWhen(false)] out string? error)
+ private bool AssertMaxOneRequiredLoader(IAssetInfo info, List<AssetLoadOperation> loaders, [NotNullWhen(false)] out string? error)
{
AssetLoadOperation[] required = loaders.Where(p => p.Priority == AssetLoadPriority.Exclusive).ToArray();
if (required.Length <= 1)
@@ -295,7 +280,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
? $"Multiple mods want to provide the '{info.Name}' asset: {string.Join(", ", loaderNames)}"
: $"The '{loaderNames[0]}' mod wants to provide the '{info.Name}' asset multiple times";
- error = $"{errorPhrase}. An asset can't be loaded multiple times, so 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.)";
+ error = $"{errorPhrase}. An asset can't be loaded multiple times, so SMAPI will use the default asset instead. Uninstall one of the mods to fix this. (Message for modders: you should avoid {nameof(AssetLoadPriority)}.{nameof(AssetLoadPriority.Exclusive)} and {nameof(IAssetLoader)} if possible to avoid conflicts.)";
return false;
}