From 19bb255c988f161ce00586cb516de830732efe31 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 16 Nov 2016 17:36:28 -0500 Subject: add emergency interrupt feature (#168) --- src/StardewModdingAPI/Framework/Monitor.cs | 16 +++++++++++ src/StardewModdingAPI/IMonitor.cs | 4 +++ src/StardewModdingAPI/Program.cs | 44 +++++++++++++++++++++++++----- 3 files changed, 57 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/Monitor.cs b/src/StardewModdingAPI/Framework/Monitor.cs index 4fe1df04..cf46a474 100644 --- a/src/StardewModdingAPI/Framework/Monitor.cs +++ b/src/StardewModdingAPI/Framework/Monitor.cs @@ -65,6 +65,22 @@ namespace StardewModdingAPI.Framework this.LogImpl(this.Source, message, Monitor.Colors[level], level); } + /// 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 reason for the shutdown. + public void ExitGameImmediately(string reason) + { + Program.ExitGameImmediately(this.Source, reason); + Program.gamePtr.Exit(); + } + + /// Log a fatal error message. + /// The message to log. + internal void LogFatal(string message) + { + Console.BackgroundColor = ConsoleColor.Red; + this.LogImpl(this.Source, message, ConsoleColor.White, LogLevel.Error); + } + /// Log a message for the player or developer, using the specified console color. /// The name of the mod logging the message. /// The message to log. diff --git a/src/StardewModdingAPI/IMonitor.cs b/src/StardewModdingAPI/IMonitor.cs index 8d20af01..571c7403 100644 --- a/src/StardewModdingAPI/IMonitor.cs +++ b/src/StardewModdingAPI/IMonitor.cs @@ -10,5 +10,9 @@ /// The message to log. /// The log severity level. void Log(string message, LogLevel level = LogLevel.Debug); + + /// 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 reason for the shutdown. + void ExitGameImmediately(string reason); } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index ab3d8f5c..962a30f8 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -42,6 +42,9 @@ namespace StardewModdingAPI /// Whether SMAPI is running in developer mode. private static bool DeveloperMode; + /// Tracks whether the game should exit immediately and any pending initialisation should be cancelled. + private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); + /********* ** Accessors @@ -127,7 +130,7 @@ namespace StardewModdingAPI if (!File.Exists(Program.GameExecutablePath)) { Program.Monitor.Log($"Couldn't find executable: {Program.GameExecutablePath}", LogLevel.Error); - Console.ReadKey(); + Program.PressAnyKeyToExit(); return; } @@ -139,10 +142,23 @@ namespace StardewModdingAPI } catch (Exception ex) { - Console.WriteLine(ex); - Console.ReadKey(); Program.Monitor.Log($"Critical error: {ex}", LogLevel.Error); } + Program.PressAnyKeyToExit(); + } + + /// 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. + internal static void ExitGameImmediately(string module, string reason) + { + Program.Monitor.LogFatal($"{module} requested an immediate game shutdown: {reason}"); + Program.CancellationTokenSource.Cancel(); + if (Program.ready) + { + Program.gamePtr.Exiting += (sender, e) => Program.PressAnyKeyToExit(); + Program.gamePtr.Exit(); + } } @@ -199,6 +215,11 @@ namespace StardewModdingAPI // load mods Program.LoadMods(); + if (Program.CancellationTokenSource.IsCancellationRequested) + { + Program.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); + return; + } // initialise console after game launches new Thread(() => @@ -223,11 +244,15 @@ namespace StardewModdingAPI // abort the console thread, we're closing if (consoleInputThread.ThreadState == ThreadState.Running) consoleInputThread.Abort(); - Program.PressAnyKeyToExit(); }).Start(); // start game loop Program.Monitor.Log("Starting game..."); + if (Program.CancellationTokenSource.IsCancellationRequested) + { + Program.Monitor.Log("Shutdown requested; interrupting initialisation.", LogLevel.Error); + return; + } try { Program.ready = true; @@ -241,8 +266,6 @@ namespace StardewModdingAPI catch (Exception ex) { Program.Monitor.Log($"SMAPI encountered a fatal error:\n{ex}", LogLevel.Error); - Program.PressAnyKeyToExit(); - return; } } @@ -269,6 +292,13 @@ namespace StardewModdingAPI { foreach (string manifestPath in Directory.GetFiles(directory, "manifest.json")) { + // check for cancellation + if (Program.CancellationTokenSource.IsCancellationRequested) + { + Program.Monitor.Log("Shutdown requested; interrupting mod loading.", LogLevel.Error); + return; + } + ModHelper helper = new ModHelper(directory); string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; @@ -426,8 +456,8 @@ namespace StardewModdingAPI private static void PressAnyKeyToExit() { Program.Monitor.Log("Game has ended. Press any key to exit.", LogLevel.Info); - Console.ReadKey(); Thread.Sleep(100); + Console.ReadKey(); Environment.Exit(0); } } -- cgit