// SPDX-FileCopyrightText: 2025 Unity Technologies and the glTFast authors // SPDX-License-Identifier: Apache-2.0 using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; namespace GLTFast { /// /// This is a stripped-down version of that supports . /// /// Member type [StructLayout(LayoutKind.Sequential)] [NativeContainer] [NativeContainerIsReadOnly] [DebuggerDisplay("Length = {Length}")] unsafe struct ReadOnlyNativeArray where T : unmanaged { [NativeDisableUnsafePtrRestriction] internal void* m_Buffer; internal int m_Length; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif internal ReadOnlyNativeArray(NativeArray nativeArray) { m_Buffer = nativeArray.GetUnsafeReadOnlyPtr(); m_Length = nativeArray.Length; #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(nativeArray); #endif } #if ENABLE_UNITY_COLLECTIONS_CHECKS internal ReadOnlyNativeArray(void* buffer, int length, ref AtomicSafetyHandle safety) { m_Buffer = buffer; m_Length = length; m_Safety = safety; } #else internal ReadOnlyNativeArray(void* buffer, int length) { m_Buffer = buffer; m_Length = length; } #endif public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_Length; } public ReadOnlyNativeArray GetSubArray(int start, int length) { CheckGetSubArrayArguments(start, length); #if ENABLE_UNITY_COLLECTIONS_CHECKS return new ReadOnlyNativeArray( ((byte*)m_Buffer) + ((long)UnsafeUtility.SizeOf()) * start, length, ref m_Safety); #else return new ReadOnlyNativeArray( ((byte*)m_Buffer) + ((long)UnsafeUtility.SizeOf()) * start, length); #endif } public NativeSlice ToSlice() { var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(m_Buffer, m_Length, Allocator.None); #if ENABLE_UNITY_COLLECTIONS_CHECKS NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, m_Safety); #endif return array.Slice(); } public NativeArray.ReadOnly AsNativeArrayReadOnly() { var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(m_Buffer, m_Length, Allocator.None); #if ENABLE_UNITY_COLLECTIONS_CHECKS NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, m_Safety); #endif return array.AsReadOnly(); } public ReadOnlyNativeStridedArray ToStrided(int offset, int count, int byteStride) where TTarget : unmanaged { return new ReadOnlyNativeStridedArray( m_Buffer, Length * UnsafeUtility.SizeOf(), offset, count, byteStride #if ENABLE_UNITY_COLLECTIONS_CHECKS ,ref m_Safety #endif ); } public void* GetUnsafeReadOnlyPtr() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Buffer; } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckGetSubArrayArguments(int start, int length) { if (start < 0) { throw new ArgumentOutOfRangeException(nameof(start), "start must be >= 0"); } if (start + length > Length) { throw new ArgumentOutOfRangeException(nameof(length), $"sub array range {start}-{start + length - 1} is outside the range of the native array 0-{Length - 1}"); } if (start + length < 0) { throw new ArgumentException($"sub array range {start}-{start + length - 1} caused an integer overflow and is outside the range of the native array 0-{Length - 1}"); } } public void CopyTo(NativeArray array) => Copy(this, array); public ReadOnlyNativeArray Reinterpret() where TTarget : unmanaged { long tSize = UnsafeUtility.SizeOf(); long uSize = UnsafeUtility.SizeOf(); var byteLen = Length * tSize; var uLen = byteLen / uSize; CheckReinterpretSize(uSize, byteLen, uLen); #if ENABLE_UNITY_COLLECTIONS_CHECKS return new ReadOnlyNativeArray(m_Buffer, (int)uLen, ref m_Safety); #else return new ReadOnlyNativeArray(m_Buffer, (int)uLen); #endif } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckReinterpretSize(long uSize, long byteLen, long uLen) { if (uLen * uSize != byteLen) { throw new InvalidOperationException($"Types {typeof(T)} (array length {Length}) and {typeof(TTarget)} cannot be aliased due to size constraints. The size of the types and lengths involved must line up."); } } public T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { CheckElementReadAccess(index); return UnsafeUtility.ReadArrayElement(m_Buffer, index); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] [MethodImpl(MethodImplOptions.AggressiveInlining)] void CheckElementReadAccess(int index) { if ((uint)index >= (uint)m_Length) { throw new IndexOutOfRangeException($"Index {index} is out of range (must be between 0 and {m_Length - 1})."); } #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } public bool IsCreated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_Buffer != null; } static void Copy(ReadOnlyNativeArray src, NativeArray dst) { CheckCopyLengths(src.Length, dst.Length); #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(src.m_Safety); var dstSafetyHandle = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(dst); AtomicSafetyHandle.CheckWriteAndThrow(dstSafetyHandle); #endif var dstPointer = (byte*)dst.GetUnsafePtr(); UnsafeUtility.MemCpy( dstPointer, (byte*)src.m_Buffer, src.Length * UnsafeUtility.SizeOf()); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] static void CheckCopyLengths(int srcLength, int dstLength) { if (srcLength != dstLength) throw new ArgumentException("source and destination length must be the same"); } } }