diff options
-rw-r--r-- | docs/release-notes.md | 3 | ||||
-rw-r--r-- | src/SMAPI/Framework/CommandQueue.cs | 47 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 43 |
3 files changed, 72 insertions, 21 deletions
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 +{ + /// <summary>A thread-safe command queue optimized for infrequent changes.</summary> + internal class CommandQueue + { + /******** + ** Fields + ********/ + /// <summary>The underlying list of queued commands to parse and execute.</summary> + private readonly List<string> RawCommandQueue = new(); + + + /******** + ** Public methods + ********/ + /// <summary>Add a command to the queue.</summary> + /// <param name="command">The command to add.</param> + public void Add(string command) + { + lock (this.RawCommandQueue) + this.RawCommandQueue.Add(command); + } + + /// <summary>Remove and return all queued commands, if any.</summary> + /// <param name="queued">The commands that were dequeued, in the order they were originally queued.</param> + /// <returns>Returns whether any values were dequeued.</returns> + [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 /// <summary>A list of queued commands to parse and execute.</summary> - /// <remarks>This property must be thread-safe, since it's accessed from a separate console input thread.</remarks> - private readonly ConcurrentQueue<string> RawCommandQueue = new(); + private readonly CommandQueue RawCommandQueue = new(); /// <summary>A list of commands to execute on each screen.</summary> private readonly PerScreen<List<QueuedCommand>> ScreenCommandQueue = new(() => new List<QueuedCommand>()); @@ -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 + { + 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("Unknown command; type 'help' for a list of available commands.", LogLevel.Error); + 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)); + } } |