diff options
-rw-r--r-- | build/common.targets | 2 | ||||
-rw-r--r-- | docs/release-notes.md | 10 | ||||
-rw-r--r-- | src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 | ||||
-rw-r--r-- | src/SMAPI.Mods.ErrorHandler/manifest.json | 4 | ||||
-rw-r--r-- | src/SMAPI.Mods.SaveBackup/manifest.json | 4 | ||||
-rw-r--r-- | src/SMAPI.Tests/Core/AssetNameTests.cs | 22 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/LogParsing/LogParser.cs | 4 | ||||
-rw-r--r-- | src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs | 3 | ||||
-rw-r--r-- | src/SMAPI.Web/Views/LogParser/Index.cshtml | 6 | ||||
-rw-r--r-- | src/SMAPI.Web/wwwroot/Content/js/log-parser.js | 11 | ||||
-rw-r--r-- | src/SMAPI/Constants.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/Content/AssetName.cs | 108 | ||||
-rw-r--r-- | src/SMAPI/Framework/Models/SConfig.cs | 34 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 19 | ||||
-rw-r--r-- | src/SMAPI/SMAPI.config.json | 6 | ||||
-rw-r--r-- | src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs | 70 |
16 files changed, 235 insertions, 74 deletions
diff --git a/build/common.targets b/build/common.targets index 2f1f822e..3c22b913 100644 --- a/build/common.targets +++ b/build/common.targets @@ -7,7 +7,7 @@ repo. It imports the other MSBuild files as needed. <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <!--set general build properties --> - <Version>3.17.1</Version> + <Version>3.17.2</Version> <Product>SMAPI</Product> <LangVersion>latest</LangVersion> <AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths> diff --git a/docs/release-notes.md b/docs/release-notes.md index 02d085d5..0843f59a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,8 +9,16 @@ ## Upcoming release * For players: - * Fixed installer crash if Steam's library data is invalid or in an old format; it'll now be ignored instead. + * Added config option to disable console input. This may reduce CPU usage on some Linux systems. + +* For the web UI: + * Fixed log parser not showing screen IDs in split-screen mode, and improved screen display. +## 3.17.2 +Released 21 October 2022 for Stardew Valley 1.5.6 or later. + +* For players: + * Fixed installer crash if Steam's library data is invalid or in an old format; it'll now be ignored instead. * For mod authors: * Fixed image patches sometimes applied one pixel higher than expected after 3.17.0 (thanks to atravita!). diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index ecac30bf..d1296dbe 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.17.1", + "Version": "3.17.2", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.17.1" + "MinimumApiVersion": "3.17.2" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index 3d708876..c3757e8f 100644 --- a/src/SMAPI.Mods.ErrorHandler/manifest.json +++ b/src/SMAPI.Mods.ErrorHandler/manifest.json @@ -1,9 +1,9 @@ { "Name": "Error Handler", "Author": "SMAPI", - "Version": "3.17.1", + "Version": "3.17.2", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.17.1" + "MinimumApiVersion": "3.17.2" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index c2bf5f05..78821a3e 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.17.1", + "Version": "3.17.2", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.17.1" + "MinimumApiVersion": "3.17.2" } diff --git a/src/SMAPI.Tests/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs index 655e9bae..fdaa2c01 100644 --- a/src/SMAPI.Tests/Core/AssetNameTests.cs +++ b/src/SMAPI.Tests/Core/AssetNameTests.cs @@ -151,6 +151,12 @@ namespace SMAPI.Tests.Core // with locale codes [TestCase("Data/Achievements.fr-FR", "Data/Achievements", ExpectedResult = true)] + + // prefix ends with path separator + [TestCase("Data/Events/Boop", "Data/Events/", ExpectedResult = true)] + [TestCase("Data/Events/Boop", "Data/Events\\", ExpectedResult = true)] + [TestCase("Data/Events", "Data/Events/", ExpectedResult = false)] + [TestCase("Data/Events", "Data/Events\\", ExpectedResult = false)] public bool StartsWith_SimpleCases(string mainAssetName, string prefix) { // arrange @@ -243,6 +249,22 @@ namespace SMAPI.Tests.Core return result; } + [TestCase("Mods/SomeMod/SomeSubdirectory", "Mods/Some", true, ExpectedResult = true)] + [TestCase("Mods/SomeMod/SomeSubdirectory", "Mods/Some", false, ExpectedResult = false)] + [TestCase("Mods/Jasper/Data", "Mods/Jas/Image", true, ExpectedResult = false)] + [TestCase("Mods/Jasper/Data", "Mods/Jas/Image", true, ExpectedResult = false)] + public bool StartsWith_PartialMatchInPathSegment(string mainAssetName, string otherAssetName, bool allowSubfolder) + { + // arrange + mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName); + + // act + AssetName name = AssetName.Parse(mainAssetName, _ => null); + + // assert value + return name.StartsWith(otherAssetName, allowPartialWord: true, allowSubfolder: allowSubfolder); + } + /**** ** GetHashCode diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index 5e0dedf3..c39e612b 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -108,6 +108,10 @@ namespace StardewModdingAPI.Web.Framework.LogParsing } } + // detect split-screen mode + if (message.ScreenId != 0) + log.IsSplitScreen = true; + // collect SMAPI metadata if (message.Mod == "SMAPI") { diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs index cda0f653..2a2e5f5c 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs @@ -22,6 +22,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models /// <summary>The raw log text.</summary> public string? RawText { get; set; } + /// <summary>Whether there are messages from multiple screens in the log.</summary> + public bool IsSplitScreen { get; set; } + /**** ** Log data ****/ diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 28127903..c1251c21 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -47,7 +47,7 @@ <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1" crossorigin="anonymous"></script> <script src="~/Content/js/file-upload.js"></script> - <script src="~/Content/js/log-parser.js?r=20220409"></script> + <script src="~/Content/js/log-parser.js"></script> <script id="serializedData" type="application/json"> @if (!Model.ShowRaw) @@ -86,7 +86,8 @@ showMods: @this.ForJson(log?.Mods.Where(p => p.Loaded && !p.IsContentPack).Select(p => Model.GetSlug(p.Name)).Distinct().ToDictionary(slug => slug, _ => true)), showSections: @this.ForJson(Enum.GetNames(typeof(LogSection)).ToDictionary(section => section, _ => false)), showLevels: @this.ForJson(defaultFilters), - enableFilters: @this.ForJson(!Model.ShowRaw) + enableFilters: @this.ForJson(!Model.ShowRaw), + isSplitScreen: @this.ForJson(log?.IsSplitScreen ?? false) } ); @@ -494,7 +495,6 @@ else if (log?.IsValid == true) <log-line v-for="msg in visibleMessages" v-bind:key="msg.id" - v-bind:showScreenId="showScreenId" v-bind:message="msg" v-bind:highlight="shouldHighlight" /> diff --git a/src/SMAPI.Web/wwwroot/Content/js/log-parser.js b/src/SMAPI.Web/wwwroot/Content/js/log-parser.js index fccd00be..324218bb 100644 --- a/src/SMAPI.Web/wwwroot/Content/js/log-parser.js +++ b/src/SMAPI.Web/wwwroot/Content/js/log-parser.js @@ -424,10 +424,6 @@ smapi.logParser = function (state) { Vue.component("log-line", { functional: true, props: { - showScreenId: { - type: Boolean, - required: true - }, message: { type: Object, required: true @@ -456,7 +452,7 @@ smapi.logParser = function (state) { "td", { attrs: { - colspan: context.props.showScreenId ? 4 : 3 + colspan: state.isSplitScreen ? 4 : 3 } }, "" @@ -541,7 +537,7 @@ smapi.logParser = function (state) { }, [ createElement("td", message.Time), - context.props.showScreenId ? createElement("td", message.ScreenId) : null, + state.isSplitScreen ? createElement("td", { attrs: { title: (message.ScreenId == 0 ? "main screen" : "screen #" + (message.ScreenId + 1)) + " in split-screen mode" } }, `🖵${message.ScreenId + 1}`) : null, createElement("td", level.toUpperCase()), createElement( "td", @@ -588,9 +584,6 @@ smapi.logParser = function (state) { anyModsShown: function () { return stats.modsShown > 0; }, - showScreenId: function () { - return this.data.screenIds.length > 1; - }, // Maybe not strictly necessary, but the Vue template is being // weird about accessing data entries on the app rather than diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index ea41a4ee..77900ca6 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -52,7 +52,7 @@ namespace StardewModdingAPI internal static int? LogScreenId { get; set; } /// <summary>SMAPI's current raw semantic version.</summary> - internal static string RawApiVersion = "3.17.1"; + internal static string RawApiVersion = "3.17.2"; } /// <summary>Contains SMAPI's constants and assumptions.</summary> diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 148354a1..99968299 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -1,6 +1,8 @@ using System; using StardewModdingAPI.Toolkit.Utilities; +using StardewModdingAPI.Utilities.AssetPathUtilities; using StardewValley; +using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; namespace StardewModdingAPI.Framework.Content { @@ -94,10 +96,26 @@ namespace StardewModdingAPI.Framework.Content if (string.IsNullOrWhiteSpace(assetName)) return false; - assetName = PathUtilities.NormalizeAssetName(assetName); + AssetNamePartEnumerator curParts = new(useBaseName ? this.BaseName : this.Name); + AssetNamePartEnumerator otherParts = new(assetName.AsSpan().Trim()); - string compareTo = useBaseName ? this.BaseName : this.Name; - return compareTo.Equals(assetName, StringComparison.OrdinalIgnoreCase); + while (true) + { + bool curHasMore = curParts.MoveNext(); + bool otherHasMore = otherParts.MoveNext(); + + // mismatch: lengths differ + if (otherHasMore != curHasMore) + return false; + + // match: both reached the end without a mismatch + if (!curHasMore) + return true; + + // mismatch: current segment is different + if (!curParts.Current.Equals(otherParts.Current, StringComparison.OrdinalIgnoreCase)) + return false; + } } /// <inheritdoc /> @@ -119,42 +137,70 @@ namespace StardewModdingAPI.Framework.Content if (prefix is null) return false; - string rawTrimmed = prefix.Trim(); + // get initial values + ReadOnlySpan<char> trimmedPrefix = prefix.AsSpan().Trim(); + if (trimmedPrefix.Length == 0) + return true; + ReadOnlySpan<char> pathSeparators = new(ToolkitPathUtilities.PossiblePathSeparators); // just to simplify calling other span APIs - // asset keys can't have a leading slash, but NormalizeAssetName will trim them - if (rawTrimmed.StartsWith('/') || rawTrimmed.StartsWith('\\')) + // asset keys can't have a leading slash, but AssetPathYielder will trim them + if (pathSeparators.Contains(trimmedPrefix[0])) return false; - // normalize prefix + // compare segments + AssetNamePartEnumerator curParts = new(this.Name); + AssetNamePartEnumerator prefixParts = new(trimmedPrefix); + while (true) { - string normalized = PathUtilities.NormalizeAssetName(prefix); + bool curHasMore = curParts.MoveNext(); + bool prefixHasMore = prefixParts.MoveNext(); - // keep trailing slash - if (rawTrimmed.EndsWith('/') || rawTrimmed.EndsWith('\\')) - normalized += PathUtilities.PreferredAssetSeparator; + // reached end for one side + if (prefixHasMore != curHasMore) + { + // mismatch: prefix is longer + if (prefixHasMore) + return false; - prefix = normalized; - } + // match if subfolder paths are fine (e.g. prefix 'Data/Events' with target 'Data/Events/Beach') + return allowSubfolder; + } - // compare - if (prefix.Length == 0) - return true; + // previous segments matched exactly and both reached the end + // match if prefix doesn't end with '/' (which should only match subfolders) + if (!prefixHasMore) + return !pathSeparators.Contains(trimmedPrefix[^1]); - return - this.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) - && ( - allowPartialWord - || this.Name.Length == prefix.Length - || !char.IsLetterOrDigit(prefix[^1]) // last character in suffix is word separator - || !char.IsLetterOrDigit(this.Name[prefix.Length]) // or first character after it is - ) - && ( - allowSubfolder - || this.Name.Length == prefix.Length - || !this.Name[prefix.Length..].Contains(PathUtilities.PreferredAssetSeparator) - ); - } + // compare segment + if (curParts.Current.Length == prefixParts.Current.Length) + { + // mismatch: segments aren't equivalent + if (!curParts.Current.Equals(prefixParts.Current, StringComparison.OrdinalIgnoreCase)) + return false; + } + else + { + // mismatch: prefix has more beyond this, and this segment isn't an exact match + if (prefixParts.Remainder.Length != 0) + return false; + + // mismatch: cur segment doesn't start with prefix + if (!curParts.Current.StartsWith(prefixParts.Current, StringComparison.OrdinalIgnoreCase)) + return false; + // mismatch: something like "Maps/" would need an exact match + if (pathSeparators.Contains(trimmedPrefix[^1])) + return false; + + // mismatch: partial word match not allowed, and the first or last letter of the suffix isn't a word separator + if (!allowPartialWord && char.IsLetterOrDigit(prefixParts.Current[^1]) && char.IsLetterOrDigit(curParts.Current[prefixParts.Current.Length])) + return false; + + // possible match + return allowSubfolder || (pathSeparators.Contains(trimmedPrefix[^1]) ? curParts.Remainder.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators) < 0 : curParts.Remainder.Length == 0); + } + } + } /// <inheritdoc /> public bool IsDirectlyUnderPath(string? assetFolder) @@ -162,7 +208,7 @@ namespace StardewModdingAPI.Framework.Content if (assetFolder is null) return false; - return this.StartsWith(assetFolder + "/", allowPartialWord: false, allowSubfolder: false); + return this.StartsWith(assetFolder + ToolkitPathUtilities.PreferredPathSeparator, allowPartialWord: false, allowSubfolder: false); } /// <inheritdoc /> diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index bceb0940..825ebb44 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -16,6 +16,7 @@ namespace StardewModdingAPI.Framework.Models private static readonly IDictionary<string, object> DefaultValues = new Dictionary<string, object> { [nameof(CheckForUpdates)] = true, + [nameof(ListenForConsoleInput)] = true, [nameof(ParanoidWarnings)] = Constants.IsDebugBuild, [nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(), [nameof(GitHubProjectName)] = "Pathoschild/SMAPI", @@ -48,6 +49,9 @@ namespace StardewModdingAPI.Framework.Models /// <summary>Whether to check for newer versions of SMAPI and mods on startup.</summary> public bool CheckForUpdates { get; set; } + /// <summary>Whether SMAPI should listen for console input to support console commands.</summary> + public bool ListenForConsoleInput { get; set; } + /// <summary>Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access.</summary> public bool ParanoidWarnings { get; set; } @@ -87,23 +91,25 @@ namespace StardewModdingAPI.Framework.Models ** Public methods ********/ /// <summary>Construct an instance.</summary> - /// <param name="developerMode">Whether to enable development features.</param> - /// <param name="checkForUpdates">Whether to check for newer versions of SMAPI and mods on startup.</param> - /// <param name="paranoidWarnings">Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access.</param> - /// <param name="useBetaChannel">Whether to show beta versions as valid updates.</param> - /// <param name="gitHubProjectName">SMAPI's GitHub project name, used to perform update checks.</param> - /// <param name="webApiBaseUrl">The base URL for SMAPI's web API, used to perform update checks.</param> - /// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param> - /// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param> - /// <param name="useCaseInsensitivePaths">>Whether to make SMAPI file APIs case-insensitive, even on Linux.</param> - /// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param> - /// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param> - /// <param name="suppressHarmonyDebugMode">Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and creates a file on your desktop. Debug mode should never be enabled by a released mod.</param> - /// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param> - public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks) + /// <param name="developerMode"><inheritdoc cref="DeveloperMode" path="/summary" /></param> + /// <param name="checkForUpdates"><inheritdoc cref="CheckForUpdates" path="/summary" /></param> + /// <param name="listenForConsoleInput"><inheritdoc cref="ListenForConsoleInput" path="/summary" /></param> + /// <param name="paranoidWarnings"><inheritdoc cref="ParanoidWarnings" path="/summary" /></param> + /// <param name="useBetaChannel"><inheritdoc cref="UseBetaChannel" path="/summary" /></param> + /// <param name="gitHubProjectName"><inheritdoc cref="GitHubProjectName" path="/summary" /></param> + /// <param name="webApiBaseUrl"><inheritdoc cref="WebApiBaseUrl" path="/summary" /></param> + /// <param name="verboseLogging"><inheritdoc cref="VerboseLogging" path="/summary" /></param> + /// <param name="rewriteMods"><inheritdoc cref="RewriteMods" path="/summary" /></param> + /// <param name="useCaseInsensitivePaths"><inheritdoc cref="UseCaseInsensitivePaths" path="/summary" /></param> + /// <param name="logNetworkTraffic"><inheritdoc cref="LogNetworkTraffic" path="/summary" /></param> + /// <param name="consoleColors"><inheritdoc cref="ConsoleColors" path="/summary" /></param> + /// <param name="suppressHarmonyDebugMode"><inheritdoc cref="SuppressHarmonyDebugMode" path="/summary" /></param> + /// <param name="suppressUpdateChecks"><inheritdoc cref="SuppressUpdateChecks" path="/summary" /></param> + public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; + this.ListenForConsoleInput = listenForConsoleInput ?? (bool)SConfig.DefaultValues[nameof(this.ListenForConsoleInput)]; this.ParanoidWarnings = paranoidWarnings ?? (bool)SConfig.DefaultValues[nameof(this.ParanoidWarnings)]; this.UseBetaChannel = useBetaChannel ?? (bool)SConfig.DefaultValues[nameof(this.UseBetaChannel)]; this.GitHubProjectName = gitHubProjectName; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 40979b09..7bb54653 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -447,14 +447,17 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("SMAPI found problems in your game's content files which are likely to cause errors or crashes. Consider uninstalling XNB mods or reinstalling the game.", LogLevel.Error); // start SMAPI console - new Thread( - () => this.LogManager.RunConsoleInputLoop( - commandManager: this.CommandManager, - reloadTranslations: this.ReloadTranslations, - handleInput: input => this.RawCommandQueue.Add(input), - continueWhile: () => this.IsGameRunning && !this.IsExiting - ) - ).Start(); + if (this.Settings.ListenForConsoleInput) + { + new Thread( + () => this.LogManager.RunConsoleInputLoop( + commandManager: this.CommandManager, + reloadTranslations: this.ReloadTranslations, + handleInput: input => this.RawCommandQueue.Add(input), + continueWhile: () => this.IsGameRunning && !this.IsExiting + ) + ).Start(); + } } /// <summary>Raised after an instance finishes loading its initial content.</summary> diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 635e3add..52f25fdd 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -42,6 +42,12 @@ copy all the settings, or you may cause bugs due to overridden changes in future "DeveloperMode": true, /** + * Whether SMAPI should listen for console input. Disabling this will prevent you from using + * console commands. On some specific Linux systems, disabling this may reduce CPU usage. + */ + "ListenForConsoleInput": true, + + /** * Whether SMAPI should rewrite mods for compatibility. This may prevent older mods from * loading, but bypasses a Visual Studio crash when debugging. */ diff --git a/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs b/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs new file mode 100644 index 00000000..11987ed6 --- /dev/null +++ b/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs @@ -0,0 +1,70 @@ +using System; +using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; + +namespace StardewModdingAPI.Utilities.AssetPathUtilities +{ + /// <summary>Handles enumerating the normalized segments in an asset name.</summary> + internal ref struct AssetNamePartEnumerator + { + /********* + ** Fields + *********/ + /// <summary>The backing field for <see cref="Remainder"/>.</summary> + private ReadOnlySpan<char> RemainderImpl; + + + /********* + ** Properties + *********/ + /// <summary>The remainder of the asset name being enumerated, ignoring segments which have already been yielded.</summary> + public ReadOnlySpan<char> Remainder => this.RemainderImpl; + + /// <summary>Get the current segment.</summary> + public ReadOnlySpan<char> Current { get; private set; } = default; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="assetName">The asset name to enumerate.</param> + public AssetNamePartEnumerator(ReadOnlySpan<char> assetName) + { + this.RemainderImpl = AssetNamePartEnumerator.TrimLeadingPathSeparators(assetName); + } + + /// <summary>Move the enumerator to the next segment.</summary> + /// <returns>Returns true if a new value was found (accessible via <see cref="Current"/>).</returns> + public bool MoveNext() + { + if (this.RemainderImpl.Length == 0) + return false; + + int index = this.RemainderImpl.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators); + + // no more separator characters found, I'm done. + if (index < 0) + { + this.Current = this.RemainderImpl; + this.RemainderImpl = ReadOnlySpan<char>.Empty; + return true; + } + + // Yield the next separate character bit + this.Current = this.RemainderImpl[..index]; + this.RemainderImpl = AssetNamePartEnumerator.TrimLeadingPathSeparators(this.RemainderImpl[(index + 1)..]); + return true; + } + + + /********* + ** Private methods + *********/ + /// <summary>Trim path separators at the start of the given path or segment.</summary> + /// <param name="span">The path or segment to trim.</param> + private static ReadOnlySpan<char> TrimLeadingPathSeparators(ReadOnlySpan<char> span) + { + return span.TrimStart(new ReadOnlySpan<char>(ToolkitPathUtilities.PossiblePathSeparators)); + } + } +} |