diff --git a/src/SMAPI/Events/AssetReadyEventArgs.cs b/src/SMAPI/Events/AssetReadyEventArgs.cs
new file mode 100644
index 00000000..946c9173
--- /dev/null
+++ b/src/SMAPI/Events/AssetReadyEventArgs.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace StardewModdingAPI.Events
+{
+ /// Event arguments for an event.
+ public class AssetReadyEventArgs : EventArgs
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// The name of the asset being requested.
+ public IAssetName Name { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ /// The name of the asset being requested.
+ internal AssetReadyEventArgs(IAssetName name)
+ {
+ this.Name = name;
+ }
+ }
+}
diff --git a/src/SMAPI/Events/IContentEvents.cs b/src/SMAPI/Events/IContentEvents.cs
index ede9ea23..abbaaf33 100644
--- a/src/SMAPI/Events/IContentEvents.cs
+++ b/src/SMAPI/Events/IContentEvents.cs
@@ -1,5 +1,4 @@
using System;
-using StardewValley;
namespace StardewModdingAPI.Events
{
@@ -8,7 +7,7 @@ namespace StardewModdingAPI.Events
{
/// Raised when an asset is being requested from the content pipeline.
///
- /// The asset isn't necessarily being loaded yet (e.g. the game may be checking if it exists). Mods can register the changes they want to apply using methods on the parameter. These will be applied when the asset is actually loaded.
+ /// The asset isn't necessarily being loaded yet (e.g. the game may be checking if it exists). Mods can register the changes they want to apply using methods on the event arguments. These will be applied when the asset is actually loaded.
///
/// If the asset is requested multiple times in the same tick (e.g. once to check if it exists and once to load it), SMAPI might only raise the event once and reuse the cached result.
///
@@ -16,5 +15,9 @@ namespace StardewModdingAPI.Events
/// Raised after one or more assets were invalidated from the content cache by a mod, so they'll be reloaded next time they're requested. If the assets will be reloaded or propagated automatically, this event is raised before that happens.
event EventHandler AssetsInvalidated;
+
+ /// Raised after an asset is loaded by the content pipeline, after all mod edits specified via have been applied.
+ /// This event is only raised if something requested the asset from the content pipeline. Invalidating an asset from the content cache won't necessarily reload it automatically.
+ event EventHandler AssetReady;
}
}
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 4f696928..4dbbae15 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -49,6 +49,9 @@ namespace StardewModdingAPI.Framework
/// A callback to invoke the first time *any* game content manager loads an asset.
private readonly Action OnLoadingFirstAsset;
+ /// A callback to invoke when an asset is fully loaded.
+ private readonly Action OnAssetLoaded;
+
/// A callback to invoke when any asset names have been invalidated from the cache.
private readonly Action> OnAssetsInvalidated;
@@ -111,16 +114,18 @@ namespace StardewModdingAPI.Framework
/// Simplifies access to private code.
/// Encapsulates SMAPI's JSON file parsing.
/// A callback to invoke the first time *any* game content manager loads an asset.
+ /// A callback to invoke when an asset is fully loaded.
/// Whether to enable more aggressive memory optimizations.
/// A callback to invoke when any asset names have been invalidated from the cache.
/// Get the load/edit operations to apply to an asset by querying registered event handlers.
- public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations, Action> onAssetsInvalidated, Func> requestAssetOperations)
+ public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action onAssetLoaded, bool aggressiveMemoryOptimizations, Action> onAssetsInvalidated, Func> requestAssetOperations)
{
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
this.Reflection = reflection;
this.JsonHelper = jsonHelper;
this.OnLoadingFirstAsset = onLoadingFirstAsset;
+ this.OnAssetLoaded = onAssetLoaded;
this.OnAssetsInvalidated = onAssetsInvalidated;
this.RequestAssetOperations = requestAssetOperations;
this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory);
@@ -135,6 +140,7 @@ namespace StardewModdingAPI.Framework
reflection: reflection,
onDisposing: this.OnDisposing,
onLoadingFirstAsset: onLoadingFirstAsset,
+ onAssetLoaded: onAssetLoaded,
aggressiveMemoryOptimizations: aggressiveMemoryOptimizations
)
);
@@ -148,6 +154,7 @@ namespace StardewModdingAPI.Framework
reflection: reflection,
onDisposing: this.OnDisposing,
onLoadingFirstAsset: onLoadingFirstAsset,
+ onAssetLoaded: onAssetLoaded,
aggressiveMemoryOptimizations: aggressiveMemoryOptimizations
);
this.ContentManagers.Add(contentManagerForAssetPropagation);
@@ -172,6 +179,7 @@ namespace StardewModdingAPI.Framework
reflection: this.Reflection,
onDisposing: this.OnDisposing,
onLoadingFirstAsset: this.OnLoadingFirstAsset,
+ onAssetLoaded: this.OnAssetLoaded,
aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations
);
this.ContentManagers.Add(manager);
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index 642e526c..12ed5506 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -35,6 +35,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// A callback to invoke the first time *any* game content manager loads an asset.
private readonly Action OnLoadingFirstAsset;
+ /// A callback to invoke when an asset is fully loaded.
+ private readonly Action OnAssetLoaded;
+
/*********
** Public methods
@@ -49,11 +52,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// Simplifies access to private code.
/// A callback to invoke when the content manager is being disposed.
/// A callback to invoke the first time *any* game content manager loads an asset.
+ /// A callback to invoke when an asset is fully loaded.
/// Whether to enable more aggressive memory optimizations.
- public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations)
+ public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, Action onAssetLoaded, bool aggressiveMemoryOptimizations)
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: false, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations)
{
this.OnLoadingFirstAsset = onLoadingFirstAsset;
+ this.OnAssetLoaded = onAssetLoaded;
}
///
@@ -129,8 +134,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
});
}
- // update cache & return data
+ // update cache
this.TrackAsset(assetName, data, language, useCache);
+
+ // raise event & return data
+ this.OnAssetLoaded(this, assetName);
return data;
}
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs
index 206ece30..847e2ce1 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs
@@ -21,8 +21,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
** Public methods
*********/
///
- public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations)
- : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, aggressiveMemoryOptimizations) { }
+ public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, Action onAssetLoaded, bool aggressiveMemoryOptimizations)
+ : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, onAssetLoaded, aggressiveMemoryOptimizations) { }
///
public override T Load(IAssetName assetName, LanguageCode language, bool useCache)
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index 96582380..bcfd7dd7 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -1,10 +1,8 @@
-using System.Diagnostics.CodeAnalysis;
using StardewModdingAPI.Events;
namespace StardewModdingAPI.Framework.Events
{
/// Manages SMAPI events.
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Private fields are deliberately named to simplify organisation.")]
internal class EventManager
{
/*********
@@ -19,6 +17,9 @@ namespace StardewModdingAPI.Framework.Events
///
public readonly ManagedEvent AssetsInvalidated;
+ ///
+ public readonly ManagedEvent AssetReady;
+
/****
** Display
@@ -202,6 +203,7 @@ namespace StardewModdingAPI.Framework.Events
// init events
this.AssetRequested = ManageEventOf(nameof(IModEvents.Content), nameof(IContentEvents.AssetRequested));
this.AssetsInvalidated = ManageEventOf(nameof(IModEvents.Content), nameof(IContentEvents.AssetsInvalidated));
+ this.AssetReady = ManageEventOf(nameof(IModEvents.Content), nameof(IContentEvents.AssetReady));
this.MenuChanged = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged));
this.Rendering = ManageEventOf(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering), isPerformanceCritical: true);
diff --git a/src/SMAPI/Framework/Events/ModContentEvents.cs b/src/SMAPI/Framework/Events/ModContentEvents.cs
index 4d0cfb97..cb242e99 100644
--- a/src/SMAPI/Framework/Events/ModContentEvents.cs
+++ b/src/SMAPI/Framework/Events/ModContentEvents.cs
@@ -23,6 +23,13 @@ namespace StardewModdingAPI.Framework.Events
remove => this.EventManager.AssetsInvalidated.Remove(value);
}
+ ///
+ public event EventHandler AssetReady
+ {
+ add => this.EventManager.AssetReady.Add(value, this.Mod);
+ remove => this.EventManager.AssetReady.Remove(value);
+ }
+
/*********
** Public methods
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index e9bc9a2b..9d97ec7d 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -19,6 +19,7 @@ using Newtonsoft.Json;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Content;
+using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Input;
@@ -1106,6 +1107,15 @@ namespace StardewModdingAPI.Framework
this.EventManager.DayEnding.RaiseEmpty();
}
+ /// A callback invoked after an asset is fully loaded through a content manager.
+ /// The content manager through which the asset was loaded.
+ /// The asset name that was loaded.
+ private void OnAssetLoaded(IContentManager contentManager, IAssetName assetName)
+ {
+ if (this.EventManager.AssetReady.HasListeners())
+ this.EventManager.AssetReady.Raise(new AssetReadyEventArgs(assetName));
+ }
+
/// A callback invoked after assets have been invalidated from the content cache.
/// The invalidated asset names.
private void OnAssetsInvalidated(IEnumerable assetNames)
@@ -1183,6 +1193,7 @@ namespace StardewModdingAPI.Framework
reflection: this.Reflection,
jsonHelper: this.Toolkit.JsonHelper,
onLoadingFirstAsset: this.InitializeBeforeFirstAssetLoaded,
+ onAssetLoaded: this.OnAssetLoaded,
onAssetsInvalidated: this.OnAssetsInvalidated,
aggressiveMemoryOptimizations: this.Settings.AggressiveMemoryOptimizations,
requestAssetOperations: this.RequestAssetOperations