// 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.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 readonly items from arbitrary memory locations. /// /// The type of items to enumerate. public readonly ref struct ReadOnlyRefEnumerable { #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. private readonly ReadOnlySpan span; #else /// /// The target instance, if present. /// private readonly object? instance; /// /// The initial offset within . /// private readonly IntPtr offset; /// /// The total available length for the sequence. /// private readonly int length; #endif /// /// The distance between items in the sequence to enumerate. /// /// The distance refers to items, not byte offset. private readonly int step; #if NETSTANDARD2_1_OR_GREATER /// /// Initializes a new instance of the struct. /// /// The instance pointing to the first item in the target memory area. /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] private ReadOnlyRefEnumerable(ReadOnlySpan span, int step) { this.span = span; this.step = step; } /// /// 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 ReadOnlyRefEnumerable(in T reference, int length, int step) { this.span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length); this.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. public static ReadOnlyRefEnumerable DangerousCreate(in T value, int length, int step) { if (length < 0) { ThrowArgumentOutOfRangeExceptionForLength(); } if (step < 0) { ThrowArgumentOutOfRangeExceptionForStep(); } OverflowHelper.EnsureIsInNativeIntRange(length, 1, step); return new(in 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 ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int step) { this.instance = instance; this.offset = offset; this.length = length; this.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 => this.length; #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 readonly 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 readonly T this[Index index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref this[index.GetOffset(Length)]; } #endif /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { #if NETSTANDARD2_1_OR_GREATER return new(this.span, this.step); #else return new(this.instance, this.offset, this.length, this.step); #endif } /// /// 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; } /// 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; } /// /// Implicitly converts a instance into a one. /// /// The input instance. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumerable) { #if NETSTANDARD2_1_OR_GREATER return new(enumerable.Span, enumerable.Step); #else return new(enumerable.Instance, enumerable.Offset, enumerable.Length, enumerable.Step); #endif } /// /// A custom enumerator type to traverse items within a instance. /// public ref struct Enumerator { #if NETSTANDARD2_1_OR_GREATER /// private readonly ReadOnlySpan 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(ReadOnlySpan 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 readonly 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 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."); } }