diff options
-rw-r--r-- | src/SMAPI/Framework/ContentCoordinator.cs | 28 | ||||
-rw-r--r-- | src/SMAPI/Framework/SContentManager.cs | 31 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 2 |
3 files changed, 55 insertions, 6 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 86ebc5c3..397a9d90 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -30,6 +30,9 @@ namespace StardewModdingAPI.Framework /// <summary>The loaded content managers (including the <see cref="MainContentManager"/>).</summary> private readonly IList<SContentManager> ContentManagers = new List<SContentManager>(); + /// <summary>Whether the content coordinator has been disposed.</summary> + private bool IsDisposed; + /********* ** Accessors @@ -65,7 +68,7 @@ namespace StardewModdingAPI.Framework this.Reflection = reflection; this.FullRootDirectory = Path.Combine(Constants.ExecutionPath, rootDirectory); this.ContentManagers.Add( - this.MainContentManager = new SContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, isModFolder: false) + this.MainContentManager = new SContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, this.OnDisposing, isModFolder: false) ); this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.NormaliseAssetName, reflection); } @@ -76,7 +79,9 @@ namespace StardewModdingAPI.Framework /// <param name="rootDirectory">The root directory to search for content (or <c>null</c>. for the default)</param> public SContentManager CreateContentManager(string name, bool isModFolder, string rootDirectory = null) { - return new SContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory ?? this.MainContentManager.RootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, isModFolder); + SContentManager manager = new SContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory ?? this.MainContentManager.RootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.OnDisposing, isModFolder); + this.ContentManagers.Add(manager); + return manager; } /// <summary>Get the current content locale.</summary> @@ -162,8 +167,9 @@ namespace StardewModdingAPI.Framework /// <summary>Dispose held resources.</summary> public void Dispose() { - if (this.MainContentManager == null) - return; // already disposed + if (this.IsDisposed) + return; + this.IsDisposed = true; this.Monitor.Log("Disposing the content coordinator. Content managers will no longer be usable after this point.", LogLevel.Trace); foreach (SContentManager contentManager in this.ContentManagers) @@ -171,5 +177,19 @@ namespace StardewModdingAPI.Framework this.ContentManagers.Clear(); this.MainContentManager = null; } + + + /********* + ** Private methods + *********/ + /// <summary>A callback invoked when a content manager is disposed.</summary> + /// <param name="contentManager">The content manager being disposed.</param> + private void OnDisposing(SContentManager contentManager) + { + if (this.IsDisposed) + return; + + this.ContentManagers.Remove(contentManager); + } } } diff --git a/src/SMAPI/Framework/SContentManager.cs b/src/SMAPI/Framework/SContentManager.cs index 8f008041..e9f46acb 100644 --- a/src/SMAPI/Framework/SContentManager.cs +++ b/src/SMAPI/Framework/SContentManager.cs @@ -43,12 +43,18 @@ namespace StardewModdingAPI.Framework /// <summary>The path prefix for assets in mod folders.</summary> private readonly string ModContentPrefix; + /// <summary>A callback to invoke when the content manager is being disposed.</summary> + private readonly Action<SContentManager> OnDisposing; + /// <summary>Interceptors which provide the initial versions of matching assets.</summary> private IDictionary<IModMetadata, IList<IAssetLoader>> Loaders => this.Coordinator.Loaders; /// <summary>Interceptors which edit matching assets after they're loaded.</summary> private IDictionary<IModMetadata, IList<IAssetEditor>> Editors => this.Coordinator.Editors; + /// <summary>Whether the content coordinator has been disposed.</summary> + private bool IsDisposed; + /********* ** Accessors @@ -78,7 +84,8 @@ namespace StardewModdingAPI.Framework /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="reflection">Simplifies access to private code.</param> /// <param name="isModFolder">Whether this content manager is wrapped around a mod folder.</param> - public SContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, bool isModFolder) + /// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param> + public SContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<SContentManager> onDisposing, bool isModFolder) : base(serviceProvider, rootDirectory, currentCulture) { // init @@ -88,6 +95,7 @@ namespace StardewModdingAPI.Framework this.Cache = new ContentCache(this, reflection); this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.ModContentPrefix = this.GetAssetNameFromFilePath(Constants.ModPath, ContentSource.GameContent); + this.OnDisposing = onDisposing; // get asset data this.LanguageCodes = this.GetKeyLocales().ToDictionary(p => p.Value, p => p.Key, StringComparer.InvariantCultureIgnoreCase); @@ -291,6 +299,27 @@ namespace StardewModdingAPI.Framework return removeAssetNames; } + /// <summary>Dispose held resources.</summary> + /// <param name="isDisposing">Whether the content manager is being disposed (rather than finalized).</param> + protected override void Dispose(bool isDisposing) + { + if (this.IsDisposed) + return; + this.IsDisposed = true; + + this.OnDisposing(this); + base.Dispose(isDisposing); + } + + /// <inheritdoc /> + public override void Unload() + { + if (this.IsDisposed) + return; // base logic doesn't allow unloading twice, which happens due to SMAPI and the game both unloading + + base.Unload(); + } + /********* ** Private methods diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 48a70688..8a4f987b 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -209,7 +209,7 @@ namespace StardewModdingAPI.Framework if (this.NextContentManagerIsMain) { this.NextContentManagerIsMain = false; - return this.ContentCore.CreateContentManager("Game1.content", isModFolder: false, rootDirectory: rootDirectory); + return this.ContentCore.MainContentManager; } // any other content manager |