diff --git a/docs/release-notes.md b/docs/release-notes.md
index 16b47670..622a146a 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -8,6 +8,9 @@
-->
## Upcoming release
+* For players:
+ * Minor optimizations.
+
* For the web UI:
* Fixed the mod count in the log parser metadata.
diff --git a/src/SMAPI/Framework/CommandQueue.cs b/src/SMAPI/Framework/CommandQueue.cs
new file mode 100644
index 00000000..c51016ad
--- /dev/null
+++ b/src/SMAPI/Framework/CommandQueue.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace StardewModdingAPI.Framework
+{
+ /// A thread-safe command queue optimized for infrequent changes.
+ internal class CommandQueue
+ {
+ /********
+ ** Fields
+ ********/
+ /// The underlying list of queued commands to parse and execute.
+ private readonly List RawCommandQueue = new();
+
+
+ /********
+ ** Public methods
+ ********/
+ /// Add a command to the queue.
+ /// The command to add.
+ public void Add(string command)
+ {
+ lock (this.RawCommandQueue)
+ this.RawCommandQueue.Add(command);
+ }
+
+ /// Remove and return all queued commands, if any.
+ /// The commands that were dequeued, in the order they were originally queued.
+ /// Returns whether any values were dequeued.
+ [SuppressMessage("ReSharper", "InconsistentlySynchronizedField", Justification = "Deliberately check if it's empty before locking unnecessarily.")]
+ public bool TryDequeue([NotNullWhen(true)] out string[]? queued)
+ {
+ if (this.RawCommandQueue.Count is 0)
+ {
+ queued = null;
+ return false;
+ }
+
+ lock (this.RawCommandQueue)
+ {
+ queued = this.RawCommandQueue.ToArray();
+ this.RawCommandQueue.Clear();
+ return queued.Length > 0;
+ }
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index ff3eadf5..cc332225 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@@ -147,8 +146,7 @@ namespace StardewModdingAPI.Framework
#endif
/// A list of queued commands to parse and execute.
- /// This property must be thread-safe, since it's accessed from a separate console input thread.
- private readonly ConcurrentQueue RawCommandQueue = new();
+ private readonly CommandQueue RawCommandQueue = new();
/// A list of commands to execute on each screen.
private readonly PerScreen> ScreenCommandQueue = new(() => new List());
@@ -437,7 +435,7 @@ namespace StardewModdingAPI.Framework
() => this.LogManager.RunConsoleInputLoop(
commandManager: this.CommandManager,
reloadTranslations: this.ReloadTranslations,
- handleInput: input => this.RawCommandQueue.Enqueue(input),
+ handleInput: input => this.RawCommandQueue.Add(input),
continueWhile: () => this.IsGameRunning && !this.CancellationToken.IsCancellationRequested
)
).Start();
@@ -525,29 +523,32 @@ namespace StardewModdingAPI.Framework
/*********
** Parse commands
*********/
- while (this.RawCommandQueue.TryDequeue(out string? rawInput))
+ if (this.RawCommandQueue.TryDequeue(out string[]? rawCommands))
{
- // parse command
- string? name;
- string[]? args;
- Command? command;
- int screenId;
- try
+ foreach (string rawInput in rawCommands)
{
- if (!this.CommandManager.TryParse(rawInput, out name, out args, out command, out screenId))
+ // parse command
+ string? name;
+ string[]? args;
+ Command? command;
+ int screenId;
+ try
{
- this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error);
+ if (!this.CommandManager.TryParse(rawInput, out name, out args, out command, out screenId))
+ {
+ this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error);
+ continue;
+ }
+ }
+ catch (Exception ex)
+ {
+ this.Monitor.Log($"Failed parsing that command:\n{ex.GetLogSummary()}", LogLevel.Error);
continue;
}
- }
- catch (Exception ex)
- {
- this.Monitor.Log($"Failed parsing that command:\n{ex.GetLogSummary()}", LogLevel.Error);
- continue;
- }
- // queue command for screen
- this.ScreenCommandQueue.GetValueForScreen(screenId).Add(new(command, name, args));
+ // queue command for screen
+ this.ScreenCommandQueue.GetValueForScreen(screenId).Add(new(command, name, args));
+ }
}