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 --- src/StardewModdingAPI/Program.cs | 50 ++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 27 deletions(-) (limited to 'src/StardewModdingAPI/Program.cs') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 6e57fd65..57ec1a17 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI /// Manages console output interception. private readonly ConsoleInterceptionManager ConsoleManager = new ConsoleInterceptionManager(); - /// The core logger for SMAPI. + /// The core logger and monitor for SMAPI. private readonly Monitor Monitor; /// Tracks whether the game should exit immediately and any pending initialisation should be cancelled. @@ -99,7 +99,7 @@ namespace StardewModdingAPI public Program(bool writeToConsole, string logPath) { this.LogFile = new LogFileManager(logPath); - this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = writeToConsole }; + this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.CancellationTokenSource) { WriteToConsole = writeToConsole }; } /// Launch SMAPI. @@ -142,6 +142,17 @@ namespace StardewModdingAPI this.GameInstance = new SGame(this.Monitor); StardewValley.Program.gamePtr = this.GameInstance; + // add exit handler + new Thread(() => + { + this.CancellationTokenSource.Token.WaitHandle.WaitOne(); + if (this.IsGameRunning) + { + this.GameInstance.Exiting += (sender, e) => this.PressAnyKeyToExit(); + this.GameInstance.Exit(); + } + }).Start(); + // hook into game events #if SMAPI_FOR_WINDOWS ((Form)Control.FromHandle(this.GameInstance.Window.Handle)).FormClosing += (sender, args) => this.Dispose(); @@ -180,20 +191,6 @@ namespace StardewModdingAPI } } - /// Immediately exit the game without saving. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs. - /// The module which requested an immediate exit. - /// The reason provided for the shutdown. - public void ExitGameImmediately(string module, string reason) - { - this.Monitor.LogFatal($"{module} requested an immediate game shutdown: {reason}"); - this.CancellationTokenSource.Cancel(); - if (this.IsGameRunning) - { - this.GameInstance.Exiting += (sender, e) => this.PressAnyKeyToExit(); - this.GameInstance.Exit(); - } - } - /// Get a monitor for legacy code which doesn't have one passed in. [Obsolete("This method should only be used when needed for backwards compatibility.")] internal IMonitor GetLegacyMonitorForMod() @@ -264,9 +261,9 @@ namespace StardewModdingAPI // load mods int modsLoaded = this.LoadMods(); - if (this.CancellationTokenSource.IsCancellationRequested) + if (this.Monitor.IsExiting) { - this.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); + this.Monitor.Log("SMAPI shutting down: aborting initialisation.", LogLevel.Warn); return; } @@ -307,7 +304,7 @@ namespace StardewModdingAPI inputThread.Start(); // keep console thread alive while the game is running - while (this.IsGameRunning) + while (this.IsGameRunning && !this.Monitor.IsExiting) Thread.Sleep(1000 / 10); if (inputThread.ThreadState == ThreadState.Running) inputThread.Abort(); @@ -368,18 +365,17 @@ namespace StardewModdingAPI List deprecationWarnings = new List(); // queue up deprecation warnings to show after mod list foreach (string directoryPath in Directory.GetDirectories(Constants.ModPath)) { + if (this.Monitor.IsExiting) + { + this.Monitor.Log("SMAPI shutting down: aborting mod scan.", LogLevel.Warn); + return modsLoaded; + } + // passthrough empty directories DirectoryInfo directory = new DirectoryInfo(directoryPath); while (!directory.GetFiles().Any() && directory.GetDirectories().Length == 1) directory = directory.GetDirectories().First(); - // check for cancellation - if (this.CancellationTokenSource.IsCancellationRequested) - { - this.Monitor.Log("Shutdown requested; interrupting mod loading.", LogLevel.Error); - return modsLoaded; - } - // get manifest path string manifestPath = Path.Combine(directory.FullName, "manifest.json"); if (!File.Exists(manifestPath)) @@ -625,7 +621,7 @@ namespace StardewModdingAPI /// The name of the module which will log messages with this instance. private Monitor GetSecondaryMonitor(string name) { - return new Monitor(name, this.ConsoleManager, this.LogFile, this.ExitGameImmediately) { WriteToConsole = this.Monitor.WriteToConsole, ShowTraceInConsole = this.Settings.DeveloperMode }; + return new Monitor(name, this.ConsoleManager, this.LogFile, this.CancellationTokenSource) { WriteToConsole = this.Monitor.WriteToConsole, ShowTraceInConsole = this.Settings.DeveloperMode }; } /// Get a human-readable name for the current platform. -- cgit