summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs7
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs123
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs120
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs20
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs129
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentHelper.cs4
6 files changed, 229 insertions, 174 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 0a91ccd5..2dfe1f1c 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -176,16 +176,15 @@ namespace StardewModdingAPI.Framework
/// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param>
/// <param name="relativePath">The internal SMAPI asset key.</param>
/// <param name="language">The language code for which to load content.</param>
- public T LoadAndCloneManagedAsset<T>(string internalKey, string contentManagerID, string relativePath, LocalizedContentManager.LanguageCode language)
+ public T LoadManagedAsset<T>(string internalKey, string contentManagerID, string relativePath, LocalizedContentManager.LanguageCode language)
{
// get content manager
IContentManager contentManager = this.ContentManagers.FirstOrDefault(p => p.IsNamespaced && p.Name == contentManagerID);
if (contentManager == null)
throw new InvalidOperationException($"The '{contentManagerID}' prefix isn't handled by any mod.");
- // get cloned asset
- T data = contentManager.Load<T>(internalKey, language);
- return contentManager.CloneIfPossible(data);
+ // get fresh asset
+ return contentManager.Load<T>(relativePath, language, useCache: false);
}
/// <summary>Purge assets from the cache that match one of the interceptors.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 3f7c2cee..fc558eb9 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -6,7 +6,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.Xna.Framework.Content;
-using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
@@ -38,6 +37,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>The language enum values indexed by locale code.</summary>
protected IDictionary<string, LanguageCode> LanguageCodes { get; }
+ /// <summary>A list of disposable assets.</summary>
+ private readonly List<WeakReference<IDisposable>> Disposables = new List<WeakReference<IDisposable>>();
+
/*********
** Accessors
@@ -88,54 +90,32 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
public override T Load<T>(string assetName)
{
- return this.Load<T>(assetName, LocalizedContentManager.CurrentLanguageCode);
+ return this.Load<T>(assetName, this.Language, useCache: true);
}
- /// <summary>Load the base asset without localisation.</summary>
+ /// <summary>Load an asset that has been processed by the content pipeline.</summary>
/// <typeparam name="T">The type of asset to load.</typeparam>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
- public override T LoadBase<T>(string assetName)
+ /// <param name="language">The language code for which to load content.</param>
+ public override T Load<T>(string assetName, LanguageCode language)
{
- return this.Load<T>(assetName, LanguageCode.en);
+ return this.Load<T>(assetName, language, useCache: true);
}
- /// <summary>Inject an asset into the cache.</summary>
- /// <typeparam name="T">The type of asset to inject.</typeparam>
+ /// <summary>Load an asset that has been processed by the content pipeline.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
- /// <param name="value">The asset value.</param>
- /// <param name="language">The language code for which to inject the asset.</param>
- public virtual void Inject<T>(string assetName, T value, LanguageCode language)
- {
- assetName = this.AssertAndNormaliseAssetName(assetName);
- this.Cache[assetName] = value;
- }
+ /// <param name="language">The language code for which to load content.</param>
+ /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
+ public abstract T Load<T>(string assetName, LocalizedContentManager.LanguageCode language, bool useCache);
- /// <summary>Get a copy of the given asset if supported.</summary>
- /// <typeparam name="T">The asset type.</typeparam>
- /// <param name="asset">The asset to clone.</param>
- public T CloneIfPossible<T>(T asset)
+ /// <summary>Load the base asset without localisation.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ [Obsolete("This method is implemented for the base game and should not be used directly. To load an asset from the underlying content manager directly, use " + nameof(BaseContentManager.RawLoad) + " instead.")]
+ public override T LoadBase<T>(string assetName)
{
- switch (asset as object)
- {
- case Texture2D source:
- {
- int[] pixels = new int[source.Width * source.Height];
- source.GetData(pixels);
-
- Texture2D clone = new Texture2D(source.GraphicsDevice, source.Width, source.Height);
- clone.SetData(pixels);
- return (T)(object)clone;
- }
-
- case Dictionary<string, string> source:
- return (T)(object)new Dictionary<string, string>(source);
-
- case Dictionary<int, string> source:
- return (T)(object)new Dictionary<int, string>(source);
-
- default:
- return asset;
- }
+ return this.Load<T>(assetName, LanguageCode.en, useCache: true);
}
/// <summary>Perform any cleanup needed when the locale changes.</summary>
@@ -228,11 +208,28 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="isDisposing">Whether the content manager is being disposed (rather than finalized).</param>
protected override void Dispose(bool isDisposing)
{
+ // ignore if disposed
if (this.IsDisposed)
return;
this.IsDisposed = true;
+ // dispose uncached assets
+ foreach (WeakReference<IDisposable> reference in this.Disposables)
+ {
+ if (reference.TryGetTarget(out IDisposable disposable))
+ {
+ try
+ {
+ disposable.Dispose();
+ }
+ catch { /* ignore dispose errors */ }
+ }
+ }
+ this.Disposables.Clear();
+
+ // raise event
this.OnDisposing(this);
+
base.Dispose(isDisposing);
}
@@ -249,23 +246,26 @@ namespace StardewModdingAPI.Framework.ContentManagers
/*********
** Private methods
*********/
- /// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary>
- private IDictionary<LanguageCode, string> GetKeyLocales()
+ /// <summary>Load an asset file directly from the underlying content manager.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The normalised asset key.</param>
+ /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
+ protected virtual T RawLoad<T>(string assetName, bool useCache)
{
- // create locale => code map
- IDictionary<LanguageCode, string> map = new Dictionary<LanguageCode, string>();
- foreach (LanguageCode code in Enum.GetValues(typeof(LanguageCode)))
- map[code] = this.GetLocale(code);
-
- return map;
+ return useCache
+ ? base.LoadBase<T>(assetName)
+ : base.ReadAsset<T>(assetName, disposable => this.Disposables.Add(new WeakReference<IDisposable>(disposable)));
}
- /// <summary>Get the asset name from a cache key.</summary>
- /// <param name="cacheKey">The input cache key.</param>
- private string GetAssetName(string cacheKey)
+ /// <summary>Inject an asset into the cache.</summary>
+ /// <typeparam name="T">The type of asset to inject.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ /// <param name="value">The asset value.</param>
+ /// <param name="language">The language code for which to inject the asset.</param>
+ protected virtual void Inject<T>(string assetName, T value, LanguageCode language)
{
- this.ParseCacheKey(cacheKey, out string assetName, out string _);
- return assetName;
+ assetName = this.AssertAndNormaliseAssetName(assetName);
+ this.Cache[assetName] = value;
}
/// <summary>Parse a cache key into its component parts.</summary>
@@ -298,5 +298,24 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Get whether an asset has already been loaded.</summary>
/// <param name="normalisedAssetName">The normalised asset name.</param>
protected abstract bool IsNormalisedKeyLoaded(string normalisedAssetName);
+
+ /// <summary>Get the locale codes (like <c>ja-JP</c>) used in asset keys.</summary>
+ private IDictionary<LanguageCode, string> GetKeyLocales()
+ {
+ // create locale => code map
+ IDictionary<LanguageCode, string> map = new Dictionary<LanguageCode, string>();
+ foreach (LanguageCode code in Enum.GetValues(typeof(LanguageCode)))
+ map[code] = this.GetLocale(code);
+
+ return map;
+ }
+
+ /// <summary>Get the asset name from a cache key.</summary>
+ /// <param name="cacheKey">The input cache key.</param>
+ private string GetAssetName(string cacheKey)
+ {
+ this.ParseCacheKey(cacheKey, out string assetName, out string _);
+ return assetName;
+ }
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 90278c36..ecabcaca 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using Microsoft.Xna.Framework.Content;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
@@ -59,7 +60,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <typeparam name="T">The type of asset to load.</typeparam>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
/// <param name="language">The language code for which to load content.</param>
- public override T Load<T>(string assetName, LanguageCode language)
+ /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
+ public override T Load<T>(string assetName, LocalizedContentManager.LanguageCode language, bool useCache)
{
// raise first-load callback
if (GameContentManager.IsFirstLoad)
@@ -71,17 +73,18 @@ namespace StardewModdingAPI.Framework.ContentManagers
// normalise asset name
assetName = this.AssertAndNormaliseAssetName(assetName);
if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage))
- return this.Load<T>(newAssetName, newLanguage);
+ return this.Load<T>(newAssetName, newLanguage, useCache);
// get from cache
- if (this.IsLoaded(assetName))
- return base.Load<T>(assetName, language);
+ if (useCache && this.IsLoaded(assetName))
+ return this.RawLoad<T>(assetName, language, useCache: true);
// get managed asset
if (this.Coordinator.TryParseManagedAssetKey(assetName, out string contentManagerID, out string relativePath))
{
- T managedAsset = this.Coordinator.LoadAndCloneManagedAsset<T>(assetName, contentManagerID, relativePath, language);
- this.Inject(assetName, managedAsset, language);
+ T managedAsset = this.Coordinator.LoadManagedAsset<T>(assetName, contentManagerID, relativePath, language);
+ if (useCache)
+ this.Inject(assetName, managedAsset, language);
return managedAsset;
}
@@ -91,7 +94,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn);
this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}", LogLevel.Trace);
- data = base.Load<T>(assetName, language);
+ data = this.RawLoad<T>(assetName, language, useCache);
}
else
{
@@ -101,7 +104,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormaliseAssetName);
IAssetData asset =
this.ApplyLoader<T>(info)
- ?? new AssetDataForObject(info, base.Load<T>(assetName, language), this.AssertAndNormaliseAssetName);
+ ?? new AssetDataForObject(info, this.RawLoad<T>(assetName, language, useCache), this.AssertAndNormaliseAssetName);
asset = this.ApplyEditors<T>(info, asset);
return (T)asset.Data;
});
@@ -112,39 +115,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
return data;
}
- /// <summary>Inject an asset into the cache.</summary>
- /// <typeparam name="T">The type of asset to inject.</typeparam>
- /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
- /// <param name="value">The asset value.</param>
- /// <param name="language">The language code for which to inject the asset.</param>
- public override void Inject<T>(string assetName, T value, LanguageCode language)
- {
- // handle explicit language in asset name
- {
- if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage))
- {
- this.Inject(newAssetName, value, newLanguage);
- return;
- }
- }
- base.Inject(assetName, value, language);
-
- // track whether the injected asset is translatable for is-loaded lookups
- string keyWithLocale = $"{assetName}.{this.GetLocale(language)}";
- if (this.Cache.ContainsKey(keyWithLocale))
- {
- this.IsLocalisableLookup[assetName] = true;
- this.IsLocalisableLookup[keyWithLocale] = true;
- }
- else if (this.Cache.ContainsKey(assetName))
- {
- this.IsLocalisableLookup[assetName] = false;
- this.IsLocalisableLookup[keyWithLocale] = false;
- }
- else
- this.Monitor.Log($"Asset '{assetName}' could not be found in the cache immediately after injection.", LogLevel.Error);
- }
-
/// <summary>Perform any cleanup needed when the locale changes.</summary>
public override void OnLocaleChanged()
{
@@ -199,6 +169,72 @@ namespace StardewModdingAPI.Framework.ContentManagers
return false;
}
+ /// <summary>Inject an asset into the cache.</summary>
+ /// <typeparam name="T">The type of asset to inject.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ /// <param name="value">The asset value.</param>
+ /// <param name="language">The language code for which to inject the asset.</param>
+ protected override void Inject<T>(string assetName, T value, LanguageCode language)
+ {
+ // handle explicit language in asset name
+ {
+ if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage))
+ {
+ this.Inject(newAssetName, value, newLanguage);
+ return;
+ }
+ }
+ base.Inject(assetName, value, language);
+
+ // track whether the injected asset is translatable for is-loaded lookups
+ string keyWithLocale = $"{assetName}.{this.GetLocale(language)}";
+ if (this.Cache.ContainsKey(keyWithLocale))
+ {
+ this.IsLocalisableLookup[assetName] = true;
+ this.IsLocalisableLookup[keyWithLocale] = true;
+ }
+ else if (this.Cache.ContainsKey(assetName))
+ {
+ this.IsLocalisableLookup[assetName] = false;
+ this.IsLocalisableLookup[keyWithLocale] = false;
+ }
+ else
+ this.Monitor.Log($"Asset '{assetName}' could not be found in the cache immediately after injection.", LogLevel.Error);
+ }
+
+ /// <summary>Load an asset file directly from the underlying content manager.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The normalised asset key.</param>
+ /// <param name="language">The language code for which to load content.</param>
+ /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
+ /// <remarks>Derived from <see cref="LocalizedContentManager.Load{T}(string, LocalizedContentManager.LanguageCode)"/>.</remarks>
+ private T RawLoad<T>(string assetName, LanguageCode language, bool useCache)
+ {
+ // try translated asset
+ if (language != LocalizedContentManager.LanguageCode.en)
+ {
+ string translatedKey = $"{assetName}.{this.GetLocale(language)}";
+ if (!this.IsLocalisableLookup.TryGetValue(translatedKey, out bool isTranslatable) || isTranslatable)
+ {
+ try
+ {
+ T obj = base.RawLoad<T>(translatedKey, useCache);
+ this.IsLocalisableLookup[assetName] = true;
+ this.IsLocalisableLookup[translatedKey] = true;
+ return obj;
+ }
+ catch (ContentLoadException)
+ {
+ this.IsLocalisableLookup[assetName] = false;
+ this.IsLocalisableLookup[translatedKey] = false;
+ }
+ }
+ }
+
+ // try base asset
+ return base.RawLoad<T>(assetName, useCache);
+ }
+
/// <summary>Parse an asset key that contains an explicit language into its asset name and language, if applicable.</summary>
/// <param name="rawAsset">The asset key to parse.</param>
/// <param name="assetName">The asset name without the language code.</param>
@@ -260,7 +296,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
T data;
try
{
- data = this.CloneIfPossible(loader.Load<T>(info));
+ data = loader.Load<T>(info);
this.Monitor.Log($"{mod.DisplayName} loaded asset '{info.AssetName}'.", LogLevel.Trace);
}
catch (Exception ex)
diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
index 2365789b..78211821 100644
--- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
@@ -32,25 +32,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Load an asset that has been processed by the content pipeline.</summary>
/// <typeparam name="T">The type of asset to load.</typeparam>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
- T Load<T>(string assetName);
-
- /// <summary>Load an asset that has been processed by the content pipeline.</summary>
- /// <typeparam name="T">The type of asset to load.</typeparam>
- /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
/// <param name="language">The language code for which to load content.</param>
- T Load<T>(string assetName, LocalizedContentManager.LanguageCode language);
-
- /// <summary>Inject an asset into the cache.</summary>
- /// <typeparam name="T">The type of asset to inject.</typeparam>
- /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
- /// <param name="value">The asset value.</param>
- /// <param name="language">The language code for which to inject the asset.</param>
- void Inject<T>(string assetName, T value, LocalizedContentManager.LanguageCode language);
-
- /// <summary>Get a copy of the given asset if supported.</summary>
- /// <typeparam name="T">The asset type.</typeparam>
- /// <param name="asset">The asset to clone.</param>
- T CloneIfPossible<T>(T asset);
+ /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
+ T Load<T>(string assetName, LocalizedContentManager.LanguageCode language, bool useCache);
/// <summary>Perform any cleanup needed when the locale changes.</summary>
void OnLocaleChanged();
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 064a7907..1d015138 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -29,6 +29,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>The game content manager used for map tilesheets not provided by the mod.</summary>
private readonly IContentManager GameContentManager;
+ /// <summary>The language code for language-agnostic mod assets.</summary>
+ private const LanguageCode NoLanguage = LanguageCode.en;
+
/*********
** Public methods
@@ -54,66 +57,58 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Load an asset that has been processed by the content pipeline.</summary>
/// <typeparam name="T">The type of asset to load.</typeparam>
/// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
- /// <param name="language">The language code for which to load content.</param>
- public override T Load<T>(string assetName, LanguageCode language)
+ public override T Load<T>(string assetName)
{
- assetName = this.AssertAndNormaliseAssetName(assetName);
-
- // resolve managed asset key
- if (this.Coordinator.TryParseManagedAssetKey(assetName, out string contentManagerID, out string relativePath))
- {
- if (contentManagerID != this.Name)
- throw new SContentLoadException($"Can't load managed asset key '{assetName}' through content manager '{this.Name}' for a different mod.");
- assetName = relativePath;
- }
-
- // get local asset
- string internalKey = this.GetInternalAssetKey(assetName);
- if (this.IsLoaded(internalKey))
- return base.Load<T>(internalKey, language);
- return this.LoadImpl<T>(internalKey, this.Name, assetName, this.Language);
+ return this.Load<T>(assetName, ModContentManager.NoLanguage, useCache: false);
}
- /// <summary>Create a new content manager for temporary use.</summary>
- public override LocalizedContentManager CreateTemporary()
+ /// <summary>Load an asset that has been processed by the content pipeline.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ /// <param name="language">The language code for which to load content.</param>
+ public override T Load<T>(string assetName, LanguageCode language)
{
- throw new NotSupportedException("Can't create a temporary mod content manager.");
+ return this.Load<T>(assetName, language, useCache: false);
}
- /// <summary>Get the underlying key in the game's content cache for an asset. This does not validate whether the asset exists.</summary>
- /// <param name="key">The local path to a content file relative to the mod folder.</param>
- /// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
- public string GetInternalAssetKey(string key)
+ /// <summary>Load an asset that has been processed by the content pipeline.</summary>
+ /// <typeparam name="T">The type of asset to load.</typeparam>
+ /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param>
+ /// <param name="language">The language code for which to load content.</param>
+ /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param>
+ public override T Load<T>(string assetName, LanguageCode language, bool useCache)
{
- FileInfo file = this.GetModFile(key);
- string relativePath = PathUtilities.GetRelativePath(this.RootDirectory, file.FullName);
- return Path.Combine(this.Name, relativePath);
- }
+ assetName = this.AssertAndNormaliseAssetName(assetName);
+ // disable caching
+ // This is necessary to avoid assets being shared between content managers, which can
+ // cause changes to an asset through one content manager affecting the same asset in
+ // others (or even fresh content managers). See https://www.patreon.com/posts/27247161
+ // for more background info.
+ if (useCache)
+ throw new InvalidOperationException("Mod content managers don't support asset caching.");
- /*********
- ** Private methods
- *********/
- /// <summary>Get whether an asset has already been loaded.</summary>
- /// <param name="normalisedAssetName">The normalised asset name.</param>
- protected override bool IsNormalisedKeyLoaded(string normalisedAssetName)
- {
- return this.Cache.ContainsKey(normalisedAssetName);
- }
+ // disable language handling
+ // Mod files don't support automatic translation logic, so this should never happen.
+ if (language != ModContentManager.NoLanguage)
+ throw new InvalidOperationException("Caching is not supported by the mod content manager.");
- /// <summary>Load a local mod asset.</summary>
- /// <typeparam name="T">The type of asset to load.</typeparam>
- /// <param name="cacheKey">The mod asset cache key to save.</param>
- /// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param>
- /// <param name="relativePath">The relative path within the mod folder.</param>
- /// <param name="language">The language code for which to load content.</param>
- private T LoadImpl<T>(string cacheKey, string contentManagerID, string relativePath, LanguageCode language)
- {
- SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading asset '{relativePath}' from {contentManagerID}: {reasonPhrase}");
+ // resolve managed asset key
+ {
+ if (this.Coordinator.TryParseManagedAssetKey(assetName, out string contentManagerID, out string relativePath))
+ {
+ if (contentManagerID != this.Name)
+ throw new SContentLoadException($"Can't load managed asset key '{assetName}' through content manager '{this.Name}' for a different mod.");
+ assetName = relativePath;
+ }
+ }
+
+ // get local asset
+ SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}");
try
{
// get file
- FileInfo file = this.GetModFile(relativePath);
+ FileInfo file = this.GetModFile(assetName);
if (!file.Exists)
throw GetContentError("the specified path doesn't exist.");
@@ -122,14 +117,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
// XNB file
case ".xnb":
- return base.Load<T>(relativePath, language);
+ return this.RawLoad<T>(assetName, useCache: false);
// unpacked data
case ".json":
{
if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T data))
throw GetContentError("the JSON file is invalid."); // should never happen since we check for file existence above
-
return data;
}
@@ -144,7 +138,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
texture = this.PremultiplyTransparency(texture);
- this.Inject(cacheKey, texture, language);
return (T)(object)texture;
}
@@ -157,10 +150,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// fetch & cache
FormatManager formatManager = FormatManager.Instance;
Map map = formatManager.LoadMap(file.FullName);
- this.FixCustomTilesheetPaths(map, relativeMapPath: relativePath);
-
- // inject map
- this.Inject(cacheKey, map, this.Language);
+ this.FixCustomTilesheetPaths(map, relativeMapPath: assetName);
return (T)(object)map;
default:
@@ -171,10 +161,37 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
if (ex.GetInnermostException() is DllNotFoundException dllEx && dllEx.Message == "libgdiplus.dylib")
throw GetContentError("couldn't find libgdiplus, which is needed to load mod images. Make sure Mono is installed and you're running the game through the normal launcher.");
- throw new SContentLoadException($"The content manager failed loading content asset '{relativePath}' from {contentManagerID}.", ex);
+ throw new SContentLoadException($"The content manager failed loading content asset '{assetName}' from {this.Name}.", ex);
}
}
+ /// <summary>Create a new content manager for temporary use.</summary>
+ public override LocalizedContentManager CreateTemporary()
+ {
+ throw new NotSupportedException("Can't create a temporary mod content manager.");
+ }
+
+ /// <summary>Get the underlying key in the game's content cache for an asset. This does not validate whether the asset exists.</summary>
+ /// <param name="key">The local path to a content file relative to the mod folder.</param>
+ /// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
+ public string GetInternalAssetKey(string key)
+ {
+ FileInfo file = this.GetModFile(key);
+ string relativePath = PathUtilities.GetRelativePath(this.RootDirectory, file.FullName);
+ return Path.Combine(this.Name, relativePath);
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get whether an asset has already been loaded.</summary>
+ /// <param name="normalisedAssetName">The normalised asset name.</param>
+ protected override bool IsNormalisedKeyLoaded(string normalisedAssetName)
+ {
+ return this.Cache.ContainsKey(normalisedAssetName);
+ }
+
/// <summary>Get a file from the mod folder.</summary>
/// <param name="path">The asset path relative to the content folder.</param>
private FileInfo GetModFile(string path)
@@ -318,7 +335,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
try
{
- this.GameContentManager.Load<Texture2D>(contentKey);
+ this.GameContentManager.Load<Texture2D>(contentKey, this.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset
return contentKey;
}
catch
diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
index 2d65f1f6..0be9aea5 100644
--- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
@@ -91,10 +91,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
switch (source)
{
case ContentSource.GameContent:
- return this.GameContentManager.Load<T>(key);
+ return this.GameContentManager.Load<T>(key, this.CurrentLocaleConstant, useCache: false);
case ContentSource.ModFolder:
- return this.ModContentManager.Load<T>(key);
+ return this.ModContentManager.Load<T>(key, this.CurrentLocaleConstant, useCache: false);
default:
throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: unknown content source '{source}'.");