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. --- .../AssetPathUtilities/AssetPartYielder.cs | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/SMAPI/Utilities/AssetPathUtilities/AssetPartYielder.cs (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/AssetPathUtilities/AssetPartYielder.cs b/src/SMAPI/Utilities/AssetPathUtilities/AssetPartYielder.cs new file mode 100644 index 00000000..a55a0ab4 --- /dev/null +++ b/src/SMAPI/Utilities/AssetPathUtilities/AssetPartYielder.cs @@ -0,0 +1,67 @@ +using System; + +using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; + +namespace StardewModdingAPI.Utilities.AssetPathUtilities; + +/// +/// A helper class that yields out each bit of an asset path +/// +internal ref struct AssetPartYielder +{ + private ReadOnlySpan remainder; + + /// + /// Construct an instance. + /// + /// The asset name. + internal AssetPartYielder(ReadOnlySpan assetName) + { + this.remainder = AssetPartYielder.TrimLeadingPathSeperators(assetName); + } + + /// + /// The remainder of the assetName (that hasn't been yielded out yet.) + /// + internal ReadOnlySpan Remainder => this.remainder; + + /// + /// The current segment. + /// + public ReadOnlySpan Current { get; private set; } = default; + + // this is just so it can be used in a foreach loop. + public AssetPartYielder GetEnumerator() => this; + + /// + /// Moves the enumerator to the next element. + /// + /// True if there is a new + public bool MoveNext() + { + if (this.remainder.Length == 0) + { + return false; + } + + int index = this.remainder.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators); + + // no more seperator characters found, I'm done. + if (index < 0) + { + this.Current = this.remainder; + this.remainder = ReadOnlySpan.Empty; + return true; + } + + // Yield the next seperate character bit + this.Current = this.remainder[..index]; + this.remainder = AssetPartYielder.TrimLeadingPathSeperators(this.remainder[(index + 1)..]); + return true; + } + + private static ReadOnlySpan TrimLeadingPathSeperators(ReadOnlySpan span) + { + return span.TrimStart(new ReadOnlySpan(ToolkitPathUtilities.PossiblePathSeparators)); + } +} -- 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 ++++++++--------- .../AssetPathUtilities/AssetNamePartEnumerator.cs | 66 +++++++++++++++++++++ .../AssetPathUtilities/AssetPartYielder.cs | 67 ---------------------- 3 files changed, 91 insertions(+), 94 deletions(-) create mode 100644 src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs delete mode 100644 src/SMAPI/Utilities/AssetPathUtilities/AssetPartYielder.cs (limited to 'src/SMAPI/Utilities') 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. diff --git a/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs b/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs new file mode 100644 index 00000000..0840617a --- /dev/null +++ b/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs @@ -0,0 +1,66 @@ +using System; +using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; + +namespace StardewModdingAPI.Utilities.AssetPathUtilities; + +/// +/// A helper class that yields out each bit of an asset path +/// +internal ref struct AssetNamePartEnumerator +{ + private ReadOnlySpan RemainderImpl; + + /// + /// Construct an instance. + /// + /// The asset name. + internal AssetNamePartEnumerator(ReadOnlySpan assetName) + { + this.RemainderImpl = AssetNamePartEnumerator.TrimLeadingPathSeparators(assetName); + } + + /// + /// The remainder of the assetName (that hasn't been yielded out yet.) + /// + internal ReadOnlySpan Remainder => this.RemainderImpl; + + /// + /// The current segment. + /// + public ReadOnlySpan Current { get; private set; } = default; + + // this is just so it can be used in a foreach loop. + public AssetNamePartEnumerator GetEnumerator() => this; + + /// + /// Moves the enumerator to the next element. + /// + /// True if there is a new + 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.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 static ReadOnlySpan TrimLeadingPathSeparators(ReadOnlySpan span) + { + return span.TrimStart(new ReadOnlySpan(ToolkitPathUtilities.PossiblePathSeparators)); + } +} diff --git a/src/SMAPI/Utilities/AssetPathUtilities/AssetPartYielder.cs b/src/SMAPI/Utilities/AssetPathUtilities/AssetPartYielder.cs deleted file mode 100644 index a55a0ab4..00000000 --- a/src/SMAPI/Utilities/AssetPathUtilities/AssetPartYielder.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; - -using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; - -namespace StardewModdingAPI.Utilities.AssetPathUtilities; - -/// -/// A helper class that yields out each bit of an asset path -/// -internal ref struct AssetPartYielder -{ - private ReadOnlySpan remainder; - - /// - /// Construct an instance. - /// - /// The asset name. - internal AssetPartYielder(ReadOnlySpan assetName) - { - this.remainder = AssetPartYielder.TrimLeadingPathSeperators(assetName); - } - - /// - /// The remainder of the assetName (that hasn't been yielded out yet.) - /// - internal ReadOnlySpan Remainder => this.remainder; - - /// - /// The current segment. - /// - public ReadOnlySpan Current { get; private set; } = default; - - // this is just so it can be used in a foreach loop. - public AssetPartYielder GetEnumerator() => this; - - /// - /// Moves the enumerator to the next element. - /// - /// True if there is a new - public bool MoveNext() - { - if (this.remainder.Length == 0) - { - return false; - } - - int index = this.remainder.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators); - - // no more seperator characters found, I'm done. - if (index < 0) - { - this.Current = this.remainder; - this.remainder = ReadOnlySpan.Empty; - return true; - } - - // Yield the next seperate character bit - this.Current = this.remainder[..index]; - this.remainder = AssetPartYielder.TrimLeadingPathSeperators(this.remainder[(index + 1)..]); - return true; - } - - private static ReadOnlySpan TrimLeadingPathSeperators(ReadOnlySpan span) - { - return span.TrimStart(new ReadOnlySpan(ToolkitPathUtilities.PossiblePathSeparators)); - } -} -- cgit From eed1deb3c75ba2aeea94ea9a57f9fe7ad92a90ce Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 16 Oct 2022 14:41:45 -0400 Subject: apply conventions to asset part enumerator --- .../AssetPathUtilities/AssetNamePartEnumerator.cs | 98 +++++++++++----------- 1 file changed, 51 insertions(+), 47 deletions(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs b/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs index 0840617a..11987ed6 100644 --- a/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs +++ b/src/SMAPI/Utilities/AssetPathUtilities/AssetNamePartEnumerator.cs @@ -1,66 +1,70 @@ using System; using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; -namespace StardewModdingAPI.Utilities.AssetPathUtilities; - -/// -/// A helper class that yields out each bit of an asset path -/// -internal ref struct AssetNamePartEnumerator +namespace StardewModdingAPI.Utilities.AssetPathUtilities { - private ReadOnlySpan RemainderImpl; - - /// - /// Construct an instance. - /// - /// The asset name. - internal AssetNamePartEnumerator(ReadOnlySpan assetName) + /// Handles enumerating the normalized segments in an asset name. + internal ref struct AssetNamePartEnumerator { - this.RemainderImpl = AssetNamePartEnumerator.TrimLeadingPathSeparators(assetName); - } + /********* + ** Fields + *********/ + /// The backing field for . + private ReadOnlySpan RemainderImpl; - /// - /// The remainder of the assetName (that hasn't been yielded out yet.) - /// - internal ReadOnlySpan Remainder => this.RemainderImpl; - /// - /// The current segment. - /// - public ReadOnlySpan Current { get; private set; } = default; + /********* + ** Properties + *********/ + /// The remainder of the asset name being enumerated, ignoring segments which have already been yielded. + public ReadOnlySpan Remainder => this.RemainderImpl; - // this is just so it can be used in a foreach loop. - public AssetNamePartEnumerator GetEnumerator() => this; + /// Get the current segment. + public ReadOnlySpan Current { get; private set; } = default; - /// - /// Moves the enumerator to the next element. - /// - /// True if there is a new - public bool MoveNext() - { - if (this.RemainderImpl.Length == 0) + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The asset name to enumerate. + public AssetNamePartEnumerator(ReadOnlySpan assetName) { - return false; + this.RemainderImpl = AssetNamePartEnumerator.TrimLeadingPathSeparators(assetName); } - int index = this.RemainderImpl.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators); - - // no more separator characters found, I'm done. - if (index < 0) + /// Move the enumerator to the next segment. + /// Returns true if a new value was found (accessible via ). + public bool MoveNext() { - this.Current = this.RemainderImpl; - this.RemainderImpl = ReadOnlySpan.Empty; + 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.Empty; + return true; + } + + // Yield the next separate character bit + this.Current = this.RemainderImpl[..index]; + this.RemainderImpl = AssetNamePartEnumerator.TrimLeadingPathSeparators(this.RemainderImpl[(index + 1)..]); return true; } - // Yield the next separate character bit - this.Current = this.RemainderImpl[..index]; - this.RemainderImpl = AssetNamePartEnumerator.TrimLeadingPathSeparators(this.RemainderImpl[(index + 1)..]); - return true; - } - private static ReadOnlySpan TrimLeadingPathSeparators(ReadOnlySpan span) - { - return span.TrimStart(new ReadOnlySpan(ToolkitPathUtilities.PossiblePathSeparators)); + /********* + ** Private methods + *********/ + /// Trim path separators at the start of the given path or segment. + /// The path or segment to trim. + private static ReadOnlySpan TrimLeadingPathSeparators(ReadOnlySpan span) + { + return span.TrimStart(new ReadOnlySpan(ToolkitPathUtilities.PossiblePathSeparators)); + } } } -- cgit