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