From 0c191eb32c41ffedd321951cda70b521e9b51c96 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Sat, 15 Oct 2022 08:36:24 -0400 Subject: make asset name comparing lazy. --- src/SMAPI/Framework/Content/AssetName.cs | 108 ++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 30 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 148354a1..05e1d1c2 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -1,7 +1,11 @@ using System; using StardewModdingAPI.Toolkit.Utilities; +using StardewModdingAPI.Utilities.AssetPathUtilities; + using StardewValley; +using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; + namespace StardewModdingAPI.Framework.Content { /// An asset name that can be loaded through the content pipeline. @@ -94,10 +98,28 @@ namespace StardewModdingAPI.Framework.Content if (string.IsNullOrWhiteSpace(assetName)) return false; - assetName = PathUtilities.NormalizeAssetName(assetName); + AssetPartYielder compareTo = new(useBaseName ? this.BaseName : this.Name); + AssetPartYielder compareFrom = new(assetName); + + while (true) + { + bool otherHasMore = compareFrom.MoveNext(); + bool iHaveMore = compareTo.MoveNext(); + + // neither of us have any more to yield, I'm done. + if (!otherHasMore && !iHaveMore) + return true; + + // One of us has more but the other doesn't, this isn't a match. + if (otherHasMore ^ iHaveMore) + return false; - string compareTo = useBaseName ? this.BaseName : this.Name; - return compareTo.Equals(assetName, StringComparison.OrdinalIgnoreCase); + // My next bit doesn't match their next bit, this isn't a match. + if (!compareTo.Current.Equals(compareFrom.Current, StringComparison.OrdinalIgnoreCase)) + return false; + + // continue checking. + } } /// @@ -119,43 +141,69 @@ namespace StardewModdingAPI.Framework.Content if (prefix is null) return false; - string rawTrimmed = prefix.Trim(); + ReadOnlySpan trimmed = prefix.AsSpan().Trim(); + + // just because most ReadOnlySpan/Span APIs expect a ReadOnlySpan/Span, easier to read. + ReadOnlySpan seperators = new(ToolkitPathUtilities.PossiblePathSeparators); - // 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 won't yield that. + if (seperators.Contains(trimmed[0])) return false; - // normalize prefix + if (trimmed.Length == 0) + return true; + + AssetPartYielder compareTo = new(this.Name); + AssetPartYielder compareFrom = new(trimmed); + + while (true) { - string normalized = PathUtilities.NormalizeAssetName(prefix); + bool otherHasMore = compareFrom.MoveNext(); + bool iHaveMore = compareTo.MoveNext(); - // keep trailing slash - if (rawTrimmed.EndsWith('/') || rawTrimmed.EndsWith('\\')) - normalized += PathUtilities.PreferredAssetSeparator; + // Neither of us have any more to yield, I'm done. + if (!otherHasMore && !iHaveMore) + return true; - prefix = normalized; - } + // the prefix is actually longer than the asset name, this can't be true. + if (otherHasMore && !iHaveMore) + return false; - // compare - if (prefix.Length == 0) - return true; + // they're done, I have more. (These are going to be word boundaries, I don't need to check that). + if (!otherHasMore && iHaveMore) + { + return allowSubfolder || !compareTo.Remainder.Contains(seperators, StringComparison.Ordinal); + } - 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) - ); + // check my next segment against theirs. + if (otherHasMore && iHaveMore) + { + // my next segment doesn't match theirs. + if (!compareTo.Current.StartsWith(compareFrom.Current, StringComparison.OrdinalIgnoreCase)) + return false; + + // my next segment starts with theirs but isn't an exact match. + if (compareTo.Current.Length != compareFrom.Current.Length) + { + // something like "Maps/" would require an exact match. + if (seperators.Contains(trimmed[^1])) + return false; + + // check for partial word. + if (!allowPartialWord + && char.IsLetterOrDigit(compareFrom.Current[^1]) // last character in suffix is not word separator + && char.IsLetterOrDigit(compareTo.Current[compareFrom.Current.Length]) // and the first character after it isn't either. + ) + return false; + + return allowSubfolder || !compareTo.Remainder.Contains(seperators, StringComparison.Ordinal); + } + + // exact matches should continue checking. + } + } } - /// public bool IsDirectlyUnderPath(string? assetFolder) { -- cgit From 70cde89480e43bb1369c1063c7b19f757784f269 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 16 Oct 2022 14:41:45 -0400 Subject: tweak naming in new code --- src/SMAPI/Framework/Content/AssetName.cs | 52 +++++++++++++++----------------- 1 file changed, 25 insertions(+), 27 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 05e1d1c2..d7ee6dba 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -1,9 +1,7 @@ using System; using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Utilities.AssetPathUtilities; - using StardewValley; - using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; namespace StardewModdingAPI.Framework.Content @@ -98,24 +96,24 @@ namespace StardewModdingAPI.Framework.Content if (string.IsNullOrWhiteSpace(assetName)) return false; - AssetPartYielder compareTo = new(useBaseName ? this.BaseName : this.Name); - AssetPartYielder compareFrom = new(assetName); - + AssetNamePartEnumerator curParts = new(useBaseName ? this.BaseName : this.Name); + AssetNamePartEnumerator otherParts = new(assetName); + while (true) { - bool otherHasMore = compareFrom.MoveNext(); - bool iHaveMore = compareTo.MoveNext(); + bool otherHasMore = otherParts.MoveNext(); + bool curHasMore = curParts.MoveNext(); // neither of us have any more to yield, I'm done. - if (!otherHasMore && !iHaveMore) + if (!otherHasMore && !curHasMore) return true; // One of us has more but the other doesn't, this isn't a match. - if (otherHasMore ^ iHaveMore) + if (otherHasMore ^ curHasMore) return false; // My next bit doesn't match their next bit, this isn't a match. - if (!compareTo.Current.Equals(compareFrom.Current, StringComparison.OrdinalIgnoreCase)) + if (!curParts.Current.Equals(otherParts.Current, StringComparison.OrdinalIgnoreCase)) return false; // continue checking. @@ -144,59 +142,59 @@ namespace StardewModdingAPI.Framework.Content ReadOnlySpan trimmed = prefix.AsSpan().Trim(); // just because most ReadOnlySpan/Span APIs expect a ReadOnlySpan/Span, easier to read. - ReadOnlySpan seperators = new(ToolkitPathUtilities.PossiblePathSeparators); + ReadOnlySpan pathSeparators = new(ToolkitPathUtilities.PossiblePathSeparators); // asset keys can't have a leading slash, but AssetPathYielder won't yield that. - if (seperators.Contains(trimmed[0])) + if (pathSeparators.Contains(trimmed[0])) return false; if (trimmed.Length == 0) return true; - AssetPartYielder compareTo = new(this.Name); - AssetPartYielder compareFrom = new(trimmed); + AssetNamePartEnumerator curParts = new(this.Name); + AssetNamePartEnumerator prefixParts = new(trimmed); while (true) { - bool otherHasMore = compareFrom.MoveNext(); - bool iHaveMore = compareTo.MoveNext(); + bool prefixHasMore = prefixParts.MoveNext(); + bool curHasMore = curParts.MoveNext(); // Neither of us have any more to yield, I'm done. - if (!otherHasMore && !iHaveMore) + if (!prefixHasMore && !curHasMore) return true; // the prefix is actually longer than the asset name, this can't be true. - if (otherHasMore && !iHaveMore) + if (prefixHasMore && !curHasMore) return false; // they're done, I have more. (These are going to be word boundaries, I don't need to check that). - if (!otherHasMore && iHaveMore) + if (!prefixHasMore && curHasMore) { - return allowSubfolder || !compareTo.Remainder.Contains(seperators, StringComparison.Ordinal); + return allowSubfolder || !curParts.Remainder.Contains(pathSeparators, StringComparison.Ordinal); } // check my next segment against theirs. - if (otherHasMore && iHaveMore) + if (prefixHasMore && curHasMore) { // my next segment doesn't match theirs. - if (!compareTo.Current.StartsWith(compareFrom.Current, StringComparison.OrdinalIgnoreCase)) + if (!curParts.Current.StartsWith(prefixParts.Current, StringComparison.OrdinalIgnoreCase)) return false; // my next segment starts with theirs but isn't an exact match. - if (compareTo.Current.Length != compareFrom.Current.Length) + if (curParts.Current.Length != prefixParts.Current.Length) { // something like "Maps/" would require an exact match. - if (seperators.Contains(trimmed[^1])) + if (pathSeparators.Contains(trimmed[^1])) return false; // check for partial word. if (!allowPartialWord - && char.IsLetterOrDigit(compareFrom.Current[^1]) // last character in suffix is not word separator - && char.IsLetterOrDigit(compareTo.Current[compareFrom.Current.Length]) // and the first character after it isn't either. + && char.IsLetterOrDigit(prefixParts.Current[^1]) // last character in suffix is not word separator + && char.IsLetterOrDigit(curParts.Current[prefixParts.Current.Length]) // and the first character after it isn't either. ) return false; - return allowSubfolder || !compareTo.Remainder.Contains(seperators, StringComparison.Ordinal); + return allowSubfolder || !curParts.Remainder.Contains(pathSeparators, StringComparison.Ordinal); } // exact matches should continue checking. -- cgit From 4e3b2810e6951b72bdf5c5cbdd23a079d53a4c96 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 16 Oct 2022 14:41:45 -0400 Subject: fix index-out-of-range error when StartsWith prefix is empty --- src/SMAPI/Framework/Content/AssetName.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index d7ee6dba..c0572105 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -139,21 +139,19 @@ namespace StardewModdingAPI.Framework.Content if (prefix is null) return false; + // get initial values ReadOnlySpan trimmed = prefix.AsSpan().Trim(); + if (trimmed.Length == 0) + return true; + ReadOnlySpan pathSeparators = new(ToolkitPathUtilities.PossiblePathSeparators); // just to simplify calling other span APIs - // just because most ReadOnlySpan/Span APIs expect a ReadOnlySpan/Span, easier to read. - ReadOnlySpan pathSeparators = new(ToolkitPathUtilities.PossiblePathSeparators); - - // asset keys can't have a leading slash, but AssetPathYielder won't yield that. + // asset keys can't have a leading slash, but AssetPathYielder will trim them if (pathSeparators.Contains(trimmed[0])) return false; - if (trimmed.Length == 0) - return true; - + // compare segments AssetNamePartEnumerator curParts = new(this.Name); AssetNamePartEnumerator prefixParts = new(trimmed); - while (true) { bool prefixHasMore = prefixParts.MoveNext(); -- cgit From 5d30b47e1e903f7ceb53116528255934c238e5ba Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 16 Oct 2022 14:41:46 -0400 Subject: fix IsEquivalentTo no longer ignoring surrounding whitespace --- src/SMAPI/Framework/Content/AssetName.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index c0572105..6220ea61 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -97,7 +97,7 @@ namespace StardewModdingAPI.Framework.Content return false; AssetNamePartEnumerator curParts = new(useBaseName ? this.BaseName : this.Name); - AssetNamePartEnumerator otherParts = new(assetName); + AssetNamePartEnumerator otherParts = new(assetName.AsSpan().Trim()); while (true) { -- cgit From 573f732c2a2118d7a4848151764df6bef1a47008 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 16 Oct 2022 14:41:46 -0400 Subject: reduce sequential bool checks a bit --- src/SMAPI/Framework/Content/AssetName.cs | 77 +++++++++++++++----------------- 1 file changed, 37 insertions(+), 40 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 6220ea61..bdb79dde 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -101,22 +101,20 @@ namespace StardewModdingAPI.Framework.Content while (true) { - bool otherHasMore = otherParts.MoveNext(); bool curHasMore = curParts.MoveNext(); + bool otherHasMore = otherParts.MoveNext(); - // neither of us have any more to yield, I'm done. - if (!otherHasMore && !curHasMore) - return true; - - // One of us has more but the other doesn't, this isn't a match. - if (otherHasMore ^ curHasMore) + // mismatch: lengths differ + if (otherHasMore != curHasMore) return false; - // My next bit doesn't match their next bit, this isn't a match. + // 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; - - // continue checking. } } @@ -154,48 +152,47 @@ namespace StardewModdingAPI.Framework.Content AssetNamePartEnumerator prefixParts = new(trimmed); while (true) { - bool prefixHasMore = prefixParts.MoveNext(); bool curHasMore = curParts.MoveNext(); + bool prefixHasMore = prefixParts.MoveNext(); - // Neither of us have any more to yield, I'm done. - if (!prefixHasMore && !curHasMore) - return true; - - // the prefix is actually longer than the asset name, this can't be true. - if (prefixHasMore && !curHasMore) - return false; - - // they're done, I have more. (These are going to be word boundaries, I don't need to check that). - if (!prefixHasMore && curHasMore) + // reached end of prefix or asset name + if (prefixHasMore != curHasMore) { + // mismatch: prefix is longer + if (prefixHasMore) + return false; + + // possible match: all prefix segments matched return allowSubfolder || !curParts.Remainder.Contains(pathSeparators, StringComparison.Ordinal); } - // check my next segment against theirs. - if (prefixHasMore && curHasMore) + // match: previous segments matched exactly and both reached the end + if (!prefixHasMore) + return true; + + // 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 { - // my next segment doesn't match theirs. + // mismatch: cur segment doesn't start with prefix if (!curParts.Current.StartsWith(prefixParts.Current, StringComparison.OrdinalIgnoreCase)) return false; - // my next segment starts with theirs but isn't an exact match. - if (curParts.Current.Length != prefixParts.Current.Length) - { - // something like "Maps/" would require an exact match. - if (pathSeparators.Contains(trimmed[^1])) - return false; - - // check for partial word. - if (!allowPartialWord - && char.IsLetterOrDigit(prefixParts.Current[^1]) // last character in suffix is not word separator - && char.IsLetterOrDigit(curParts.Current[prefixParts.Current.Length]) // and the first character after it isn't either. - ) - return false; + // mismatch: something like "Maps/" would need an exact match + if (pathSeparators.Contains(trimmed[^1])) + return false; - return allowSubfolder || !curParts.Remainder.Contains(pathSeparators, StringComparison.Ordinal); - } + // 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; - // exact matches should continue checking. + // possible match + return allowSubfolder || !curParts.Remainder.Contains(pathSeparators, StringComparison.Ordinal); } } } -- cgit From 4dcc6904b9e72ac3567dfafe3824c2de48218b58 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Sun, 16 Oct 2022 18:04:19 -0400 Subject: fix issues with subfolders --- src/SMAPI/Framework/Content/AssetName.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index bdb79dde..9d59f222 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -162,8 +162,8 @@ namespace StardewModdingAPI.Framework.Content if (prefixHasMore) return false; - // possible match: all prefix segments matched - return allowSubfolder || !curParts.Remainder.Contains(pathSeparators, StringComparison.Ordinal); + // possible match: all prefix segments matched. + return allowSubfolder || (pathSeparators.Contains(trimmed[^1]) ? curParts.Remainder.Length == 0 : curParts.Current.Length == 0); } // match: previous segments matched exactly and both reached the end @@ -192,7 +192,7 @@ namespace StardewModdingAPI.Framework.Content return false; // possible match - return allowSubfolder || !curParts.Remainder.Contains(pathSeparators, StringComparison.Ordinal); + return allowSubfolder || (pathSeparators.Contains(trimmed[^1]) ? curParts.Remainder.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators) < 0 : curParts.Remainder.Length == 0); } } } @@ -203,7 +203,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); } /// -- cgit From b99dbf53bda9dc1178a3b6e8cbafea609f3ee6dc Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Tue, 18 Oct 2022 18:58:41 -0400 Subject: fix this case. --- src/SMAPI/Framework/Content/AssetName.cs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 9d59f222..7b87c0c5 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -179,6 +179,10 @@ namespace StardewModdingAPI.Framework.Content } 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; -- cgit From 303b3924ae3ef905d77b9d7ef0f9efc70e58c2b8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 10 Nov 2022 21:50:01 -0500 Subject: fix case where prefix ends with a path separator --- src/SMAPI/Framework/Content/AssetName.cs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 7b87c0c5..99968299 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -138,37 +138,38 @@ namespace StardewModdingAPI.Framework.Content return false; // get initial values - ReadOnlySpan trimmed = prefix.AsSpan().Trim(); - if (trimmed.Length == 0) + ReadOnlySpan trimmedPrefix = prefix.AsSpan().Trim(); + if (trimmedPrefix.Length == 0) return true; ReadOnlySpan pathSeparators = new(ToolkitPathUtilities.PossiblePathSeparators); // just to simplify calling other span APIs // asset keys can't have a leading slash, but AssetPathYielder will trim them - if (pathSeparators.Contains(trimmed[0])) + if (pathSeparators.Contains(trimmedPrefix[0])) return false; // compare segments AssetNamePartEnumerator curParts = new(this.Name); - AssetNamePartEnumerator prefixParts = new(trimmed); + AssetNamePartEnumerator prefixParts = new(trimmedPrefix); while (true) { bool curHasMore = curParts.MoveNext(); bool prefixHasMore = prefixParts.MoveNext(); - // reached end of prefix or asset name + // reached end for one side if (prefixHasMore != curHasMore) { // mismatch: prefix is longer if (prefixHasMore) return false; - // possible match: all prefix segments matched. - return allowSubfolder || (pathSeparators.Contains(trimmed[^1]) ? curParts.Remainder.Length == 0 : curParts.Current.Length == 0); + // match if subfolder paths are fine (e.g. prefix 'Data/Events' with target 'Data/Events/Beach') + return allowSubfolder; } - // match: previous segments matched exactly and both reached the end + // previous segments matched exactly and both reached the end + // match if prefix doesn't end with '/' (which should only match subfolders) if (!prefixHasMore) - return true; + return !pathSeparators.Contains(trimmedPrefix[^1]); // compare segment if (curParts.Current.Length == prefixParts.Current.Length) @@ -188,7 +189,7 @@ namespace StardewModdingAPI.Framework.Content return false; // mismatch: something like "Maps/" would need an exact match - if (pathSeparators.Contains(trimmed[^1])) + 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 @@ -196,7 +197,7 @@ namespace StardewModdingAPI.Framework.Content return false; // possible match - return allowSubfolder || (pathSeparators.Contains(trimmed[^1]) ? curParts.Remainder.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators) < 0 : curParts.Remainder.Length == 0); + return allowSubfolder || (pathSeparators.Contains(trimmedPrefix[^1]) ? curParts.Remainder.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators) < 0 : curParts.Remainder.Length == 0); } } } -- cgit