// 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.");
}
}