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