add AssetReady content event (#766)

This commit is contained in:
Jesse Plamondon-Willard 2022-03-25 00:35:31 -04:00
parent 2b0ce2bb4d
commit b77eab6e0a
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
8 changed files with 73 additions and 9 deletions

View File

@ -0,0 +1,25 @@
using System;
namespace StardewModdingAPI.Events
{
/// <summary>Event arguments for an <see cref="IContentEvents.AssetReady"/> event.</summary>
public class AssetReadyEventArgs : EventArgs
{
/*********
** Accessors
*********/
/// <summary>The name of the asset being requested.</summary>
public IAssetName Name { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="name">The name of the asset being requested.</param>
internal AssetReadyEventArgs(IAssetName name)
{
this.Name = name;
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using StardewValley;
namespace StardewModdingAPI.Events
{
@ -8,7 +7,7 @@ namespace StardewModdingAPI.Events
{
/// <summary>Raised when an asset is being requested from the content pipeline.</summary>
/// <remarks>
/// 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 <paramref name="e"/> 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.
/// </remarks>
@ -16,5 +15,9 @@ namespace StardewModdingAPI.Events
/// <summary>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.</summary>
event EventHandler<AssetsInvalidatedEventArgs> AssetsInvalidated;
/// <summary>Raised after an asset is loaded by the content pipeline, after all mod edits specified via <see cref="AssetRequested"/> have been applied.</summary>
/// <remarks>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.</remarks>
event EventHandler<AssetReadyEventArgs> AssetReady;
}
}

View File

@ -49,6 +49,9 @@ namespace StardewModdingAPI.Framework
/// <summary>A callback to invoke the first time *any* game content manager loads an asset.</summary>
private readonly Action OnLoadingFirstAsset;
/// <summary>A callback to invoke when an asset is fully loaded.</summary>
private readonly Action<BaseContentManager, IAssetName> OnAssetLoaded;
/// <summary>A callback to invoke when any asset names have been invalidated from the cache.</summary>
private readonly Action<IEnumerable<IAssetName>> OnAssetsInvalidated;
@ -111,16 +114,18 @@ namespace StardewModdingAPI.Framework
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param>
/// <param name="onAssetLoaded">A callback to invoke when an asset is fully loaded.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
/// <param name="onAssetsInvalidated">A callback to invoke when any asset names have been invalidated from the cache.</param>
/// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param>
public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations, Action<IEnumerable<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, IList<AssetOperationGroup>> requestAssetOperations)
public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, bool aggressiveMemoryOptimizations, Action<IEnumerable<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, IList<AssetOperationGroup>> 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);

View File

@ -35,6 +35,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>A callback to invoke the first time *any* game content manager loads an asset.</summary>
private readonly Action OnLoadingFirstAsset;
/// <summary>A callback to invoke when an asset is fully loaded.</summary>
private readonly Action<BaseContentManager, IAssetName> OnAssetLoaded;
/*********
** Public methods
@ -49,11 +52,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
/// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param>
/// <param name="onAssetLoaded">A callback to invoke when an asset is fully loaded.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations)
public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, bool aggressiveMemoryOptimizations)
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: false, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations)
{
this.OnLoadingFirstAsset = onLoadingFirstAsset;
this.OnAssetLoaded = onAssetLoaded;
}
/// <inheritdoc />
@ -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;
}

View File

@ -21,8 +21,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
** Public methods
*********/
/// <inheritdoc />
public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> 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<BaseContentManager> onDisposing, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, bool aggressiveMemoryOptimizations)
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, onAssetLoaded, aggressiveMemoryOptimizations) { }
/// <inheritdoc />
public override T Load<T>(IAssetName assetName, LanguageCode language, bool useCache)

View File

@ -1,10 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using StardewModdingAPI.Events;
namespace StardewModdingAPI.Framework.Events
{
/// <summary>Manages SMAPI events.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Private fields are deliberately named to simplify organisation.")]
internal class EventManager
{
/*********
@ -19,6 +17,9 @@ namespace StardewModdingAPI.Framework.Events
/// <inheritdoc cref="IContentEvents.AssetsInvalidated" />
public readonly ManagedEvent<AssetsInvalidatedEventArgs> AssetsInvalidated;
/// <inheritdoc cref="IContentEvents.AssetReady" />
public readonly ManagedEvent<AssetReadyEventArgs> AssetReady;
/****
** Display
@ -202,6 +203,7 @@ namespace StardewModdingAPI.Framework.Events
// init events
this.AssetRequested = ManageEventOf<AssetRequestedEventArgs>(nameof(IModEvents.Content), nameof(IContentEvents.AssetRequested));
this.AssetsInvalidated = ManageEventOf<AssetsInvalidatedEventArgs>(nameof(IModEvents.Content), nameof(IContentEvents.AssetsInvalidated));
this.AssetReady = ManageEventOf<AssetReadyEventArgs>(nameof(IModEvents.Content), nameof(IContentEvents.AssetReady));
this.MenuChanged = ManageEventOf<MenuChangedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged));
this.Rendering = ManageEventOf<RenderingEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering), isPerformanceCritical: true);

View File

@ -23,6 +23,13 @@ namespace StardewModdingAPI.Framework.Events
remove => this.EventManager.AssetsInvalidated.Remove(value);
}
/// <inheritdoc />
public event EventHandler<AssetReadyEventArgs> AssetReady
{
add => this.EventManager.AssetReady.Add(value, this.Mod);
remove => this.EventManager.AssetReady.Remove(value);
}
/*********
** Public methods

View File

@ -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();
}
/// <summary>A callback invoked after an asset is fully loaded through a content manager.</summary>
/// <param name="contentManager">The content manager through which the asset was loaded.</param>
/// <param name="assetName">The asset name that was loaded.</param>
private void OnAssetLoaded(IContentManager contentManager, IAssetName assetName)
{
if (this.EventManager.AssetReady.HasListeners())
this.EventManager.AssetReady.Raise(new AssetReadyEventArgs(assetName));
}
/// <summary>A callback invoked after assets have been invalidated from the content cache.</summary>
/// <param name="assetNames">The invalidated asset names.</param>
private void OnAssetsInvalidated(IEnumerable<IAssetName> 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