// 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.Runtime.CompilerServices; using System.Runtime.InteropServices; using CommunityToolkit.HighPerformance.Buffers.Internals.Interfaces; using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; namespace CommunityToolkit.HighPerformance.Buffers.Internals; /// /// A custom that casts data from a array, to values. /// /// The source type of items to read. /// The target type to cast the source items to. internal sealed class ArrayMemoryManager : MemoryManager, IMemoryManager where TFrom : unmanaged where TTo : unmanaged { /// /// The source array to read data from. /// private readonly TFrom[] array; /// /// The starting offset within . /// private readonly int offset; /// /// The original used length for . /// private readonly int length; /// /// Initializes a new instance of the class. /// /// The source array to read data from. /// The starting offset within . /// The original used length for . public ArrayMemoryManager(TFrom[] array, int offset, int length) { this.array = array; this.offset = offset; this.length = length; } /// public override Span GetSpan() { #if NETSTANDARD2_1_OR_GREATER ref TFrom r0 = ref this.array.DangerousGetReferenceAt(this.offset); ref TTo r1 = ref Unsafe.As(ref r0); int length = RuntimeHelpers.ConvertLength(this.length); return MemoryMarshal.CreateSpan(ref r1, length); #else Span span = this.array.AsSpan(this.offset, this.length); // We rely on MemoryMarshal.Cast here to deal with calculating the effective // size of the new span to return. This will also make the behavior consistent // for users that are both using this type as well as casting spans directly. return MemoryMarshal.Cast(span); #endif } /// public override unsafe MemoryHandle Pin(int elementIndex = 0) { if ((uint)elementIndex >= (uint)(this.length * Unsafe.SizeOf() / Unsafe.SizeOf())) { ThrowArgumentOutOfRangeExceptionForInvalidIndex(); } int bytePrefix = this.offset * Unsafe.SizeOf(); int byteSuffix = elementIndex * Unsafe.SizeOf(); int byteOffset = bytePrefix + byteSuffix; GCHandle handle = GCHandle.Alloc(this.array, GCHandleType.Pinned); ref TFrom r0 = ref this.array.DangerousGetReference(); ref byte r1 = ref Unsafe.As(ref r0); ref byte r2 = ref Unsafe.Add(ref r1, byteOffset); void* pi = Unsafe.AsPointer(ref r2); return new(pi, handle); } /// public override void Unpin() { } /// protected override void Dispose(bool disposing) { } /// public Memory GetMemory(int offset, int length) where T : unmanaged { // We need to calculate the right offset and length of the new Memory. The local offset // is the original offset into the wrapped TFrom[] array, while the input offset is the one // with respect to TTo items in the Memory instance that is currently being cast. int absoluteOffset = this.offset + RuntimeHelpers.ConvertLength(offset); int absoluteLength = RuntimeHelpers.ConvertLength(length); // We have a special handling in cases where the user is circling back to the original type // of the wrapped array. In this case we can just return a memory wrapping that array directly, // with offset and length being adjusted, without the memory manager indirection. if (typeof(T) == typeof(TFrom)) { return (Memory)(object)this.array.AsMemory(absoluteOffset, absoluteLength); } return new ArrayMemoryManager(this.array, absoluteOffset, absoluteLength).Memory; } /// /// Throws an when the target index for is invalid. /// private static void ThrowArgumentOutOfRangeExceptionForInvalidIndex() { throw new ArgumentOutOfRangeException("elementIndex", "The input index is not in the valid range"); } }