summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md4
-rw-r--r--src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs59
-rw-r--r--src/SMAPI/Framework/Logging/InterceptingTextWriter.cs55
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs15
-rw-r--r--src/SMAPI/Framework/Monitor.cs19
5 files changed, 56 insertions, 96 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 2282bc3d..a65db68c 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -9,11 +9,12 @@
## Upcoming release
* For players:
- * Removed the experimental `RewriteInParallel` option added in SMAPI 3.6 (it was already disabled by default). Unfortunately this caused intermittent unpredictable errors when enabled.
+ * Removed the experimental `RewriteInParallel` option added in SMAPI 3.6 (it was already disabled by default). Unfortunately this caused intermittent and unpredictable errors when enabled.
* Tweaked the rules for showing update alerts (see _for SMAPI developers_ below for details).
* Fixed crossplatform compatibility for mods which use the `[HarmonyPatch(type)]` attribute (thanks to spacechase0!).
* Fixed broken URL in update alerts for unofficial versions.
* Fixed rare error when a mod adds/removes event handlers asynchronously.
+ * Fixed rare issue where the console showed incorrect colors when mods wrote to it asynchronously.
* For modders:
* You can now read/write `SDate` values to JSON (e.g. for `config.json`, network mod messages, etc).
@@ -23,6 +24,7 @@
* For SMAPI developers:
* The web API now returns an update alert in two new cases: any newer unofficial update (previously only shown if the mod was incompatible), and a newer prerelease version if the installed non-prerelease version is broken (previously only shown if the installed version was prerelease).
+ * Internal refactoring to simplify future game updates.
## 3.6.2
Released 02 August 2020 for Stardew Valley 1.4.1 or later.
diff --git a/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs b/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs
deleted file mode 100644
index ef42e536..00000000
--- a/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-
-namespace StardewModdingAPI.Framework.Logging
-{
- /// <summary>Manages console output interception.</summary>
- internal class ConsoleInterceptionManager : IDisposable
- {
- /*********
- ** Fields
- *********/
- /// <summary>The intercepting console writer.</summary>
- private readonly InterceptingTextWriter Output;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>The event raised when a message is written to the console directly.</summary>
- public event Action<string> OnMessageIntercepted;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- public ConsoleInterceptionManager()
- {
- // redirect output through interceptor
- this.Output = new InterceptingTextWriter(Console.Out);
- this.Output.OnMessageIntercepted += line => this.OnMessageIntercepted?.Invoke(line);
- Console.SetOut(this.Output);
- }
-
- /// <summary>Get an exclusive lock and write to the console output without interception.</summary>
- /// <param name="action">The action to perform within the exclusive write block.</param>
- public void ExclusiveWriteWithoutInterception(Action action)
- {
- lock (Console.Out)
- {
- try
- {
- this.Output.ShouldIntercept = false;
- action();
- }
- finally
- {
- this.Output.ShouldIntercept = true;
- }
- }
- }
-
- /// <summary>Release all resources.</summary>
- public void Dispose()
- {
- Console.SetOut(this.Output.Out);
- this.Output.Dispose();
- }
- }
-}
diff --git a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs
index 9ca61b59..d99f1dd2 100644
--- a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs
+++ b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs
@@ -8,17 +8,21 @@ namespace StardewModdingAPI.Framework.Logging
internal class InterceptingTextWriter : TextWriter
{
/*********
+ ** Fields
+ *********/
+ /// <summary>Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</summary>
+ private readonly char IgnoreChar;
+
+
+ /*********
** Accessors
*********/
/// <summary>The underlying console output.</summary>
public TextWriter Out { get; }
- /// <summary>The character encoding in which the output is written.</summary>
+ /// <inheritdoc />
public override Encoding Encoding => this.Out.Encoding;
- /// <summary>Whether to intercept console output.</summary>
- public bool ShouldIntercept { get; set; }
-
/// <summary>The event raised when a message is written to the console directly.</summary>
public event Action<string> OnMessageIntercepted;
@@ -28,36 +32,53 @@ namespace StardewModdingAPI.Framework.Logging
*********/
/// <summary>Construct an instance.</summary>
/// <param name="output">The underlying output writer.</param>
- public InterceptingTextWriter(TextWriter output)
+ /// <param name="ignoreChar">Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</param>
+ public InterceptingTextWriter(TextWriter output, char ignoreChar)
{
this.Out = output;
+ this.IgnoreChar = ignoreChar;
}
- /// <summary>Writes a subarray of characters to the text string or stream.</summary>
- /// <param name="buffer">The character array to write data from.</param>
- /// <param name="index">The character position in the buffer at which to start retrieving data.</param>
- /// <param name="count">The number of characters to write.</param>
+ /// <inheritdoc />
public override void Write(char[] buffer, int index, int count)
{
- if (this.ShouldIntercept)
- this.OnMessageIntercepted?.Invoke(new string(buffer, index, count).TrimEnd('\r', '\n'));
- else
+ if (buffer.Length == 0)
this.Out.Write(buffer, index, count);
+ else if (buffer[0] == this.IgnoreChar)
+ this.Out.Write(buffer, index + 1, count - 1);
+ else if (this.IsEmptyOrNewline(buffer))
+ this.Out.Write(buffer, index, count);
+ else
+ this.OnMessageIntercepted?.Invoke(new string(buffer, index, count).TrimEnd('\r', '\n'));
}
- /// <summary>Writes a character to the text string or stream.</summary>
- /// <param name="ch">The character to write to the text stream.</param>
- /// <remarks>Console log messages from the game should be caught by <see cref="Write(char[],int,int)"/>. This method passes through anything that bypasses that method for some reason, since it's better to show it to users than hide it from everyone.</remarks>
+ /// <inheritdoc />
public override void Write(char ch)
{
this.Out.Write(ch);
}
- /// <summary>Releases the unmanaged resources used by the <see cref="T:System.IO.TextWriter" /> and optionally releases the managed resources.</summary>
- /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
+ /// <inheritdoc />
protected override void Dispose(bool disposing)
{
this.OnMessageIntercepted = null;
}
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get whether a buffer represents a line break.</summary>
+ /// <param name="buffer">The buffer to check.</param>
+ private bool IsEmptyOrNewline(char[] buffer)
+ {
+ foreach (char ch in buffer)
+ {
+ if (ch != '\n' && ch != '\r')
+ return false;
+ }
+
+ return true;
+ }
}
}
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index 3786e940..d0936f3f 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -21,8 +21,8 @@ namespace StardewModdingAPI.Framework.Logging
/// <summary>The log file to which to write messages.</summary>
private readonly LogFileManager LogFile;
- /// <summary>Manages console output interception.</summary>
- private readonly ConsoleInterceptionManager InterceptionManager = new ConsoleInterceptionManager();
+ /// <summary>Prefixing a low-level message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</summary>
+ private readonly char IgnoreChar = '\u200B';
/// <summary>Get a named monitor instance.</summary>
private readonly Func<string, Monitor> GetMonitorImpl;
@@ -86,7 +86,7 @@ namespace StardewModdingAPI.Framework.Logging
public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode)
{
// init construction logic
- this.GetMonitorImpl = name => new Monitor(name, this.InterceptionManager, this.LogFile, colorConfig, isVerbose)
+ this.GetMonitorImpl = name => new Monitor(name, this.IgnoreChar, this.LogFile, colorConfig, isVerbose)
{
WriteToConsole = writeToConsole,
ShowTraceInConsole = isDeveloperMode,
@@ -99,8 +99,10 @@ namespace StardewModdingAPI.Framework.Logging
this.MonitorForGame = this.GetMonitor("game");
// redirect direct console output
- if (this.MonitorForGame.WriteToConsole)
- this.InterceptionManager.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message);
+ var output = new InterceptingTextWriter(Console.Out, this.IgnoreChar);
+ if (writeToConsole)
+ output.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message);
+ Console.SetOut(output);
}
/// <summary>Get a monitor instance derived from SMAPI's current settings.</summary>
@@ -167,7 +169,7 @@ namespace StardewModdingAPI.Framework.Logging
public void PressAnyKeyToExit(bool showMessage)
{
if (showMessage)
- Console.WriteLine("Game has ended. Press any key to exit.");
+ this.Monitor.Log("Game has ended. Press any key to exit.");
Thread.Sleep(100);
Console.ReadKey();
Environment.Exit(0);
@@ -339,7 +341,6 @@ namespace StardewModdingAPI.Framework.Logging
/// <inheritdoc />
public void Dispose()
{
- this.InterceptionManager.Dispose();
this.LogFile.Dispose();
}
diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs
index 44eeabe6..527cba64 100644
--- a/src/SMAPI/Framework/Monitor.cs
+++ b/src/SMAPI/Framework/Monitor.cs
@@ -18,8 +18,8 @@ namespace StardewModdingAPI.Framework
/// <summary>Handles writing text to the console.</summary>
private readonly IConsoleWriter ConsoleWriter;
- /// <summary>Manages access to the console output.</summary>
- private readonly ConsoleInterceptionManager ConsoleInterceptor;
+ /// <summary>Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</summary>
+ private readonly char IgnoreChar;
/// <summary>The log file to which to write messages.</summary>
private readonly LogFileManager LogFile;
@@ -52,11 +52,11 @@ namespace StardewModdingAPI.Framework
*********/
/// <summary>Construct an instance.</summary>
/// <param name="source">The name of the module which logs messages using this instance.</param>
- /// <param name="consoleInterceptor">Intercepts access to the console output.</param>
+ /// <param name="ignoreChar">A character which indicates the message should not be intercepted if it appears as the first character of a string written to the console. The character itself is not logged in that case.</param>
/// <param name="logFile">The log file to which to write messages.</param>
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
/// <param name="isVerbose">Whether verbose logging is enabled. This enables more detailed diagnostic messages than are normally needed.</param>
- public Monitor(string source, ConsoleInterceptionManager consoleInterceptor, LogFileManager logFile, ColorSchemeConfig colorConfig, bool isVerbose)
+ public Monitor(string source, char ignoreChar, LogFileManager logFile, ColorSchemeConfig colorConfig, bool isVerbose)
{
// validate
if (string.IsNullOrWhiteSpace(source))
@@ -66,7 +66,7 @@ namespace StardewModdingAPI.Framework
this.Source = source;
this.LogFile = logFile ?? throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null.");
this.ConsoleWriter = new ColorfulConsoleWriter(Constants.Platform, colorConfig);
- this.ConsoleInterceptor = consoleInterceptor;
+ this.IgnoreChar = ignoreChar;
this.IsVerbose = isVerbose;
}
@@ -99,7 +99,7 @@ namespace StardewModdingAPI.Framework
internal void Newline()
{
if (this.WriteToConsole)
- this.ConsoleInterceptor.ExclusiveWriteWithoutInterception(Console.WriteLine);
+ Console.WriteLine();
this.LogFile.WriteLine("");
}
@@ -136,12 +136,7 @@ namespace StardewModdingAPI.Framework
// write to console
if (this.WriteToConsole && (this.ShowTraceInConsole || level != ConsoleLogLevel.Trace))
- {
- this.ConsoleInterceptor.ExclusiveWriteWithoutInterception(() =>
- {
- this.ConsoleWriter.WriteLine(consoleMessage, level);
- });
- }
+ this.ConsoleWriter.WriteLine(this.IgnoreChar + consoleMessage, level);
// write to log file
this.LogFile.WriteLine(fullMessage);