// 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 in the project root for more information. using System; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; #if NETSTANDARD2_1_OR_GREATER using System.Runtime.InteropServices; #endif using CommunityToolkit.HighPerformance.Helpers.Internals; using CommunityToolkit.HighPerformance.Memory.Internals; #if !NETSTANDARD2_1_OR_GREATER using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif namespace CommunityToolkit.HighPerformance.Enumerables; /// /// A that iterates items from arbitrary memory locations. /// /// The type of items to enumerate. public readonly ref struct RefEnumerable { #if NETSTANDARD2_1_OR_GREATER /// /// The instance pointing to the first item in the target memory area. /// /// The field maps to the total available length. internal readonly Span Span; #else /// /// The target instance, if present. /// internal readonly object? Instance; /// /// The initial offset within . /// internal readonly IntPtr Offset; #endif /// /// The distance between items in the sequence to enumerate. /// /// The distance refers to items, not byte offset. internal readonly int Step; #if NETSTANDARD2_1_OR_GREATER /// /// Initializes a new instance of the struct. /// /// A reference to the first item of the sequence. /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal RefEnumerable(ref T reference, int length, int step) { Span = MemoryMarshal.CreateSpan(ref reference, length); Step = step; } /// /// Creates a new instance of the struct with the specified parameters. /// /// The reference to the first item to map. /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. /// A instance with the specified parameters. /// Thrown when one of the parameters are negative. [Pure] public static RefEnumerable DangerousCreate(ref T value, int length, int step) { if (length < 0) { ThrowArgumentOutOfRangeExceptionForLength(); } if (step < 0) { ThrowArgumentOutOfRangeExceptionForStep(); } OverflowHelper.EnsureIsInNativeIntRange(length, 1, step); return new RefEnumerable(ref value, length, step); } #else /// /// Initializes a new instance of the struct. /// /// The target instance. /// The initial offset within . /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal RefEnumerable(object? instance, IntPtr offset, int length, int step) { Instance = instance; Offset = offset; Length = length; Step = step; } #endif /// /// Gets the total available length for the sequence. /// public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] #if NETSTANDARD2_1_OR_GREATER get => this.Span.Length; #else get; #endif } /// /// Gets the element at the specified zero-based index. /// /// The zero-based index of the element. /// A reference to the element at the specified index. /// /// Thrown when is invalid. /// public ref T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if ((uint)index >= (uint)Length) { ThrowHelper.ThrowIndexOutOfRangeException(); } #if NETSTANDARD2_1_OR_GREATER ref T r0 = ref MemoryMarshal.GetReference(this.Span); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif nint offset = (nint)(uint)index * (nint)(uint)this.Step; ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; } } #if NETSTANDARD2_1_OR_GREATER /// /// Gets the element at the specified zero-based index. /// /// The zero-based index of the element. /// A reference to the element at the specified index. /// /// Thrown when is invalid. /// public ref T this[Index index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref this[index.GetOffset(Length)]; } #endif /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { #if NETSTANDARD2_1_OR_GREATER return new Enumerator(this.Span, this.Step); #else return new Enumerator(this.Instance, this.Offset, this.Length, this.Step); #endif } /// /// Clears the contents of the current instance. /// public void Clear() { #if NETSTANDARD2_1_OR_GREATER // Fast path for contiguous items if (this.Step == 1) { this.Span.Clear(); return; } ref T r0 = ref this.Span.DangerousGetReference(); int length = this.Span.Length; #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); int length = this.Length; #endif RefEnumerableHelper.Clear(ref r0, (nint)(uint)length, (nint)(uint)this.Step); } /// /// Copies the contents of this into a destination instance. /// /// The destination instance. /// /// Thrown when is shorter than the source instance. /// public void CopyTo(RefEnumerable destination) { #if NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { destination.CopyFrom(this.Span); return; } if (destination.Step == 1) { CopyTo(destination.Span); return; } ref T sourceRef = ref this.Span.DangerousGetReference(); ref T destinationRef = ref destination.Span.DangerousGetReference(); int sourceLength = this.Span.Length; int destinationLength = destination.Span.Length; #else ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(destination.Instance, destination.Offset); int sourceLength = this.Length; int destinationLength = destination.Length; #endif if ((uint)destinationLength < (uint)sourceLength) { ThrowArgumentExceptionForDestinationTooShort(); } RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step, (nint)(uint)destination.Step); } /// /// Attempts to copy the current instance to a destination . /// /// The target of the copy operation. /// Whether or not the operation was successful. public bool TryCopyTo(RefEnumerable destination) { #if NETSTANDARD2_1_OR_GREATER int sourceLength = this.Span.Length; int destinationLength = destination.Span.Length; #else int sourceLength = this.Length; int destinationLength = destination.Length; #endif if (destinationLength >= sourceLength) { CopyTo(destination); return true; } return false; } /// /// Copies the contents of this into a destination instance. /// /// The destination instance. /// /// Thrown when is shorter than the source instance. /// public void CopyTo(Span destination) { #if NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { this.Span.CopyTo(destination); return; } ref T sourceRef = ref this.Span.DangerousGetReference(); int length = this.Span.Length; #else ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); int length = this.Length; #endif if ((uint)destination.Length < (uint)length) { ThrowArgumentExceptionForDestinationTooShort(); } ref T destinationRef = ref destination.DangerousGetReference(); RefEnumerableHelper.CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)this.Step); } /// /// Attempts to copy the current instance to a destination . /// /// The target of the copy operation. /// Whether or not the operation was successful. public bool TryCopyTo(Span destination) { #if NETSTANDARD2_1_OR_GREATER int length = this.Span.Length; #else int length = this.Length; #endif if (destination.Length >= length) { CopyTo(destination); return true; } return false; } /// /// Copies the contents of a source into the current instance. /// /// The source instance. /// /// Thrown when the current is shorter than the source instance. /// internal void CopyFrom(ReadOnlySpan source) { #if NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { source.CopyTo(this.Span); return; } ref T destinationRef = ref this.Span.DangerousGetReference(); int destinationLength = this.Span.Length; #else ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); int destinationLength = this.Length; #endif ref T sourceRef = ref source.DangerousGetReference(); int sourceLength = source.Length; if ((uint)destinationLength < (uint)sourceLength) { ThrowArgumentExceptionForDestinationTooShort(); } RefEnumerableHelper.CopyFrom(ref sourceRef, ref destinationRef, (nint)(uint)sourceLength, (nint)(uint)this.Step); } /// /// Attempts to copy the source into the current instance. /// /// The source instance. /// Whether or not the operation was successful. public bool TryCopyFrom(ReadOnlySpan source) { #if NETSTANDARD2_1_OR_GREATER int length = this.Span.Length; #else int length = this.Length; #endif if (length >= source.Length) { CopyFrom(source); return true; } return false; } /// /// Fills the elements of this with a specified value. /// /// The value to assign to each element of the instance. public void Fill(T value) { #if NETSTANDARD2_1_OR_GREATER if (this.Step == 1) { this.Span.Fill(value); return; } ref T r0 = ref this.Span.DangerousGetReference(); int length = this.Span.Length; #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); int length = this.Length; #endif RefEnumerableHelper.Fill(ref r0, (nint)(uint)length, (nint)(uint)this.Step, value); } /// /// Returns a array with the values in the target row. /// /// A array with the values in the target row. /// /// This method will allocate a new array, so only /// use it if you really need to copy the target items in a new memory location. /// [Pure] public T[] ToArray() { #if NETSTANDARD2_1_OR_GREATER int length = this.Span.Length; #else int length = this.Length; #endif // Empty array if no data is mapped if (length == 0) { return Array.Empty(); } T[] array = new T[length]; CopyTo(array); return array; } /// /// A custom enumerator type to traverse items within a instance. /// public ref struct Enumerator { #if NETSTANDARD2_1_OR_GREATER /// private readonly Span span; #else /// private readonly object? instance; /// private readonly IntPtr offset; /// private readonly int length; #endif /// private readonly int step; /// /// The current position in the sequence. /// private int position; #if NETSTANDARD2_1_OR_GREATER /// /// Initializes a new instance of the struct. /// /// The instance with the info on the items to traverse. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(Span span, int step) { this.span = span; this.step = step; this.position = -1; } #else /// /// Initializes a new instance of the struct. /// /// The target instance. /// The initial offset within . /// The number of items in the sequence. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(object? instance, IntPtr offset, int length, int step) { this.instance = instance; this.offset = offset; this.length = length; this.step = step; this.position = -1; } #endif /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { #if NETSTANDARD2_1_OR_GREATER return ++this.position < this.span.Length; #else return ++this.position < this.length; #endif } /// public readonly ref T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { #if NETSTANDARD2_1_OR_GREATER ref T r0 = ref this.span.DangerousGetReference(); #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif // Here we just offset by shifting down as if we were traversing a 2D array with a // a single column, with the width of each row represented by the step, the height // represented by the current position, and with only the first element of each row // being inspected. We can perform all the indexing operations in this type as nint, // as the maximum offset is guaranteed never to exceed the maximum value, since on // 32 bit architectures it's not possible to allocate that much memory anyway. nint offset = (nint)(uint)this.position * (nint)(uint)this.step; ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; } } } #if NETSTANDARD2_1_OR_GREATER /// /// Throws an when the "length" parameter is invalid. /// private static void ThrowArgumentOutOfRangeExceptionForLength() { throw new ArgumentOutOfRangeException("length"); } /// /// Throws an when the "step" parameter is invalid. /// private static void ThrowArgumentOutOfRangeExceptionForStep() { throw new ArgumentOutOfRangeException("step"); } #endif /// /// Throws an when the target span is too short. /// private static void ThrowArgumentExceptionForDestinationTooShort() { throw new ArgumentException("The target span is too short to copy all the current items to."); } }