From 2b0ce2bb4d6690b7d00da0a243855db9bffffbf0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 24 Mar 2022 22:55:55 -0400 Subject: add AssetInvalidated content event (#766) --- src/SMAPI/Events/AssetsInvalidatedEventArgs.cs | 27 +++++++++++++++++++++++ src/SMAPI/Events/IContentEvents.cs | 3 +++ src/SMAPI/Framework/ContentCoordinator.cs | 30 ++++++++++++++++---------- src/SMAPI/Framework/Events/EventManager.cs | 4 ++++ src/SMAPI/Framework/Events/ModContentEvents.cs | 7 ++++++ src/SMAPI/Framework/SCore.cs | 9 ++++++++ 6 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 src/SMAPI/Events/AssetsInvalidatedEventArgs.cs (limited to 'src/SMAPI') diff --git a/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs b/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs new file mode 100644 index 00000000..0127f83a --- /dev/null +++ b/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for an event. + public class AssetsInvalidatedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The asset names that were invalidated. + public IEnumerable Names { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The asset names that were invalidated. + internal AssetsInvalidatedEventArgs(IEnumerable names) + { + this.Names = names.ToArray(); + } + } +} diff --git a/src/SMAPI/Events/IContentEvents.cs b/src/SMAPI/Events/IContentEvents.cs index feaf9c0a..ede9ea23 100644 --- a/src/SMAPI/Events/IContentEvents.cs +++ b/src/SMAPI/Events/IContentEvents.cs @@ -13,5 +13,8 @@ namespace StardewModdingAPI.Events /// 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. /// event EventHandler AssetRequested; + + /// 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; } } diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 22ae0a18..4f696928 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -49,6 +49,12 @@ 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 any asset names have been invalidated from the cache. + private readonly Action> OnAssetsInvalidated; + + /// Get the load/edit operations to apply to an asset by querying registered event handlers. + private readonly Func> RequestAssetOperations; + /// The loaded content managers (including the ). private readonly List ContentManagers = new(); @@ -71,9 +77,6 @@ namespace StardewModdingAPI.Framework /// The language enum values indexed by locale code. private Lazy> LocaleCodes; - /// Get the load/edit operations to apply to an asset by querying registered event handlers. - private readonly Func> RequestAssetOperations; - /// The cached asset load/edit operations to apply, indexed by asset name. private readonly TickCacheDictionary AssetOperationsByKey = new(); @@ -109,14 +112,16 @@ namespace StardewModdingAPI.Framework /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke the first time *any* game content manager loads an asset. /// 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, Func> requestAssetOperations) + public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, 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.OnAssetsInvalidated = onAssetsInvalidated; this.RequestAssetOperations = requestAssetOperations; this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory); this.ContentManagers.Add( @@ -257,7 +262,7 @@ namespace StardewModdingAPI.Framework // Note that we *must* propagate changes here, otherwise when mods invalidate the cache later to reapply // their changes, the assets won't be found in the cache so no changes will be propagated. if (LocalizedContentManager.CurrentLanguageCode != LocalizedContentManager.LanguageCode.en) - this.InvalidateCache((contentManager, key, type) => contentManager is GameContentManager); + this.InvalidateCache((contentManager, _, _) => contentManager is GameContentManager); } /// Parse a raw asset name. @@ -347,7 +352,7 @@ namespace StardewModdingAPI.Framework public IEnumerable InvalidateCache(Func predicate, bool dispose = false) { string locale = this.GetLocale(); - return this.InvalidateCache((contentManager, rawName, type) => + return this.InvalidateCache((_, rawName, type) => { IAssetName assetName = this.ParseAssetName(rawName); IAssetInfo info = new AssetInfo(locale, assetName, type, this.MainContentManager.AssertAndNormalizeAssetName); @@ -393,13 +398,16 @@ namespace StardewModdingAPI.Framework } }); - // clear cached editor checks - foreach (IAssetName name in invalidatedAssets.Keys) - this.AssetOperationsByKey.Remove(name); - - // reload core game assets + // handle invalidation if (invalidatedAssets.Any()) { + // clear cached editor checks + foreach (IAssetName name in invalidatedAssets.Keys) + this.AssetOperationsByKey.Remove(name); + + // raise event + this.OnAssetsInvalidated(invalidatedAssets.Keys); + // propagate changes to the game this.CoreAssets.Propagate( assets: invalidatedAssets.ToDictionary(p => p.Key, p => p.Value), diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index 8142f00e..96582380 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -16,6 +16,9 @@ namespace StardewModdingAPI.Framework.Events /// public readonly ManagedEvent AssetRequested; + /// + public readonly ManagedEvent AssetsInvalidated; + /**** ** Display @@ -198,6 +201,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.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 b4d4279c..4d0cfb97 100644 --- a/src/SMAPI/Framework/Events/ModContentEvents.cs +++ b/src/SMAPI/Framework/Events/ModContentEvents.cs @@ -16,6 +16,13 @@ namespace StardewModdingAPI.Framework.Events remove => this.EventManager.AssetRequested.Remove(value); } + /// + public event EventHandler AssetsInvalidated + { + add => this.EventManager.AssetsInvalidated.Add(value, this.Mod); + remove => this.EventManager.AssetsInvalidated.Remove(value); + } + /********* ** Public methods diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index f9f84206..e9bc9a2b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1106,6 +1106,14 @@ namespace StardewModdingAPI.Framework this.EventManager.DayEnding.RaiseEmpty(); } + /// A callback invoked after assets have been invalidated from the content cache. + /// The invalidated asset names. + private void OnAssetsInvalidated(IEnumerable assetNames) + { + if (this.EventManager.AssetsInvalidated.HasListeners()) + this.EventManager.AssetsInvalidated.Raise(new AssetsInvalidatedEventArgs(assetNames)); + } + /// Get the load/edit operations to apply to an asset by querying registered event handlers. /// The asset info being requested. private IList RequestAssetOperations(IAssetInfo asset) @@ -1175,6 +1183,7 @@ namespace StardewModdingAPI.Framework reflection: this.Reflection, jsonHelper: this.Toolkit.JsonHelper, onLoadingFirstAsset: this.InitializeBeforeFirstAssetLoaded, + onAssetsInvalidated: this.OnAssetsInvalidated, aggressiveMemoryOptimizations: this.Settings.AggressiveMemoryOptimizations, requestAssetOperations: this.RequestAssetOperations ); -- cgit