From 0cf15d36d9e6f5a40a16e17f3bd3d53682fe648c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 26 Apr 2017 18:25:59 -0400 Subject: revamp 'exit immediately' to abort ongoing SMAPI tasks --- .../Framework/InternalExtensions.cs | 8 ++++++ src/StardewModdingAPI/Framework/Monitor.cs | 31 +++++++++++++--------- src/StardewModdingAPI/Framework/SGame.cs | 7 +++++ 3 files changed, 33 insertions(+), 13 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/InternalExtensions.cs b/src/StardewModdingAPI/Framework/InternalExtensions.cs index a2d589ff..2f6f7490 100644 --- a/src/StardewModdingAPI/Framework/InternalExtensions.cs +++ b/src/StardewModdingAPI/Framework/InternalExtensions.cs @@ -41,6 +41,14 @@ namespace StardewModdingAPI.Framework foreach (EventHandler handler in handlers.Cast()) { + // handle SMAPI exiting + if (monitor.IsExiting) + { + monitor.Log($"SMAPI shutting down: aborting {name} event.", LogLevel.Warn); + return; + } + + // raise event try { handler.Invoke(sender, args ?? EventArgs.Empty); diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs index dac81f14..925efc33 100644 --- a/src/StardewModdingAPI/Framework/Monitor.cs +++ b/src/StardewModdingAPI/Framework/Monitor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using StardewModdingAPI.Framework.Logging; namespace StardewModdingAPI.Framework @@ -34,13 +35,16 @@ namespace StardewModdingAPI.Framework [LogLevel.Alert] = ConsoleColor.Magenta }; - /// A delegate which requests that SMAPI immediately exit the game. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs. - private readonly RequestExitDelegate RequestExit; + /// Propagates notification that SMAPI should exit. + private readonly CancellationTokenSource ExitTokenSource; /********* ** Accessors *********/ + /// Whether SMAPI is aborting. Mods don't need to worry about this unless they have background tasks. + public bool IsExiting => this.ExitTokenSource.IsCancellationRequested; + /// Whether to show trace messages in the console. internal bool ShowTraceInConsole { get; set; } @@ -58,8 +62,8 @@ namespace StardewModdingAPI.Framework /// The name of the module which logs messages using this instance. /// Manages access to the console output. /// The log file to which to write messages. - /// A delegate which requests that SMAPI immediately exit the game. - public Monitor(string source, ConsoleInterceptionManager consoleManager, LogFileManager logFile, RequestExitDelegate requestExitDelegate) + /// Propagates notification that SMAPI should exit. + public Monitor(string source, ConsoleInterceptionManager consoleManager, LogFileManager logFile, CancellationTokenSource exitTokenSource) { // validate if (string.IsNullOrWhiteSpace(source)) @@ -69,7 +73,7 @@ namespace StardewModdingAPI.Framework this.Source = source; this.LogFile = logFile ?? throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null."); this.ConsoleManager = consoleManager; - this.RequestExit = requestExitDelegate; + this.ExitTokenSource = exitTokenSource; } /// Log a message for the player or developer. @@ -84,14 +88,8 @@ namespace StardewModdingAPI.Framework /// The reason for the shutdown. public void ExitGameImmediately(string reason) { - this.RequestExit(this.Source, reason); - } - - /// Log a fatal error message. - /// The message to log. - internal void LogFatal(string message) - { - this.LogImpl(this.Source, message, LogLevel.Error, ConsoleColor.White, background: ConsoleColor.Red); + this.LogFatal($"{this.Source} requested an immediate game shutdown: {reason}"); + this.ExitTokenSource.Cancel(); } /// Log a message for the player or developer, using the specified console color. @@ -109,6 +107,13 @@ namespace StardewModdingAPI.Framework /********* ** Private methods *********/ + /// Log a fatal error message. + /// The message to log. + private void LogFatal(string message) + { + this.LogImpl(this.Source, message, LogLevel.Error, ConsoleColor.White, background: ConsoleColor.Red); + } + /// Write a message line to the log. /// The name of the mod logging the message. /// The message to log. diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 61493e87..e7c07889 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -200,6 +200,13 @@ namespace StardewModdingAPI.Framework /// A snapshot of the game timing state. protected override void Update(GameTime gameTime) { + // SMAPI exiting, stop processing game updates + if (this.Monitor.IsExiting) + { + this.Monitor.Log("SMAPI shutting down: aborting update.", LogLevel.Trace); + return; + } + // While a background new-day task is in progress, the game skips its own update logic // and defers to the XNA Update method. Running mod code in parallel to the background // update is risky, because data changes can conflict (e.g. collection changed during -- cgit