1
0
mirror of https://github.com/chylex/.NET-Community-Toolkit.git synced 2024-11-25 01:42:46 +01:00
.NET-Community-Toolkit/CommunityToolkit.HighPerformance/Memory/ReadOnlyMemory2D{T}.cs
2022-05-03 21:21:10 -07:00

899 lines
34 KiB
C#

// 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.Buffers;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if NETSTANDARD2_1_OR_GREATER
using CommunityToolkit.HighPerformance.Buffers.Internals;
#endif
using CommunityToolkit.HighPerformance.Helpers;
using CommunityToolkit.HighPerformance.Memory.Internals;
using CommunityToolkit.HighPerformance.Memory.Views;
using static CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
#pragma warning disable CA2231
namespace CommunityToolkit.HighPerformance;
/// <summary>
/// A readonly version of <see cref="Memory2D{T}"/>.
/// </summary>
/// <typeparam name="T">The type of items in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</typeparam>
[DebuggerTypeProxy(typeof(MemoryDebugView2D<>))]
[DebuggerDisplay("{ToString(),raw}")]
public readonly struct ReadOnlyMemory2D<T> : IEquatable<ReadOnlyMemory2D<T>>
{
/// <summary>
/// The target <see cref="object"/> instance, if present.
/// </summary>
private readonly object? instance;
/// <summary>
/// The initial offset within <see cref="instance"/>.
/// </summary>
private readonly IntPtr offset;
/// <summary>
/// The height of the specified 2D region.
/// </summary>
private readonly int height;
/// <summary>
/// The width of the specified 2D region.
/// </summary>
private readonly int width;
/// <summary>
/// The pitch of the specified 2D region.
/// </summary>
private readonly int pitch;
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="text">The target <see cref="string"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="text"/>.</remarks>
public ReadOnlyMemory2D(string text, int height, int width)
: this(text, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="text">The target <see cref="string"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="text"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="text"/>.
/// </exception>
public ReadOnlyMemory2D(string text, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)text.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 = text.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = text;
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(text, ref text.DangerousGetReferenceAt(offset));
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="array"/>.</remarks>
public ReadOnlyMemory2D(T[] array, int height, int width)
: this(array, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="array">The target array to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="array"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="array"/>.
/// </exception>
public ReadOnlyMemory2D(T[] array, int offset, int height, int width, int pitch)
{
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();
}
this.instance = array;
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(offset));
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
public ReadOnlyMemory2D(T[,]? array)
{
if (array is null)
{
this = default;
return;
}
this.instance = array;
this.offset = GetArray2DDataByteOffset<T>();
this.height = array.GetLength(0);
this.width = array.GetLength(1);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a 2D array.
/// </summary>
/// <param name="array">The given 2D array to wrap.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for <paramref name="array"/>.
/// </exception>
public ReadOnlyMemory2D(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;
}
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();
}
this.instance = array;
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public ReadOnlyMemory2D(T[,,] array, int depth)
{
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForDepth();
}
this.instance = array;
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(depth, 0, 0));
this.height = array.GetLength(1);
this.width = array.GetLength(2);
this.pitch = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct wrapping a layer in a 3D array.
/// </summary>
/// <param name="array">The given 3D array to wrap.</param>
/// <param name="depth">The target layer to map within <paramref name="array"/>.</param>
/// <param name="row">The target row to map within <paramref name="array"/>.</param>
/// <param name="column">The target column to map within <paramref name="array"/>.</param>
/// <param name="height">The height to map within <paramref name="array"/>.</param>
/// <param name="width">The width to map within <paramref name="array"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when a parameter is invalid.</exception>
public ReadOnlyMemory2D(T[,,] array, int depth, int row, int column, int height, int width)
{
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();
}
this.instance = array;
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(depth, row, column));
this.height = height;
this.width = width;
this.pitch = columns - width;
}
#if NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memoryManager"/>.</remarks>
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int height, int width)
: this(memoryManager, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memoryManager">The target <see cref="MemoryManager{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memoryManager"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memoryManager"/>.
/// </exception>
public ReadOnlyMemory2D(MemoryManager<T> memoryManager, int offset, int height, int width, int pitch)
{
int length = memoryManager.GetSpan().Length;
if ((uint)offset > (uint)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 = length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
this.instance = memoryManager;
this.offset = (nint)(uint)offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="Memory{T}"/> to wrap.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/> or <paramref name="width"/> are invalid.
/// </exception>
/// <remarks>The total area must match the length of <paramref name="memory"/>.</remarks>
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int height, int width)
: this(memory, 0, height, width, 0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct.
/// </summary>
/// <param name="memory">The target <see cref="ReadOnlyMemory{T}"/> to wrap.</param>
/// <param name="offset">The initial offset within <paramref name="memory"/>.</param>
/// <param name="height">The height of the resulting 2D area.</param>
/// <param name="width">The width of each row in the resulting 2D area.</param>
/// <param name="pitch">The pitch in the resulting 2D area.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when the requested area is outside of bounds for <paramref name="memory"/>.
/// </exception>
internal ReadOnlyMemory2D(ReadOnlyMemory<T> memory, int offset, int height, int width, int pitch)
{
if ((uint)offset > (uint)memory.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 = memory.Length - offset;
if (area > remaining)
{
ThrowHelper.ThrowArgumentException();
}
// Check the supported underlying objects, like in Memory2D<T>
if (typeof(T) == typeof(char) &&
MemoryMarshal.TryGetString(Unsafe.As<ReadOnlyMemory<T>, ReadOnlyMemory<char>>(ref memory), out string? text, out int textStart, out _))
{
ref char r0 = ref text.DangerousGetReferenceAt(textStart + offset);
this.instance = text;
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(text, ref r0);
}
else if (MemoryMarshal.TryGetArray(memory, out ArraySegment<T> segment))
{
T[] array = segment.Array!;
this.instance = array;
this.offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref array.DangerousGetReferenceAt(segment.Offset + offset));
}
else if (MemoryMarshal.TryGetMemoryManager(memory, out MemoryManager<T>? memoryManager, out int memoryManagerStart, out _))
{
this.instance = memoryManager;
this.offset = (nint)(uint)(memoryManagerStart + offset);
}
else
{
ThrowHelper.ThrowArgumentExceptionForUnsupportedType();
this.instance = null;
this.offset = default;
}
this.height = height;
this.width = width;
this.pitch = pitch;
}
#endif
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyMemory2D{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="instance">The target <see cref="object"/> instance.</param>
/// <param name="offset">The initial offset within <see cref="instance"/>.</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ReadOnlyMemory2D(object instance, IntPtr offset, int height, int width, int pitch)
{
this.instance = instance;
this.offset = offset;
this.height = height;
this.width = width;
this.pitch = pitch;
}
/// <summary>
/// Creates a new <see cref="ReadOnlyMemory2D{T}"/> instance from an arbitrary object.
/// </summary>
/// <param name="instance">The <see cref="object"/> instance holding the data to map.</param>
/// <param name="value">The target reference to point to (it must be within <paramref name="instance"/>).</param>
/// <param name="height">The height of the 2D memory area to map.</param>
/// <param name="width">The width of the 2D memory area to map.</param>
/// <param name="pitch">The pitch of the 2D memory area to map.</param>
/// <returns>A <see cref="ReadOnlyMemory2D{T}"/> instance with the specified parameters.</returns>
/// <remarks>The <paramref name="value"/> parameter is not validated, and it's responsibility of the caller to ensure it's valid.</remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when one of the input parameters is out of range.
/// </exception>
public static ReadOnlyMemory2D<T> DangerousCreate(object instance, ref T value, int height, int width, int pitch)
{
if (height < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForHeight();
}
if (width < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForWidth();
}
if (pitch < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeExceptionForPitch();
}
OverflowHelper.EnsureIsInNativeIntRange(height, width, pitch);
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(instance, ref value);
return new(instance, offset, height, width, pitch);
}
/// <summary>
/// Gets an empty <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </summary>
public static ReadOnlyMemory2D<T> Empty => default;
/// <summary>
/// Gets a value indicating whether the current <see cref="ReadOnlyMemory2D{T}"/> instance is empty.
/// </summary>
public bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height == 0 || this.width == 0;
}
/// <summary>
/// Gets the length of the current <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </summary>
public nint Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (nint)(uint)this.height * (nint)(uint)this.width;
}
/// <summary>
/// Gets the height of the underlying 2D memory area.
/// </summary>
public int Height
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.height;
}
/// <summary>
/// Gets the width of the underlying 2D memory area.
/// </summary>
public int Width
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.width;
}
/// <summary>
/// Gets a <see cref="ReadOnlySpan2D{T}"/> instance from the current memory.
/// </summary>
public ReadOnlySpan2D<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (this.instance is not null)
{
#if NETSTANDARD2_1_OR_GREATER
if (this.instance is MemoryManager<T> memoryManager)
{
ref T r0 = ref memoryManager.GetSpan().DangerousGetReference();
ref T r1 = ref Unsafe.Add(ref r0, this.offset);
return new(in r1, this.height, this.width, this.pitch);
}
else
{
// This handles both arrays and strings
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.instance, this.offset);
return new(in r0, this.height, this.width, this.pitch);
}
#else
return new(this.instance, this.offset, this.height, this.width, this.pitch);
#endif
}
return default;
}
}
#if NETSTANDARD2_1_OR_GREATER
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="rows">The target range of rows to select.</param>
/// <param name="columns">The target range of columns to select.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="rows"/> or <paramref name="columns"/> are invalid.
/// </exception>
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
public ReadOnlyMemory2D<T> this[Range rows, Range columns]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
(int row, int height) = rows.GetOffsetAndLength(this.height);
(int column, int width) = columns.GetOffsetAndLength(this.width);
return Slice(row, column, height, width);
}
}
#endif
/// <summary>
/// Slices the current instance with the specified parameters.
/// </summary>
/// <param name="row">The target row to map within the current instance.</param>
/// <param name="column">The target column to map within the current instance.</param>
/// <param name="height">The height to map within the current instance.</param>
/// <param name="width">The width to map within the current instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when either <paramref name="height"/>, <paramref name="width"/> or <paramref name="height"/>
/// are negative or not within the bounds that are valid for the current instance.
/// </exception>
/// <returns>A new <see cref="ReadOnlyMemory2D{T}"/> instance representing a slice of the current one.</returns>
public ReadOnlyMemory2D<T> 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();
}
int shift = ((this.width + this.pitch) * row) + column;
int pitch = this.pitch + (this.width - width);
IntPtr offset = this.offset + (shift * Unsafe.SizeOf<T>());
return new(this.instance!, offset, height, width, pitch);
}
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Memory{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory{T}"/>.
/// </summary>
/// <param name="destination">The target <see cref="Memory{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Copies the contents of this <see cref="ReadOnlyMemory2D{T}"/> into a destination <see cref="Memory2D{T}"/> instance.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The destination <see cref="Memory2D{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination" /> is shorter than the source <see cref="ReadOnlyMemory2D{T}"/> instance.
/// </exception>
public void CopyTo(Memory2D<T> destination) => Span.CopyTo(destination.Span);
/// <summary>
/// Attempts to copy the current <see cref="ReadOnlyMemory2D{T}"/> instance to a destination <see cref="Memory2D{T}"/>.
/// For this API to succeed, the target <see cref="Memory2D{T}"/> has to have the same shape as the current one.
/// </summary>
/// <param name="destination">The target <see cref="Memory2D{T}"/> of the copy operation.</param>
/// <returns>Whether or not the operation was successful.</returns>
public bool TryCopyTo(Memory2D<T> destination) => Span.TryCopyTo(destination.Span);
/// <summary>
/// Creates a handle for the memory.
/// The GC will not move the memory until the returned <see cref="MemoryHandle"/>
/// is disposed, enabling taking and using the memory's address.
/// </summary>
/// <exception cref="ArgumentException">
/// An instance with non-primitive (non-blittable) members cannot be pinned.
/// </exception>
/// <returns>A <see cref="MemoryHandle"/> instance wrapping the pinned handle.</returns>
public unsafe MemoryHandle Pin()
{
if (this.instance is not null)
{
if (this.instance is MemoryManager<T> memoryManager)
{
return memoryManager.Pin();
}
GCHandle handle = GCHandle.Alloc(this.instance, GCHandleType.Pinned);
void* pointer = Unsafe.AsPointer(ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(this.instance, this.offset));
return new(pointer, handle);
}
return default;
}
/// <summary>
/// Tries to get a <see cref="ReadOnlyMemory{T}"/> instance, if the underlying buffer is contiguous and small enough.
/// </summary>
/// <param name="memory">The resulting <see cref="ReadOnlyMemory{T}"/>, in case of success.</param>
/// <returns>Whether or not <paramref name="memory"/> was correctly assigned.</returns>
public bool TryGetMemory(out ReadOnlyMemory<T> memory)
{
if (this.pitch == 0 &&
Length <= int.MaxValue)
{
// Empty Memory2D<T> instance
if (this.instance is null)
{
memory = default;
}
else if (typeof(T) == typeof(char) && this.instance.GetType() == typeof(string))
{
// Here we need to create a Memory<char> from the wrapped string, and to do so we need to do an inverse
// lookup to find the initial index of the string with respect to the byte offset we're currently using,
// which refers to the raw string object data. This can include variable padding or other additional
// fields on different runtimes. The lookup operation is still O(1) and just computes the byte offset
// difference between the start of the Span<char> (which directly wraps just the actual character data
// within the string), and the input reference, which we can get from the byte offset in use. The result
// is the character index which we can use to create the final Memory<char> instance.
string text = Unsafe.As<string>(this.instance)!;
int index = text.AsSpan().IndexOf(in ObjectMarshal.DangerousGetObjectDataReferenceAt<char>(text, this.offset));
ReadOnlyMemory<char> temp = text.AsMemory(index, (int)Length);
memory = Unsafe.As<ReadOnlyMemory<char>, ReadOnlyMemory<T>>(ref temp);
}
else if (this.instance is MemoryManager<T> memoryManager)
{
// If the object is a MemoryManager<T>, just slice it as needed
memory = memoryManager.Memory.Slice((int)(nint)this.offset, this.height * this.width);
}
else if (this.instance.GetType() == typeof(T[]))
{
// If it's a T[] array, also handle the initial offset
T[] array = Unsafe.As<T[]>(this.instance)!;
int index = array.AsSpan().IndexOf(ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, this.offset));
memory = array.AsMemory(index, this.height * this.width);
}
#if NETSTANDARD2_1_OR_GREATER
else if (this.instance.GetType() == typeof(T[,]) ||
this.instance.GetType() == typeof(T[,,]))
{
memory = new RawObjectMemoryManager<T>(this.instance, this.offset, this.height * this.width).Memory;
}
#endif
else
{
// Reuse a single failure path to reduce
// the number of returns in the method
goto Failure;
}
return true;
}
Failure:
memory = default;
return false;
}
/// <summary>
/// Copies the contents of the current <see cref="ReadOnlyMemory2D{T}"/> instance into a new 2D array.
/// </summary>
/// <returns>A 2D array containing the data in the current <see cref="ReadOnlyMemory2D{T}"/> instance.</returns>
public T[,] ToArray() => Span.ToArray();
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj)
{
if (obj is ReadOnlyMemory2D<T> readOnlyMemory)
{
return Equals(readOnlyMemory);
}
if (obj is Memory2D<T> memory)
{
return Equals(memory);
}
return false;
}
/// <inheritdoc/>
public bool Equals(ReadOnlyMemory2D<T> other)
{
return
this.instance == other.instance &&
this.offset == other.offset &&
this.height == other.height &&
this.width == other.width &&
this.pitch == other.pitch;
}
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode()
{
if (this.instance is not null)
{
return HashCode.Combine(
RuntimeHelpers.GetHashCode(this.instance),
this.offset,
this.height,
this.width,
this.pitch);
}
return 0;
}
/// <inheritdoc/>
public override string ToString()
{
return $"CommunityToolkit.HighPerformance.ReadOnlyMemory2D<{typeof(T)}>[{this.height}, {this.width}]";
}
/// <summary>
/// Defines an implicit conversion of an array to a <see cref="ReadOnlyMemory2D{T}"/>
/// </summary>
public static implicit operator ReadOnlyMemory2D<T>(T[,]? array) => new(array);
/// <summary>
/// Defines an implicit conversion of a <see cref="Memory2D{T}"/> to a <see cref="ReadOnlyMemory2D{T}"/>
/// </summary>
public static implicit operator ReadOnlyMemory2D<T>(Memory2D<T> memory) => Unsafe.As<Memory2D<T>, ReadOnlyMemory2D<T>>(ref memory);
}