// 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.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if !NETSTANDARD2_1_OR_GREATER
using CommunityToolkit.HighPerformance.Helpers;
#endif
using CommunityToolkit.HighPerformance.Memory.Internals;
using CommunityToolkit.HighPerformance.Memory.Views;
#if !NETSTANDARD2_1_OR_GREATER
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#endif
#pragma warning disable CS0809, CA1065
namespace CommunityToolkit.HighPerformance;
///
/// represents a 2D region of arbitrary memory. Like the type,
/// it can point to either managed or native memory, or to memory allocated on the stack. It is type- and memory-safe.
/// One key difference with and arrays is that the underlying buffer for a
/// instance might not be contiguous in memory: this is supported to enable mapping arbitrary 2D regions even if they
/// require padding between boundaries of sequential rows. All this logic is handled internally by the
/// type and it is transparent to the user, but note that working over discontiguous buffers has a performance impact.
///
/// The type of items in the current instance.
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly ref partial struct Span2D
{
// Let's consider a representation of a discontiguous 2D memory
// region within an existing array. The data is represented in
// row-major order as usual, and the 'XX' grid cells represent
// locations that are mapped by a given Span2D instance:
//
// _____________________stride_____...
// reference__ /________width_________ ________...
// \/ \/
// | -- | -- | |- | -- | -- | -- | -- | -- | -- | -- |_
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- | |_height
// | -- | -- | XX | XX | XX | XX | XX | XX | -- | -- |_|
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
// | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
// ...__pitch__/
// ...________/
//
// The pitch is used to calculate the offset between each
// discontiguous row, so that any arbitrary memory locations
// can be used to internally represent a 2D span. This gives
// users much more flexibility when creating spans from data.
#if NETSTANDARD2_1_OR_GREATER
///
/// The instance pointing to the first item in the target memory area.
///
///
/// The field maps to the height of the 2D region.
/// This is done to save 4 bytes in the layout of the type.
///
private readonly Span span;
#else
///
/// The target instance, if present.
///
internal readonly object? Instance;
///
/// The initial offset within .
///
internal readonly IntPtr Offset;
///
/// The height of the specified 2D region.
///
private readonly int height;
#endif
///
/// The width of the specified 2D region.
///
private readonly int width;
///
/// The stride of the specified 2D region.
///
///
/// This combines both the width and pitch in a single value so that the indexing
/// logic can be simplified (no need to recompute the sum every time) and be faster.
///
internal readonly int Stride;
#if NETSTANDARD2_1_OR_GREATER
///
/// Initializes a new instance of the struct with the specified parameters.
///
/// The reference to the first item to map.
/// The height of the 2D memory area to map.
/// The width of the 2D memory area to map.
/// The pitch of the 2D memory area to map (the distance between each row).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span2D(ref T value, int height, int width, int pitch)
{
this.span = MemoryMarshal.CreateSpan(ref value, height);
this.width = width;
this.Stride = width + pitch;
}
#endif
///
/// Initializes a new instance of the struct with the specified parameters.
///
/// The pointer to the first item to map.
/// The height of the 2D memory area to map.
/// The width of the 2D memory area to map.
/// The pitch of the 2D memory area to map (the distance between each row).
/// Thrown when one of the parameters are negative.
public unsafe Span2D(void* pointer, int height, int width, int pitch)
{
if (RuntimeHelpers.IsReferenceOrContainsReferences())
{
ThrowHelper.ThrowArgumentExceptionForManagedType();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
#if NETSTANDARD2_1_OR_GREATER
this.span = new Span(pointer, height);
#else
this.Instance = null;
this.Offset = (IntPtr)pointer;
this.height = height;
#endif
this.width = width;
this.Stride = width + pitch;
}
#if !NETSTANDARD2_1_OR_GREATER
///
/// Initializes a new instance of the struct with the specified parameters.
///
/// The target instance.
/// The initial offset within the target instance.
/// The height of the 2D memory area to map.
/// The width of the 2D memory area to map.
/// The pitch of the 2D memory area to map.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span2D(object? instance, IntPtr offset, int height, int width, int pitch)
{
this.Instance = instance;
this.Offset = offset;
this.height = height;
this.width = width;
this.Stride = width + pitch;
}
#endif
///
/// Initializes a new instance of the struct.
///
/// The target array to wrap.
/// The height of the resulting 2D area.
/// The width of each row in the resulting 2D area.
///
/// Thrown when doesn't match .
///
///
/// Thrown when either or are invalid.
///
/// The total area must match the length of .
public Span2D(T[] array, int height, int width)
: this(array, 0, height, width, 0)
{
}
///
/// Initializes a new instance of the struct.
///
/// The target array to wrap.
/// The initial offset within .
/// The height of the resulting 2D area.
/// The width of each row in the resulting 2D area.
/// The pitch in the resulting 2D area.
///
/// Thrown when doesn't match .
///
///
/// Thrown when one of the input parameters is out of range.
///
///
/// Thrown when the requested area is outside of bounds for .
///
public Span2D(T[] array, int offset, int height, int width, int pitch)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)offset > (uint)array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
int area = OverflowHelper.ComputeInt32Area(height, width, pitch);
int remaining = array.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
#if NETSTANDARD2_1_OR_GREATER
this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(offset), height);
#else
this.Instance = array;
this.Offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(offset));
this.height = height;
#endif
this.width = width;
this.Stride = width + pitch;
}
///
/// Initializes a new instance of the struct wrapping a 2D array.
///
/// The given 2D array to wrap.
///
/// Thrown when doesn't match .
///
public Span2D(T[,]? array)
{
if (array is null)
{
this = default;
return;
}
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
#if NETSTANDARD2_1_OR_GREATER
this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReference(), array.GetLength(0));
#else
this.Instance = array;
this.Offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(0, 0));
this.height = array.GetLength(0);
#endif
this.width = this.Stride = array.GetLength(1);
}
///
/// Initializes a new instance of the struct wrapping a 2D array.
///
/// The given 2D array to wrap.
/// The target row to map within .
/// The target column to map within .
/// The height to map within .
/// The width to map within .
///
/// Thrown when doesn't match .
///
///
/// Thrown when either , or
/// are negative or not within the bounds that are valid for .
///
public Span2D(T[,]? array, int row, int column, int height, int width)
{
if (array is null)
{
if (row != 0 || column != 0 || height != 0 || width != 0)
{
ThrowHelper.ThrowArgumentException();
}
this = default;
return;
}
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
int rows = array.GetLength(0);
int columns = array.GetLength(1);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
#if NETSTANDARD2_1_OR_GREATER
this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(row, column), height);
#else
this.Instance = array;
this.Offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(row, column));
this.height = height;
#endif
this.width = width;
this.Stride = columns;
}
///
/// Initializes a new instance of the struct wrapping a layer in a 3D array.
///
/// The given 3D array to wrap.
/// The target layer to map within .
///
/// Thrown when doesn't match .
///
/// Thrown when a parameter is invalid.
public Span2D(T[,,] array, int depth)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
#if NETSTANDARD2_1_OR_GREATER
this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(depth, 0, 0), array.GetLength(1));
#else
this.Instance = array;
this.Offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(depth, 0, 0));
this.height = array.GetLength(1);
#endif
this.width = this.Stride = array.GetLength(2);
}
///
/// Initializes a new instance of the struct wrapping a layer in a 3D array.
///
/// The given 3D array to wrap.
/// The target layer to map within .
/// The target row to map within .
/// The target column to map within .
/// The height to map within .
/// The width to map within .
///
/// Thrown when doesn't match .
///
/// Thrown when a parameter is invalid.
public Span2D(T[,,] array, int depth, int row, int column, int height, int width)
{
if (array.IsCovariant())
{
ThrowHelper.ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
int rows = array.GetLength(1);
int columns = array.GetLength(2);
if ((uint)row >= (uint)rows)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= (uint)columns)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (uint)(rows - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (uint)(columns - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
#if NETSTANDARD2_1_OR_GREATER
this.span = MemoryMarshal.CreateSpan(ref array.DangerousGetReferenceAt(depth, row, column), height);
#else
this.Instance = array;
this.Offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(depth, row, column));
this.height = height;
#endif
this.width = width;
this.Stride = columns;
}
#if NETSTANDARD2_1_OR_GREATER
///
/// Initializes a new instance of the struct.
///
/// The target to wrap.
/// The height of the resulting 2D area.
/// The width of each row in the resulting 2D area.
///
/// Thrown when either or are invalid.
///
/// The total area must match the length of .
internal Span2D(Span span, int height, int width)
: this(span, 0, height, width, 0)
{
}
///
/// Initializes a new instance of the struct.
///
/// The target to wrap.
/// The initial offset within .
/// The height of the resulting 2D area.
/// The width of each row in the resulting 2D area.
/// The pitch in the resulting 2D area.
///
/// Thrown when one of the input parameters is out of range.
///
///
/// Thrown when the requested area is outside of bounds for .
///
internal Span2D(Span span, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)span.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForOffset();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
if (width == 0 || height == 0)
{
this = default;
return;
}
int area = OverflowHelper.ComputeInt32Area(height, width, pitch);
int remaining = span.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.span = MemoryMarshal.CreateSpan(ref span.DangerousGetReferenceAt(offset), height);
this.width = width;
this.Stride = width + pitch;
}
///
/// Creates a new instance of the struct with the specified parameters.
///
/// The reference to the first item to map.
/// The height of the 2D memory area to map.
/// The width of the 2D memory area to map.
/// The pitch of the 2D memory area to map (the distance between each row).
/// A instance with the specified parameters.
/// Thrown when one of the parameters are negative.
[Pure]
public static Span2D DangerousCreate(ref T value, int height, int width, int pitch)
{
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
return new Span2D(ref value, height, width, pitch);
}
#endif
///
/// Gets an empty instance.
///
public static Span2D Empty => default;
///
/// Gets a value indicating whether the current instance is empty.
///
public bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Height == 0 || this.width == 0;
}
///
/// Gets the length of the current instance.
///
public nint Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (nint)(uint)Height * (nint)(uint)this.width;
}
///
/// Gets the height of the underlying 2D memory area.
///
public int Height
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
#if NETSTANDARD2_1_OR_GREATER
return this.span.Length;
#else
return this.height;
#endif
}
}
///
/// Gets the width of the underlying 2D memory area.
///
public int Width
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.width;
}
///
/// Gets the element at the specified zero-based indices.
///
/// The target row to get the element from.
/// The target column to get the element from.
/// A reference to the element at the specified indices.
///
/// Thrown when either or are invalid.
///
public ref T this[int row, int column]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if ((uint)row >= (uint)Height ||
(uint)column >= (uint)this.width)
{
ThrowHelper.ThrowIndexOutOfRangeException();
}
return ref DangerousGetReferenceAt(row, column);
}
}
#if NETSTANDARD2_1_OR_GREATER
///
/// Gets the element at the specified zero-based indices.
///
/// The target row to get the element from.
/// The target column to get the element from.
/// A reference to the element at the specified indices.
///
/// Thrown when either or are invalid.
///
public ref T this[Index row, Index column]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this[row.GetOffset(Height), column.GetOffset(this.width)];
}
///
/// Slices the current instance with the specified parameters.
///
/// The target range of rows to select.
/// The target range of columns to select.
///
/// Thrown when either or are invalid.
///
/// A new instance representing a slice of the current one.
public Span2D this[Range rows, Range columns]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
(int row, int height) = rows.GetOffsetAndLength(Height);
(int column, int width) = columns.GetOffsetAndLength(this.width);
return Slice(row, column, height, width);
}
}
#endif
///
/// Clears the contents of the current instance.
///
public void Clear()
{
if (IsEmpty)
{
return;
}
if (TryGetSpan(out Span span))
{
span.Clear();
}
else
{
// Clear one row at a time
#if NETSTANDARD2_1_OR_GREATER
for (int i = 0; i < Height; i++)
{
GetRowSpan(i).Clear();
}
#else
int height = Height;
nint width = (nint)(uint)this.width;
for (int i = 0; i < height; i++)
{
ref T rStart = ref DangerousGetReferenceAt(i, 0);
ref T rEnd = ref Unsafe.Add(ref rStart, width);
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
{
rStart = default!;
rStart = ref Unsafe.Add(ref rStart, 1);
}
}
#endif
}
}
///
/// 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 (IsEmpty)
{
return;
}
if (TryGetSpan(out Span span))
{
span.CopyTo(destination);
}
else
{
if (Length > destination.Length)
{
ThrowHelper.ThrowArgumentExceptionForDestinationTooShort();
}
// Copy each row individually
#if NETSTANDARD2_1_OR_GREATER
for (int i = 0, j = 0; i < Height; i++, j += this.width)
{
GetRowSpan(i).CopyTo(destination.Slice(j));
}
#else
int height = Height;
nint width = (nint)(uint)this.width;
ref T destinationRef = ref MemoryMarshal.GetReference(destination);
for (int i = 0; i < height; i++)
{
ref T sourceStart = ref DangerousGetReferenceAt(i, 0);
ref T sourceEnd = ref Unsafe.Add(ref sourceStart, width);
while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd))
{
destinationRef = sourceStart;
sourceStart = ref Unsafe.Add(ref sourceStart, 1);
destinationRef = ref Unsafe.Add(ref destinationRef, 1);
}
}
#endif
}
}
///
/// Copies the contents of this into a destination instance.
/// For this API to succeed, the target has to have the same shape as the current one.
///
/// The destination instance.
///
/// Thrown when does not have the same shape as the source instance.
///
public void CopyTo(Span2D destination)
{
if (destination.Height != Height ||
destination.width != this.width)
{
ThrowHelper.ThrowArgumentExceptionForDestinationWithNotSameShape();
}
if (IsEmpty)
{
return;
}
if (destination.TryGetSpan(out Span span))
{
CopyTo(span);
}
else
{
// Copy each row individually
#if NETSTANDARD2_1_OR_GREATER
for (int i = 0; i < Height; i++)
{
GetRowSpan(i).CopyTo(destination.GetRowSpan(i));
}
#else
int height = Height;
nint width = (nint)(uint)this.width;
for (int i = 0; i < height; i++)
{
ref T sourceStart = ref DangerousGetReferenceAt(i, 0);
ref T sourceEnd = ref Unsafe.Add(ref sourceStart, width);
ref T destinationRef = ref destination.DangerousGetReferenceAt(i, 0);
while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd))
{
destinationRef = sourceStart;
sourceStart = ref Unsafe.Add(ref sourceStart, 1);
destinationRef = ref Unsafe.Add(ref destinationRef, 1);
}
}
#endif
}
}
///
/// 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 (destination.Length >= Length)
{
CopyTo(destination);
return true;
}
return false;
}
///
/// 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(Span2D destination)
{
if (destination.Height == Height &&
destination.Width == this.width)
{
CopyTo(destination);
return true;
}
return false;
}
///
/// Fills the elements of this span with a specified value.
///
/// The value to assign to each element of the instance.
public void Fill(T value)
{
if (IsEmpty)
{
return;
}
if (TryGetSpan(out Span span))
{
span.Fill(value);
}
else
{
// Fill one row at a time
#if NETSTANDARD2_1_OR_GREATER
for (int i = 0; i < Height; i++)
{
GetRowSpan(i).Fill(value);
}
#else
int height = Height;
nint width = (nint)(uint)this.width;
for (int i = 0; i < height; i++)
{
ref T rStart = ref DangerousGetReferenceAt(i, 0);
ref T rEnd = ref Unsafe.Add(ref rStart, width);
while (Unsafe.IsAddressLessThan(ref rStart, ref rEnd))
{
rStart = value;
rStart = ref Unsafe.Add(ref rStart, 1);
}
}
#endif
}
}
///
/// Returns a reference to the 0th element of the instance. If the current
/// instance is empty, returns a reference. It can be used for pinning
/// and is required to support the use of span within a fixed statement.
///
/// A reference to the 0th element, or a reference.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[EditorBrowsable(EditorBrowsableState.Never)]
public unsafe ref T GetPinnableReference()
{
ref T r0 = ref Unsafe.AsRef(null);
if (Length != 0)
{
#if NETSTANDARD2_1_OR_GREATER
r0 = ref MemoryMarshal.GetReference(this.span);
#else
r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset);
#endif
}
return ref r0;
}
///
/// Returns a reference to the first element within the current instance, with no bounds check.
///
/// A reference to the first element within the current instance.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T DangerousGetReference()
{
#if NETSTANDARD2_1_OR_GREATER
return ref MemoryMarshal.GetReference(this.span);
#else
return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset);
#endif
}
///
/// Returns a reference to a specified element within the current instance, with no bounds check.
///
/// The target row to get the element from.
/// The target column to get the element from.
/// A reference to the element at the specified indices.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T DangerousGetReferenceAt(int i, int j)
{
#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 index = ((nint)(uint)i * (nint)(uint)this.Stride) + (nint)(uint)j;
return ref Unsafe.Add(ref r0, index);
}
///
/// Slices the current instance with the specified parameters.
///
/// The target row to map within the current instance.
/// The target column to map within the current instance.
/// The height to map within the current instance.
/// The width to map within the current instance.
///
/// Thrown when either , or
/// are negative or not within the bounds that are valid for the current instance.
///
/// A new instance representing a slice of the current one.
[Pure]
public Span2D Slice(int row, int column, int height, int width)
{
if ((uint)row >= Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
if ((uint)column >= this.width)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForColumn();
}
if ((uint)height > (Height - row))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if ((uint)width > (this.width - column))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
nint shift = ((nint)(uint)this.Stride * (nint)(uint)row) + (nint)(uint)column;
int pitch = this.Stride - width;
#if NETSTANDARD2_1_OR_GREATER
ref T r0 = ref this.span.DangerousGetReferenceAt(shift);
return new Span2D(ref r0, height, width, pitch);
#else
IntPtr offset = this.Offset + (shift * (nint)(uint)Unsafe.SizeOf());
return new Span2D(this.Instance, offset, height, width, pitch);
#endif
}
#if NETSTANDARD2_1_OR_GREATER
///
/// Gets a for a specified row.
///
/// The index of the target row to retrieve.
/// Throw when is out of range.
/// The resulting row .
[Pure]
public Span GetRowSpan(int row)
{
if ((uint)row >= (uint)Height)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForRow();
}
ref T r0 = ref DangerousGetReferenceAt(row, 0);
return MemoryMarshal.CreateSpan(ref r0, this.width);
}
#endif
///
/// Tries to get a instance, if the underlying buffer is contiguous and small enough.
///
/// The resulting , in case of success.
/// Whether or not was correctly assigned.
public bool TryGetSpan(out Span span)
{
// We can only create a Span if the buffer is contiguous
if (this.Stride == this.width &&
Length <= int.MaxValue)
{
#if NETSTANDARD2_1_OR_GREATER
span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(this.span), (int)Length);
return true;
#else
// An empty Span2D is still valid
if (IsEmpty)
{
span = default;
return true;
}
// Pinned Span2D
if (this.Instance is null)
{
unsafe
{
span = new Span((void*)this.Offset, (int)Length);
}
return true;
}
// Without Span runtime support, we can only get a Span from a T[] instance
if (this.Instance.GetType() == typeof(T[]))
{
T[] array = Unsafe.As(this.Instance)!;
int index = array.AsSpan().IndexOf(ref ObjectMarshal.DangerousGetObjectDataReferenceAt(array, this.Offset));
span = array.AsSpan(index, (int)Length);
return true;
}
#endif
}
span = default;
return false;
}
///
/// Copies the contents of the current instance into a new 2D array.
///
/// A 2D array containing the data in the current instance.
[Pure]
public T[,] ToArray()
{
T[,] array = new T[Height, this.width];
#if NETSTANDARD2_1_OR_GREATER
CopyTo(array.AsSpan());
#else
// Skip the initialization if the array is empty
if (Length > 0)
{
int height = Height;
nint width = (nint)(uint)this.width;
ref T destinationRef = ref array.DangerousGetReference();
for (int i = 0; i < height; i++)
{
ref T sourceStart = ref DangerousGetReferenceAt(i, 0);
ref T sourceEnd = ref Unsafe.Add(ref sourceStart, width);
while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd))
{
destinationRef = sourceStart;
sourceStart = ref Unsafe.Add(ref sourceStart, 1);
destinationRef = ref Unsafe.Add(ref destinationRef, 1);
}
}
}
#endif
return array;
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Equals() on Span will always throw an exception. Use == instead.")]
public override bool Equals(object? obj)
{
throw new NotSupportedException("CommunityToolkit.HighPerformance.Span2D.Equals(object) is not supported.");
}
///
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("GetHashCode() on Span will always throw an exception.")]
public override int GetHashCode()
{
throw new NotSupportedException("CommunityToolkit.HighPerformance.Span2D.GetHashCode() is not supported.");
}
///
public override string ToString()
{
return $"CommunityToolkit.HighPerformance.Span2D<{typeof(T)}>[{Height}, {this.width}]";
}
///
/// Checks whether two instances are equal.
///
/// The first instance to compare.
/// The second instance to compare.
/// Whether or not and are equal.
public static bool operator ==(Span2D left, Span2D right)
{
return
#if NETSTANDARD2_1_OR_GREATER
left.span == right.span &&
#else
ReferenceEquals(
left.Instance, right.Instance) &&
left.Offset == right.Offset &&
left.height == right.height &&
#endif
left.width == right.width &&
left.Stride == right.Stride;
}
///
/// Checks whether two instances are not equal.
///
/// The first instance to compare.
/// The second instance to compare.
/// Whether or not and are not equal.
public static bool operator !=(Span2D left, Span2D right)
{
return !(left == right);
}
///
/// Implicily converts a given 2D array into a instance.
///
/// The input 2D array to convert.
public static implicit operator Span2D(T[,]? array) => new(array);
}