// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using CommunityToolkit.HighPerformance.Helpers;
#if NETSTANDARD2_1_OR_GREATER
using System.Runtime.InteropServices;
using CommunityToolkit.HighPerformance.Buffers.Internals;
#endif
using CommunityToolkit.HighPerformance.Helpers.Internals;
using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers;
namespace CommunityToolkit.HighPerformance;
///
partial class ArrayExtensions
{
///
/// Returns a reference to the first element within a given 3D array, with no bounds checks.
///
/// The type of elements in the input 3D array instance.
/// The input array instance.
/// A reference to the first element within , or the location it would have used, if is empty.
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReference(this T[,,] array)
{
#if NET6_0_OR_GREATER
return ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array));
#elif NETCOREAPP3_1
RawArray3DData? arrayData = Unsafe.As(array)!;
ref T r0 = ref Unsafe.As(ref arrayData.Data);
return ref r0;
#else
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset();
return ref ObjectMarshal.DangerousGetObjectDataReferenceAt(array, offset);
#endif
}
///
/// Returns a reference to an element at a specified coordinate within a given 3D array, with no bounds checks.
///
/// The type of elements in the input 3D array instance.
/// The input 2D array instance.
/// The depth index of the element to retrieve within .
/// The vertical index of the element to retrieve within .
/// The horizontal index of the element to retrieve within .
/// A reference to the element within at the coordinate specified by and .
///
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the
/// and parameters are valid. Furthermore, this extension will ignore the lower bounds for the input
/// array, and will just assume that the input index is 0-based. It is responsibility of the caller to adjust the input
/// indices to account for the actual lower bounds, if the input array has either axis not starting at 0.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt(this T[,,] array, int i, int j, int k)
{
#if NET6_0_OR_GREATER
int height = array.GetLength(1);
int width = array.GetLength(2);
nint index =
((nint)(uint)i * (nint)(uint)height * (nint)(uint)width) +
((nint)(uint)j * (nint)(uint)width) + (nint)(uint)k;
ref T r0 = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array));
ref T ri = ref Unsafe.Add(ref r0, index);
return ref ri;
#elif NETCOREAPP3_1
RawArray3DData? arrayData = Unsafe.As(array)!;
nint offset =
((nint)(uint)i * (nint)(uint)arrayData.Height * (nint)(uint)arrayData.Width) +
((nint)(uint)j * (nint)(uint)arrayData.Width) + (nint)(uint)k;
ref T r0 = ref Unsafe.As(ref arrayData.Data);
ref T ri = ref Unsafe.Add(ref r0, offset);
return ref ri;
#else
int height = array.GetLength(1);
int width = array.GetLength(2);
nint index =
((nint)(uint)i * (nint)(uint)height * (nint)(uint)width) +
((nint)(uint)j * (nint)(uint)width) + (nint)(uint)k;
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset();
ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt(array, offset);
ref T ri = ref Unsafe.Add(ref r0, index);
return ref ri;
#endif
}
#if NETCOREAPP3_1
// See description for this in the 2D partial file.
// Using the CHW naming scheme here (like with RGB images).
[StructLayout(LayoutKind.Sequential)]
private sealed class RawArray3DData
{
#pragma warning disable CS0649 // Unassigned fields
public IntPtr Length;
public int Channel;
public int Height;
public int Width;
public int ChannelLowerBound;
public int HeightLowerBound;
public int WidthLowerBound;
public byte Data;
#pragma warning restore CS0649
}
#endif
#if NETSTANDARD2_1_OR_GREATER
///
/// Creates a new over an input 3D array.
///
/// The type of elements in the input 3D array instance.
/// The input 3D array instance.
/// A instance with the values of .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory AsMemory(this T[,,]? array)
{
if (array is null)
{
return default;
}
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
IntPtr offset = RuntimeHelpers.GetArray3DDataByteOffset();
int length = array.Length;
return new RawObjectMemoryManager(array, offset, length).Memory;
}
///
/// Creates a new over an input 3D array.
///
/// The type of elements in the input 3D array instance.
/// The input 3D array instance.
/// A instance with the values of .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span AsSpan(this T[,,]? array)
{
if (array is null)
{
return default;
}
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
ref T r0 = ref array.DangerousGetReference();
int length = array.Length;
return MemoryMarshal.CreateSpan(ref r0, length);
}
///
/// Creates a new instance of the struct wrapping a layer in a 3D array.
///
/// The type of elements in the input 3D array instance.
/// The given 3D array to wrap.
/// The target layer to map within .
/// Thrown when doesn't match .
/// Thrown when is invalid.
/// A instance wrapping the target layer within .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span AsSpan(this T[,,] array, int depth)
{
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowArgumentOutOfRangeExceptionForDepth();
}
ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
int length = checked(array.GetLength(1) * array.GetLength(2));
return MemoryMarshal.CreateSpan(ref r0, length);
}
///
/// Creates a new instance of the struct wrapping a layer in a 3D array.
///
/// The type of elements in the input 3D array instance.
/// The given 3D array to wrap.
/// The target layer to map within .
/// Thrown when doesn't match .
/// Thrown when is invalid.
/// A instance wrapping the target layer within .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory AsMemory(this T[,,] array, int depth)
{
if (array.IsCovariant())
{
ThrowArrayTypeMismatchException();
}
if ((uint)depth >= (uint)array.GetLength(0))
{
ThrowArgumentOutOfRangeExceptionForDepth();
}
ref T r0 = ref array.DangerousGetReferenceAt(depth, 0, 0);
IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0);
int length = checked(array.GetLength(1) * array.GetLength(2));
return new RawObjectMemoryManager(array, offset, length).Memory;
}
#endif
///
/// Creates a new instance of the struct wrapping a layer in a 3D array.
///
/// The type of elements in the input 3D array instance.
/// The given 3D array to wrap.
/// The target layer to map within .
///
/// Thrown when doesn't match .
///
/// Thrown when either is invalid.
/// A instance wrapping the target layer within .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span2D AsSpan2D(this T[,,] array, int depth)
{
return new(array, depth);
}
///
/// Creates a new instance of the struct wrapping a layer in a 3D array.
///
/// The type of elements in the input 3D array instance.
/// The given 3D array to wrap.
/// The target layer to map within .
///
/// Thrown when doesn't match .
///
/// Thrown when either is invalid.
/// A instance wrapping the target layer within .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory2D AsMemory2D(this T[,,] array, int depth)
{
return new(array, depth);
}
///
/// Counts the number of occurrences of a given value into a target 3D array instance.
///
/// The type of items in the input 3D array instance.
/// The input 3D array instance.
/// The value to look for.
/// The number of occurrences of in .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count(this T[,,] array, T value)
where T : IEquatable
{
ref T r0 = ref array.DangerousGetReference();
nint length = RuntimeHelpers.GetArrayNativeLength(array);
nint count = SpanHelper.Count(ref r0, length, value);
if ((nuint)count > int.MaxValue)
{
ThrowOverflowException();
}
return (int)count;
}
///
/// Gets a content hash from the input 3D array instance using the Djb2 algorithm.
/// For more info, see the documentation for .
///
/// The type of items in the input 3D array instance.
/// The input 3D array instance.
/// The Djb2 value for the input 3D array instance.
/// The Djb2 hash is fully deterministic and with no random components.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode(this T[,,] array)
where T : notnull
{
ref T r0 = ref array.DangerousGetReference();
nint length = RuntimeHelpers.GetArrayNativeLength(array);
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
///
/// Checks whether or not a given array is covariant.
///
/// The type of items in the input array instance.
/// The input array instance.
/// Whether or not is covariant.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsCovariant(this T[,,] array)
{
return default(T) is null && array.GetType() != typeof(T[,,]);
}
///
/// Throws an when the "depth" parameter is invalid.
///
private static void ThrowArgumentOutOfRangeExceptionForDepth()
{
throw new ArgumentOutOfRangeException("depth");
}
}