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