using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Runtime.InteropServices; using System; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; namespace Ximmerse.XR.Collections { struct InternalPointer { public IntPtr m_Pointer; } /// /// Native list which compares to Unity.Collections.NativeList, this type is able to be used as generic type to Unity's native array or native list collection types. /// /// Make sure Dispose() is called properly. /// /// Note: foreach() has GC problem due to type casting, using for() in most case. /// /// /// internal unsafe struct xNativeList : IDisposable, IEnumerable, IList, IReadOnlyList where T : unmanaged { const int kDefaultIncrementElement = 8; private const string kErrorMsgFormat01 = "Invalid index: {0} of accessing array length = {1}"; private const string kErrorMsg02 = "PENativeList is not created !"; /// /// Length of int, should be 4 in x64 platform. /// static readonly int kLengthInt = Marshal.SizeOf(); /// /// Double kLengthInt. /// static readonly int kLengthTwoInt = 2 * Marshal.SizeOf(); /// /// Capacity of the list /// public int Capacity { get { CheckSafety(); return Marshal.ReadInt32(m_InternalPointer->m_Pointer); } set { CheckSafety(); Marshal.WriteInt32(m_InternalPointer->m_Pointer, value); } } /// /// Element length of the list /// public int Length { get { CheckSafety(); return Marshal.ReadInt32(m_InternalPointer->m_Pointer, kLengthInt); } private set { CheckSafety(); Marshal.WriteInt32(m_InternalPointer->m_Pointer, kLengthInt, value); } } int m_ElementSize; /// /// Element size in memory bytes. /// public int ElementSize { get { if (m_ElementSize == 0) { m_ElementSize = Marshal.SizeOf(); } return m_ElementSize; } } InternalPointer* m_InternalPointer; /// /// Gets the pointer of the list memory. /// First int(32bits, or 4 bytes) = capacity. /// Second int(32bits, or 4 bytes) = length. /// public IntPtr Pointer { get { CheckSafety(); return m_InternalPointer->m_Pointer; } } /// /// Gets the data pointer of the list. The capacity / length is skipped . /// The pointer should be used for reading or modifying data only. /// DO NOT INSERT/DELETE DATA BY THIS POINTER ! /// public IntPtr ReadOnlyDataPointer { get { CheckSafety(); return IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt); } } bool m_IsCreated; /// /// If the native list has been allocated. /// public bool IsCreated { get => m_IsCreated; } /// /// Element count of the list /// public int Count => Length; /// /// Is the list readonly ? /// public bool IsReadOnly => false; /// /// Create a new PENativeList with initial capacity. /// /// /// public static xNativeList Create(int Capacity) { xNativeList list = default; list.m_ElementSize = Marshal.SizeOf(); int sizeOfInterPtr = Marshal.SizeOf(); list.m_InternalPointer = (InternalPointer*)Marshal.AllocHGlobal(sizeOfInterPtr).ToPointer(); list.m_InternalPointer->m_Pointer = Marshal.AllocHGlobal(list.m_ElementSize * Capacity + kLengthTwoInt); //[capacity] | [length] | [content] Marshal.WriteInt32(list.m_InternalPointer->m_Pointer, Capacity); Marshal.WriteInt32(list.m_InternalPointer->m_Pointer, kLengthInt, 0); list.m_IsCreated = true; return list; } /// /// Create a new PENativeList by given array /// /// /// public static xNativeList Create(NativeArray nativeArray) { xNativeList list = default; list.m_ElementSize = Marshal.SizeOf(); int lengthInByte = list.m_ElementSize * nativeArray.Length; list.m_InternalPointer = (InternalPointer*)Marshal.AllocHGlobal(Marshal.SizeOf()).ToPointer(); list.m_InternalPointer->m_Pointer = Marshal.AllocHGlobal(lengthInByte + kLengthTwoInt); //[capacity] | [length] | [content] Buffer.MemoryCopy(nativeArray.GetUnsafePtr(), IntPtr.Add(list.m_InternalPointer->m_Pointer, kLengthTwoInt).ToPointer(), lengthInByte, lengthInByte); Marshal.WriteInt32(list.m_InternalPointer->m_Pointer, nativeArray.Length);//capacity Marshal.WriteInt32(IntPtr.Add(list.m_InternalPointer->m_Pointer, kLengthInt), nativeArray.Length);//length list.m_IsCreated = true; return list; } /// /// Create a new PENativeList by given array /// /// /// public static xNativeList Create(T[] array) { xNativeList list = default; list.m_ElementSize = Marshal.SizeOf(); int lengthInByte = list.m_ElementSize * array.Length; list.m_InternalPointer = (InternalPointer*)Marshal.AllocHGlobal(Marshal.SizeOf()).ToPointer(); list.m_InternalPointer->m_Pointer = Marshal.AllocHGlobal(lengthInByte + kLengthTwoInt); //[capacity] | [length] | [content] IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0); Buffer.MemoryCopy(ptr.ToPointer(), IntPtr.Add(list.m_InternalPointer->m_Pointer, kLengthTwoInt).ToPointer(), lengthInByte, lengthInByte); Marshal.WriteInt32(list.m_InternalPointer->m_Pointer, array.Length);//capacity Marshal.WriteInt32(IntPtr.Add(list.m_InternalPointer->m_Pointer, kLengthInt), array.Length);//length list.m_IsCreated = true; return list; } /// /// Allocates by another native list. /// /// /// public static xNativeList Create(xNativeList source) { xNativeList list = default; list.m_ElementSize = Marshal.SizeOf(); int lengthInByte = list.m_ElementSize * source.Length; list.m_InternalPointer = (InternalPointer*)Marshal.AllocHGlobal(Marshal.SizeOf()).ToPointer(); list.m_InternalPointer->m_Pointer = Marshal.AllocHGlobal(lengthInByte + kLengthTwoInt); //[capacity] | [length] | [content] //从source pointer执行 memory copy: IntPtr ptr_source = source.m_InternalPointer->m_Pointer; Buffer.MemoryCopy(ptr_source.ToPointer(), (list.m_InternalPointer->m_Pointer).ToPointer(), lengthInByte + kLengthTwoInt, lengthInByte + kLengthTwoInt); list.m_IsCreated = true; return list; } /// /// Gets/sets array element. /// /// /// public T this[int index] { get { CheckSafety(); int length = this.Length; if (index < 0 || index >= length) { throw new ArgumentOutOfRangeException(string.Format(kErrorMsgFormat01, index, length)); } T val = Marshal.PtrToStructure(IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + index * ElementSize)); return val; } set { CheckSafety(); int length = this.Length; if (index < 0 || index >= length) { throw new ArgumentOutOfRangeException(string.Format(kErrorMsgFormat01, index, length)); } T* p = &value; Buffer.MemoryCopy(p, IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + index * ElementSize).ToPointer(), ElementSize, ElementSize); } } /// /// Appends a value to the list. /// /// public void Add(T value) { CheckSafety(); int length = this.Length; int capacity = this.Capacity; if (length >= capacity) { Reallocate(capacity + kDefaultIncrementElement); } T* p = &value; Buffer.MemoryCopy(p, IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + length * ElementSize).ToPointer(), ElementSize, ElementSize); Length = length + 1; } /// /// Add multiple elements. /// /// public void AddRange(T[] values) { CheckSafety(); int length = this.Length; int capacity = this.Capacity; if (length + values.Length >= capacity) { Reallocate(capacity + values.Length); } fixed (void* ptr = values) { Buffer.MemoryCopy(ptr, IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + length * ElementSize).ToPointer(), ElementSize * values.Length, ElementSize * values.Length); Length = length + values.Length; } } /// /// Add multiple elements. /// /// public void AddRange(NativeArray array) { void* src = array.GetUnsafePtr(); if (this.Length + array.Length > this.Capacity) { Reallocate(this.Length + array.Length); } int currentLength = this.Length; void* dst = IntPtr.Add(this.m_InternalPointer->m_Pointer, kLengthTwoInt + currentLength * ElementSize).ToPointer(); Buffer.MemoryCopy(src, dst, array.Length * ElementSize, array.Length * ElementSize); this.Length = array.Length + currentLength; } /// /// Remove element at index. /// Performance tips : remove from the last index has best performance than remove from the head. /// /// public void RemoveAt(int index) { CheckSafety(); int length = this.Length; if (index < 0 || index >= length) { throw new ArgumentOutOfRangeException(string.Format(kErrorMsgFormat01, index, length)); } if (index == length - 1) { Length = length - 1; } else { //preservedLength 是要保留的内容长度 int preservedLength = length - index; IntPtr slice = GetSlice(index + 1, preservedLength); Buffer.MemoryCopy( slice.ToPointer(), IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + index * ElementSize).ToPointer(), ElementSize * preservedLength, ElementSize * preservedLength); Marshal.FreeHGlobal(slice); Length = length - 1; } } /// /// Removes element /// /// public bool Remove(T value) { CheckSafety(); int length = this.Length; for (int i = length - 1; i >= 0; i--) { if (this[i].Equals(value)) { RemoveAt(i); return true; } } return false; } /// /// Convert the native list to native array. /// /// public NativeArray ToArray(Allocator allocator) { NativeArray array = new NativeArray(this.Length, allocator); xNativeList list = this; for (int i = 0, iMax = list.Count; i < iMax; i++) { array[i] = list[i]; } return array; } /// /// Clears all element /// public void Clear() { CheckSafety(); Length = 0; } /// /// Reallocate a longer memory, the exists content is copied to new allocated memory area.List length remains unchanged. /// Content length remains unchanged , while the list's capacity is extended. /// /// The element length. internal void Reallocate(int length) { IntPtr newBufferPtr = Marshal.AllocHGlobal(kLengthTwoInt + length * ElementSize); Buffer.MemoryCopy(m_InternalPointer->m_Pointer.ToPointer(), newBufferPtr.ToPointer(), kLengthTwoInt + ElementSize * Length, kLengthTwoInt + ElementSize * Length); Marshal.FreeHGlobal(m_InternalPointer->m_Pointer); m_InternalPointer->m_Pointer = newBufferPtr; Capacity = length; } /// /// Gets a slice inside the list. /// /// /// /// IntPtr GetSlice(int index, int length) { IntPtr slicePtr = Marshal.AllocHGlobal(length * ElementSize); IntPtr src = IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + index * ElementSize); Buffer.MemoryCopy(src.ToPointer(), slicePtr.ToPointer(), ElementSize * length, ElementSize * length); return slicePtr; } /// /// Gets the element's data pointer at the index /// /// /// /// public unsafe bool GetElementPtr(int index, out T* elementPtr) { elementPtr = default; if (this.Length <= index) { return false; } var ptr = IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + m_ElementSize * index); elementPtr = (T*)ptr.ToPointer(); return true; } /// /// Dispose the native list and deallocate the memory. /// public void Dispose() { if (IsCreated) { Marshal.FreeHGlobal(this.m_InternalPointer->m_Pointer); Marshal.FreeHGlobal(new IntPtr(m_InternalPointer)); m_IsCreated = false; } } /// /// Gets enumerator. /// /// public IEnumerator GetEnumerator() { CheckSafety(); return new NativeListEnumerator(this); } IEnumerator IEnumerable.GetEnumerator() { CheckSafety(); return new NativeListEnumerator(this); } /// /// Find index of the element /// /// /// public int IndexOf(T item) { CheckSafety(); int length = this.Length; for (int i = 0, imax = length; i < imax; i++) { if (this[i].Equals(item)) { return i; } } return -1; } /// /// Inserts item at the index. /// /// /// public void Insert(int index, T item) { CheckSafety(); int length = this.Length; int capacity = this.Capacity; if (length >= capacity) { Reallocate(capacity + kDefaultIncrementElement); } //先将 index 以后的元素后移一位: int preservedLength = length - index; IntPtr slice = GetSlice(index, preservedLength); Buffer.MemoryCopy( slice.ToPointer(), IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + (index + 1) * ElementSize).ToPointer(), ElementSize * preservedLength, ElementSize * preservedLength); Marshal.FreeHGlobal(slice); //然后将新的内容copy到 index 对应的位置 T* p = &item; Buffer.MemoryCopy(p, IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + index * ElementSize).ToPointer(), ElementSize, ElementSize); Length = length + 1; } /// /// Inserts items at the index. /// /// /// public void InsertValues(int index, T[] items) { CheckSafety(); int length = this.Length; int capacity = this.Capacity; if (length + items.Length >= capacity) { Reallocate(capacity + items.Length + kDefaultIncrementElement); } //先将 index 以后的元素后移N位: int preservedLength = length - index; IntPtr slice = GetSlice(index, preservedLength); Buffer.MemoryCopy( slice.ToPointer(), IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + (index + items.Length) * ElementSize).ToPointer(), ElementSize * preservedLength, ElementSize * preservedLength); Marshal.FreeHGlobal(slice); //然后将新的内容copy到 index 对应的位置 IntPtr srcPtr = Marshal.UnsafeAddrOfPinnedArrayElement(items, 0); Buffer.MemoryCopy(srcPtr.ToPointer(), IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + index * ElementSize).ToPointer(), ElementSize * items.Length, ElementSize * items.Length); Length = length + items.Length; } /// /// Inserts items at the index. /// /// /// public void InsertValues(int index, NativeArray items) { CheckSafety(); int length = this.Length; int capacity = this.Capacity; if (length + items.Length >= capacity) { Reallocate(capacity + items.Length + kDefaultIncrementElement); } //先将 index 以后的元素后移N位: int preservedLength = length - index; IntPtr slice = GetSlice(index, preservedLength); Buffer.MemoryCopy( slice.ToPointer(), IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + (index + items.Length) * ElementSize).ToPointer(), ElementSize * preservedLength, ElementSize * preservedLength); Marshal.FreeHGlobal(slice); //然后将新的内容copy到 index 对应的位置 void* srcPtr = items.GetUnsafePtr(); Buffer.MemoryCopy(srcPtr, IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt + index * ElementSize).ToPointer(), ElementSize * items.Length, ElementSize * items.Length); Length = length + items.Length; } /// /// If the item presents in the list /// /// /// public bool Contains(T item) { CheckSafety(); int length = this.Length; for (int i = 0, imax = length; i < imax; i++) { if (this[i].Equals(item)) { return true; } } return false; } /// /// Copy content to array /// /// /// public void CopyTo(T[] array, int arrayIndex) { CheckSafety(); int length = this.Length; if ((arrayIndex + length) > array.Length) { throw new ArgumentOutOfRangeException(string.Format("ArrayIndex + m_Length: {0} GE array length :{1}", arrayIndex + length, array.Length)); } if (length == 0) { return;//nothing to copy ... } fixed (void* dst = array) { IntPtr dstPtr = arrayIndex > 0 ? IntPtr.Add(new IntPtr(dst), ElementSize * arrayIndex) : new IntPtr(dst); Buffer.MemoryCopy(IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt).ToPointer(), dstPtr.ToPointer(), ElementSize * length, ElementSize * length); } } /// /// Copy content to array /// /// /// public void CopyTo(NativeArray nativeArray, int arrayIndex) { CheckSafety(); int length = this.Length; if ((arrayIndex + length) > nativeArray.Length) { throw new ArgumentOutOfRangeException(string.Format("ArrayIndex + m_Length: {0} GE array length :{1}", arrayIndex + length, nativeArray.Length)); } if (length == 0) { return;//nothing to copy ... } void* ptr = nativeArray.GetUnsafePtr(); Buffer.MemoryCopy(IntPtr.Add(m_InternalPointer->m_Pointer, kLengthTwoInt).ToPointer(), ptr, ElementSize * length, ElementSize * length); } private void CheckSafety() { if (!m_IsCreated) { throw new InvalidOperationException(kErrorMsg02); } } /// /// Enumerator for PENativeList /// internal struct NativeListEnumerator : IEnumerator where T : unmanaged { xNativeList m_list; int m_index; public NativeListEnumerator(xNativeList lst) { m_list = lst; m_index = -1; } public T Current => m_list[m_index]; object IEnumerator.Current => m_list[m_index]; public void Dispose() { } public bool MoveNext() { m_index++; int max = m_list.Length; if (m_index < max) { return true; } else return false; } public void Reset() { m_index = -1; } } } }