// Based on // https://github.com/CommunityToolkit/dotnet/blob/main/src/CommunityToolkit.HighPerformance/Extensions/SpanExtensions.cs // with additional methods and support for more types // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file on https://github.com/CommunityToolkit/dotnet for more information. using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace FisheryLib; [PublicAPI] public static class SpanExtensions { /// /// Returns a reference to an element at a specified index within a given , with no bounds checks. /// /// The type of elements in the input instance. /// The input instance. /// The index of the element to retrieve within . /// A reference to the element within at the index specified by . /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousGetReferenceAt(this Span span, int i) => ref Unsafe.Add(ref span.DangerousGetPinnableReference(), (nint)(uint)i); /// /// Returns a reference to an element at a specified index within a given , with no bounds checks. /// /// The type of elements in the input instance. /// The input instance. /// The index of the element to retrieve within . /// A reference to the element within at the index specified by . /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousGetReferenceAt(this Span span, nint i) => ref Unsafe.Add(ref span.DangerousGetPinnableReference(), i); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Sort(this Span span, IComparer? comparer = null) => ArraySortHelper.Default.Sort(span, comparer); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Sort(this Span values, Span keys, IComparer? comparer = null) => ArraySortHelper.Default.Sort(keys, values, comparer); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T First(this Span span) => ref span[0]; [return: MaybeNull] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T FirstOrDefault(this Span span) => !span.IsEmpty ? span[0] : default; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref TSource First(this Span span, delegate* predicate) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (predicate(ref Unsafe.AddByteOffset(ref r0, offset))) goto returnForOffset0; if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)))) return ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)); if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))))) return ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))); if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))))) return ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))); length -= 4; offset += 4 * (nuint)sizeof(TSource); } while (length > 0) { if (predicate(ref Unsafe.AddByteOffset(ref r0, offset))) goto returnForOffset0; length--; offset += (nuint)sizeof(TSource); } return ref ThrowNotFoundException(); returnForOffset0: return ref Unsafe.AddByteOffset(ref r0, offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref TSource First(this Span span, Predicate predicate) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (predicate(Unsafe.AddByteOffset(ref r0, offset))) goto returnForOffset0; if (predicate(Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)))) return ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)); if (predicate(Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))))) return ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))); if (predicate(Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))))) return ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))); length -= 4; offset += 4 * (nuint)sizeof(TSource); } while (length > 0) { if (predicate(Unsafe.AddByteOffset(ref r0, offset))) goto returnForOffset0; length--; offset += (nuint)sizeof(TSource); } return ref ThrowNotFoundException(); returnForOffset0: return ref Unsafe.AddByteOffset(ref r0, offset); } [DoesNotReturn] private static ref T ThrowNotFoundException() => throw new InvalidOperationException(); [return: MaybeNull] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe TSource FirstOrDefault(this Span span, delegate* predicate) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (predicate(ref Unsafe.AddByteOffset(ref r0, offset))) goto returnForOffset0; if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)))) return Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)); if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))))) return Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))); if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))))) return Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))); length -= 4; offset += 4 * (nuint)sizeof(TSource); } while (length > 0) { if (predicate(ref Unsafe.AddByteOffset(ref r0, offset))) goto returnForOffset0; length--; offset += (nuint)sizeof(TSource); } return default; returnForOffset0: return Unsafe.AddByteOffset(ref r0, offset); } [return: MaybeNull] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe TSource FirstOrDefault(this Span span, Predicate predicate) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (predicate(Unsafe.AddByteOffset(ref r0, offset))) goto returnForOffset0; if (predicate(Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)))) return Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)); if (predicate(Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))))) return Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))); if (predicate(Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))))) return Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))); length -= 4; offset += 4 * (nuint)sizeof(TSource); } while (length > 0) { if (predicate(Unsafe.AddByteOffset(ref r0, offset))) goto returnForOffset0; length--; offset += (nuint)sizeof(TSource); } return default; returnForOffset0: return Unsafe.AddByteOffset(ref r0, offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Any(this Span span) => !span.IsEmpty; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool Any(this Span span, delegate* predicate) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (predicate(ref Unsafe.AddByteOffset(ref r0, offset)) || predicate(ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource))) || predicate(ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)))) || predicate(ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))))) return true; length -= 4; offset += 4 * (nuint)sizeof(TSource); } while (length > 0) { if (predicate(ref Unsafe.AddByteOffset(ref r0, offset))) return true; length--; offset += (nuint)sizeof(TSource); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool Any(this Span span, Predicate predicate) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (predicate(Unsafe.AddByteOffset(ref r0, offset)) || predicate(Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource))) || predicate(Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)))) || predicate(Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource))))) return true; length -= 4; offset += 4 * (nuint)sizeof(TSource); } while (length > 0) { if (predicate(Unsafe.AddByteOffset(ref r0, offset))) return true; length--; offset += (nuint)sizeof(TSource); } return false; } /// /// Counts the number of occurrences of a given value into a target instance. /// /// The type of items in the input instance. /// The input instance to read. /// The value to look for. /// The number of occurrences of in . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int Count(this Span span, T value) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint result = 0; nuint offset = 0; // Main loop with 4 unrolled iterations while (length >= 4) { result += Unsafe.AddByteOffset(ref r0, offset).Equals(value).ToByte(); result += Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(value).ToByte(); result += Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(value).ToByte(); result += Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(value).ToByte(); length -= 4; offset += 4 * (nuint)sizeof(T); } // Iterate over the remaining values and count those that match while (length > 0) { result += Unsafe.AddByteOffset(ref r0, offset).Equals(value).ToByte(); length--; offset += (nuint)sizeof(T); } return (int)result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool Contains(this Span span, T value) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (Unsafe.AddByteOffset(ref r0, offset).Equals(value) || Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(value) || Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(value) || Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(value)) return true; length -= 4; offset += 4 * (nuint)sizeof(T); } while (length > 0) { if (Unsafe.AddByteOffset(ref r0, offset).Equals(value)) return true; length--; offset += (nuint)sizeof(T); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int IndexOf(this Span span, T value) { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (Unsafe.AddByteOffset(ref r0, offset).Equals(value)) return (int)offset; if (Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(value)) return (int)offset + 1; if (Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(value)) return (int)offset + 2; if (Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(value)) return (int)offset + 3; length -= 4; offset += 4 * (nuint)sizeof(T); } while (length > 0) { if (Unsafe.AddByteOffset(ref r0, offset).Equals(value)) return (int)offset; length--; offset += (nuint)sizeof(T); } return -1; } /// /// Enumerates the items in the input instance, as pairs of reference/index values. /// This extension should be used directly within a loop: /// /// Span<int> numbers = new[] { 1, 2, 3, 4, 5, 6, 7 }; /// /// foreach (var item in numbers.Enumerate()) /// { /// // Access the index and value of each item here... /// int index = item.Index; /// ref int value = ref item.Value; /// } /// /// The compiler will take care of properly setting up the loop with the type returned from this method. /// /// The type of items to enumerate. /// The source to enumerate. /// A wrapper type that will handle the reference/index enumeration for . /// The returned value shouldn't be used directly: use this extension in a loop. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static SpanEnumerable Enumerate(this Span span) => new(span); } public static class SpanReferenceExtensions { /// /// Gets the index of an element of a given from its reference. /// /// The type of items in the input . /// The input to calculate the index for. /// The reference to the target item to get the index for. /// The index of within , or -1. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int IndexOf(this Span span, ref T reference) { ref var r0 = ref span.DangerousGetPinnableReference(); var byteOffset = Unsafe.ByteOffset(ref r0, ref reference); var elementOffset = byteOffset / (nint)(uint)sizeof(T); return (nuint)elementOffset >= (uint)span.Length ? -1 : (int)elementOffset; } /// /// Determines whether a reference points to an element within the . /// /// The type of items in the input . /// The input to locate the reference in. /// The reference to the target item to locate. /// true if the reference points towards an element of ; otherwise, false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Contains(this Span span, ref T reference) => span.IndexOf(ref reference) >= 0; } public static class SpanStructExtensions { /// /// Counts the number of occurrences of a given value into a target instance. /// /// The type of items in the input instance. /// The input instance to read. /// The value to look for. /// The number of occurrences of in . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int Count(this Span span, in T value) where T : struct { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint result = 0; nuint offset = 0; // Main loop with 4 unrolled iterations while (length >= 4) { result += Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value)).ToByte(); result += Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(ref Unsafe.AsRef(in value)).ToByte(); result += Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value)).ToByte(); result += Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value)).ToByte(); length -= 4; offset += 4 * (nuint)sizeof(T); } // Iterate over the remaining values and count those that match while (length > 0) { result += Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value)).ToByte(); length--; offset += (nuint)sizeof(T); } return (int)result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool Contains(this Span span, in T value) where T : struct { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value)) || Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(ref Unsafe.AsRef(in value)) || Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value)) || Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value))) return true; length -= 4; offset += 4 * (nuint)sizeof(T); } while (length > 0) { if (Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value))) return true; length--; offset += (nuint)sizeof(T); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int IndexOf(this Span span, in T value) where T : struct { ref var r0 = ref span.DangerousGetPinnableReference(); var length = (nuint)span.Length; nuint offset = 0; while (length >= 4) { if (Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value))) return (int)offset; if (Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(ref Unsafe.AsRef(in value))) return (int)offset + 1; if (Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value))) return (int)offset + 2; if (Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value))) return (int)offset + 3; length -= 4; offset += 4 * (nuint)sizeof(T); } while (length > 0) { if (Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value))) return (int)offset; length--; offset += (nuint)sizeof(T); } return -1; } }