Highlights:
- cast
T[]toNativeArray<T> - schedule jobs that read/write to managed arrays
- pointer safety assertion that prevent crashes and data corruption
more info in the first comment under the source code
| // src* https://gist.github.com/andrew-raphael-lukasik/812e152f95e38cc13c44e5040c5739bc | |
| using UnityEngine; | |
| using Unity.Collections; | |
| using Unity.Collections.LowLevel.Unsafe; | |
| using Unity.Profiling; | |
| public static class ArrayExtensionMethods | |
| { | |
| /// <summary> | |
| /// Pins this GC array and turns it's pointer into a NativeArray. | |
| /// Do not Dispose but call UnsafeUtility.ReleaseGCObject(gcHandle) when done with it. | |
| /// </summary> | |
| public static unsafe NativeArray<T> AsNativeArray<T>(this T[] array, out ulong gcHandle) where T : unmanaged | |
| { | |
| void* ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out gcHandle); | |
| var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(ptr, array.Length, Allocator.None); | |
| #if ENABLE_UNITY_COLLECTIONS_CHECKS | |
| NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); | |
| #endif | |
| return nativeArray; | |
| } | |
| /// <inheritdoc /> | |
| public static unsafe NativeArray<T> AsNativeArray<T>(this T[,] array, out ulong gcHandle) where T : unmanaged | |
| { | |
| void* ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out gcHandle); | |
| var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(ptr, array.Length, Allocator.None); | |
| #if ENABLE_UNITY_COLLECTIONS_CHECKS | |
| NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); | |
| #endif | |
| return nativeArray; | |
| } | |
| /// <inheritdoc /> | |
| public static unsafe NativeArray<T> AsNativeArray<T>(this T[,,] array, out ulong gcHandle) where T : unmanaged | |
| { | |
| void* ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out gcHandle); | |
| var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(ptr, array.Length, Allocator.None); | |
| #if ENABLE_UNITY_COLLECTIONS_CHECKS | |
| NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); | |
| #endif | |
| return nativeArray; | |
| } | |
| /// <inheritdoc /> | |
| public static unsafe NativeArray<(T1,T2)> AsNativeArray<T1,T2>(this (T1,T2)[] array, out ulong gcHandle) | |
| where T1 : unmanaged | |
| where T2 : unmanaged | |
| { | |
| void* ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out gcHandle); | |
| var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<(T1,T2)>(ptr, array.Length, Allocator.None); | |
| #if ENABLE_UNITY_COLLECTIONS_CHECKS | |
| NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); | |
| #endif | |
| return nativeArray; | |
| } | |
| /// <inheritdoc /> | |
| public static unsafe NativeArray<(T1,T2,T3)> AsNativeArray<T1,T2,T3>(this (T1,T2,T3)[] array, out ulong gcHandle) | |
| where T1 : unmanaged | |
| where T2 : unmanaged | |
| where T3 : unmanaged | |
| { | |
| void* ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out gcHandle); | |
| var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<(T1,T2,T3)>(ptr, array.Length, Allocator.None); | |
| #if ENABLE_UNITY_COLLECTIONS_CHECKS | |
| NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.Create()); | |
| #endif | |
| return nativeArray; | |
| } | |
| public static string ToReadableString<T>(this T[] arr) | |
| { | |
| if (arr.Length == 0) return "()"; | |
| var sb = new System.Text.StringBuilder(); | |
| sb.Append($"({arr[0]}"); | |
| for (int i = 1; i < arr.Length; i++) | |
| sb.Append($",{arr[i]}"); | |
| sb.Append(')'); | |
| return sb.ToString(); | |
| } | |
| public static string ToReadableString<T>(this T[,] arr) | |
| { | |
| int | |
| lengthY = arr.GetLength(0), | |
| lengthX = arr.GetLength(1), | |
| length = arr.Length; | |
| var sb = new System.Text.StringBuilder($"[{lengthY},{lengthX}]( "); | |
| for (int y = 0; y < lengthY; y++) | |
| { | |
| if (y != 0) | |
| sb.Append(" , "); | |
| if (lengthX != 0) | |
| sb.Append($"({arr[y, 0]}"); | |
| for (int x = 1; x < lengthX; x++) | |
| sb.Append($",{arr[y, x]}"); | |
| sb.Append(')'); | |
| } | |
| sb.Append($" )"); | |
| return sb.ToString(); | |
| } | |
| public static string ToReadableString<T>(this T[,,] arr) | |
| { | |
| int | |
| lengthZ = arr.GetLength(0), | |
| lengthY = arr.GetLength(1), | |
| lengthX = arr.GetLength(2), | |
| length = arr.Length; | |
| var sb = new System.Text.StringBuilder($"[{lengthZ},{lengthY},{lengthX}]( "); | |
| for (int z = 0; z < lengthZ; z++) | |
| { | |
| if (z != 0) sb.Append(" , "); | |
| sb.Append("( "); | |
| for (int y = 0; y < lengthY; y++) | |
| { | |
| if (y != 0) | |
| sb.Append(" , "); | |
| if (lengthX != 0) | |
| sb.Append($"({arr[z, y, 0]}"); | |
| for (int x = 1; x < lengthX; x++) | |
| sb.Append($",{arr[z, y, x]}"); | |
| sb.Append(')'); | |
| } | |
| sb.Append(" )"); | |
| } | |
| sb.Append($" )"); | |
| return sb.ToString(); | |
| } | |
| } |
| // src* https://gist.github.com/andrew-raphael-lukasik/812e152f95e38cc13c44e5040c5739bc | |
| using Unity.Jobs; | |
| public static class JobUtility | |
| { | |
| /// <summary> | |
| /// Schedules a job that calls UnsafeUtility.ReleaseGCObject(gcHandle). | |
| /// </summary> | |
| public static JobHandle ReleaseGCObject(ulong gcHandle, JobHandle dependency = default) | |
| => new ReleaseGCObjectJob(gcHandle).Schedule(dependency); | |
| /// <summary> | |
| /// This equation is yet to be (im)proved experimentally, the current version is merely an initial guesswork | |
| /// </summary> | |
| public static int OptimalLoopBatchCount(int length) | |
| => Unity.Mathematics.math.max(length / (UnityEngine.SystemInfo.processorCount * 3), 1); | |
| } |
| // src* https://gist.github.com/andrew-raphael-lukasik/812e152f95e38cc13c44e5040c5739bc | |
| using UnityEngine; | |
| using Unity.Collections; | |
| using Unity.Collections.LowLevel.Unsafe; | |
| using Unity.Jobs; | |
| using Unity.Profiling; | |
| public static class NativeArrayExtensionMethods | |
| { | |
| static readonly ProfilerMarker | |
| ___CopyTo = new ProfilerMarker("CopyTo"), | |
| ___CopyFrom = new ProfilerMarker("CopyFrom"); | |
| public static unsafe void CopyTo<T>(this NativeArray<T> src, T[,] dst) where T : unmanaged | |
| { | |
| ___CopyTo.Begin(); | |
| if (src.Length == dst.Length) | |
| { | |
| int size = src.Length * UnsafeUtility.SizeOf<T>(); | |
| void* srcPtr = NativeArrayUnsafeUtility.GetUnsafePtr(src); | |
| void* dstPtr = UnsafeUtility.PinGCArrayAndGetDataAddress(dst, out ulong dstHandle); | |
| UnsafeUtility.MemCpy(destination: dstPtr, source: srcPtr, size: size); | |
| UnsafeUtility.ReleaseGCObject(dstHandle); | |
| } | |
| else Debug.LogError($"<b>{nameof(src)}.Length</b> ({src}[b]) and <b>{nameof(dst)}.Length</b> ({dst.Length}[b]) must be equal. MemCpy aborted."); | |
| ___CopyTo.End(); | |
| } | |
| public static unsafe void CopyTo<T>(this NativeArray<T> src, T[,,] dst) where T : unmanaged | |
| { | |
| ___CopyTo.Begin(); | |
| if (src.Length == dst.Length) | |
| { | |
| int size = src.Length * UnsafeUtility.SizeOf<T>(); | |
| void* srcPtr = NativeArrayUnsafeUtility.GetUnsafePtr(src); | |
| void* dstPtr = UnsafeUtility.PinGCArrayAndGetDataAddress(dst, out ulong dstHandle); | |
| UnsafeUtility.MemCpy(destination: dstPtr, source: srcPtr, size: size); | |
| UnsafeUtility.ReleaseGCObject(dstHandle); | |
| } | |
| else Debug.LogError($"<b>{nameof(src)}.Length</b> ({src}[b]) and <b>{nameof(dst)}.Length</b> ({dst.Length}[b]) must be equal. MemCpy aborted."); | |
| ___CopyTo.End(); | |
| } | |
| public static unsafe void CopyFrom<T>(this NativeArray<T> dst, T[,] src) where T : unmanaged | |
| { | |
| ___CopyFrom.Begin(); | |
| if (src.Length == dst.Length) | |
| { | |
| int size = src.Length * UnsafeUtility.SizeOf<T>(); | |
| void* srcPtr = UnsafeUtility.PinGCArrayAndGetDataAddress(src, out ulong dstHandle); | |
| void* dstPtr = NativeArrayUnsafeUtility.GetUnsafePtr(dst); | |
| UnsafeUtility.MemCpy(destination: dstPtr, source: srcPtr, size: size); | |
| UnsafeUtility.ReleaseGCObject(dstHandle); | |
| } | |
| else Debug.LogError($"<b>{nameof(src)}.Length</b> ({src}[b]) and <b>{nameof(dst)}.Length</b> ({dst.Length}[b]) must be equal. MemCpy aborted."); | |
| ___CopyFrom.End(); | |
| } | |
| public static unsafe void CopyFrom<T>(this NativeArray<T> dst, T[,,] src) where T : unmanaged | |
| { | |
| ___CopyFrom.Begin(); | |
| if (src.Length == dst.Length) | |
| { | |
| int size = src.Length * UnsafeUtility.SizeOf<T>(); | |
| void* srcPtr = UnsafeUtility.PinGCArrayAndGetDataAddress(src, out ulong dstHandle); | |
| void* dstPtr = NativeArrayUnsafeUtility.GetUnsafePtr(dst); | |
| UnsafeUtility.MemCpy(destination: dstPtr, source: srcPtr, size: size); | |
| UnsafeUtility.ReleaseGCObject(dstHandle); | |
| } | |
| else Debug.LogError($"<b>{nameof(src)}.Length</b> ({src}[b]) and <b>{nameof(dst)}.Length</b> ({dst.Length}[b]) must be equal. MemCpy aborted."); | |
| ___CopyFrom.End(); | |
| } | |
| /// <summary> Schedules a <see cref="CopyToJob{T}"/>. </summary> | |
| [Unity.Burst.BurstDiscard]// Burst warning without it, not sure why | |
| public static JobHandle CopyTo<T>(this NativeArray<T> src, NativeArray<T> dst, JobHandle dependency) where T : unmanaged | |
| => new CopyToJob<T>(src,dst).Schedule(dependency); | |
| /// <summary> Schedules a <see cref="CopyToJob{T}"/>. </summary> | |
| public static JobHandle CopyFrom<T>(this NativeArray<T> dst, NativeArray<T> src, JobHandle dependency) where T : unmanaged | |
| => new CopyToJob<T>(src,dst).Schedule(dependency); | |
| /// <summary> Fills entire array using given value. </summary> | |
| public static unsafe void Fill<T>(this NativeArray<T> Array, T value) where T : unmanaged | |
| { | |
| void* src = UnsafeUtility.Malloc(UnsafeUtility.SizeOf<T>(), UnsafeUtility.AlignOf<T>(), Allocator.Temp); | |
| void* dst = NativeArrayUnsafeUtility.GetUnsafePtr<T>(Array); | |
| int size = UnsafeUtility.SizeOf<T>(); | |
| int count = Array.Length; | |
| UnsafeUtility.MemCpyReplicate(destination: dst, source: src, size: size, count: count); | |
| } | |
| /// <summary> | |
| /// Raises an exception when given pointer refers to address outside this array. | |
| /// This makes sure this pointer won't crash the game. | |
| /// <b>NOTE</b>: Editor and DEBUG builds only. | |
| /// </summary> | |
| [System.Diagnostics.Conditional("DEBUG")] | |
| public static unsafe void AssertPtrScope<ARRAY_ITEM, POINTER>(this NativeSlice<ARRAY_ITEM> array, POINTER* ptr) | |
| where ARRAY_ITEM : unmanaged | |
| where POINTER : unmanaged | |
| { | |
| long addr = (long)ptr; | |
| long firstItemAddr = (long)NativeSliceUnsafeUtility.GetUnsafeReadOnlyPtr(array); | |
| long lastItemAddr = firstItemAddr + array.Length * (long)UnsafeUtility.SizeOf<ARRAY_ITEM>() - UnsafeUtility.SizeOf<POINTER>(); | |
| if (!(addr >= firstItemAddr && addr < lastItemAddr)) | |
| { | |
| string message = $"Pointer is out of scope, so considered unsafe (possible crash prevented). Ptr:{addr}, array first item addr:{firstItemAddr}, array last item addr:{lastItemAddr}"; | |
| Debug.LogWarning(message); | |
| throw new System.ArgumentOutOfRangeException(message); | |
| } | |
| } | |
| /// <inheritdoc /> | |
| [System.Diagnostics.Conditional("DEBUG")] | |
| public static unsafe void AssertPtrScope<ARRAY_ITEM, POINTER>(this NativeArray<ARRAY_ITEM> array, POINTER* ptr) | |
| where ARRAY_ITEM : unmanaged | |
| where POINTER : unmanaged | |
| { | |
| long addr = (long)ptr; | |
| long firstItemAddr = (long)NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(array); | |
| long lastItemAddr = firstItemAddr + array.Length * (long)UnsafeUtility.SizeOf<ARRAY_ITEM>() - UnsafeUtility.SizeOf<POINTER>(); | |
| if (!(addr >= firstItemAddr && addr <= lastItemAddr)) | |
| { | |
| string message = $"Pointer is out of scope, so considered unsafe (possible crash prevented). Ptr:{addr}, array first item addr:{firstItemAddr}, array last item addr:{lastItemAddr}"; | |
| Debug.LogWarning(message); | |
| throw new System.ArgumentOutOfRangeException(message); | |
| } | |
| } | |
| public static string ToReadableString<T>(this NativeArray<T> arr) where T : unmanaged | |
| { | |
| if (arr.Length == 0) return "()"; | |
| var sb = new System.Text.StringBuilder(); | |
| sb.Append($"({arr[0]}"); | |
| for (int i = 1; i < arr.Length; i++) | |
| sb.Append($",{arr[i]}"); | |
| sb.Append(')'); | |
| return sb.ToString(); | |
| } | |
| } |
| // src* https://gist.github.com/andrew-raphael-lukasik/812e152f95e38cc13c44e5040c5739bc | |
| using Unity.Collections; | |
| using Unity.Collections.LowLevel.Unsafe; | |
| using Unity.Jobs; | |
| [Unity.Burst.BurstCompile] | |
| public struct DeallocateArrayJob<T> : IJob where T : unmanaged | |
| { | |
| [ReadOnly] [DeallocateOnJobCompletion] NativeArray<T> Array; | |
| public DeallocateArrayJob(NativeArray<T> array) => this.Array = array; | |
| void IJob.Execute() { } | |
| } | |
| [Unity.Burst.BurstCompile] | |
| public struct FillJob<T> : IJob where T : unmanaged | |
| { | |
| T Value; | |
| [WriteOnly] NativeArray<T> Array; | |
| public FillJob(NativeArray<T> array, T value) | |
| { | |
| this.Value = value; | |
| this.Array = array; | |
| } | |
| unsafe void IJob.Execute() | |
| { | |
| void* src = UnsafeUtility.Malloc(UnsafeUtility.SizeOf<T>(), UnsafeUtility.AlignOf<T>(), Allocator.Temp); | |
| void* dst = NativeArrayUnsafeUtility.GetUnsafePtr<T>(Array); | |
| int size = UnsafeUtility.SizeOf<T>(); | |
| int count = this.Array.Length; | |
| UnsafeUtility.MemCpyReplicate(destination: dst, source: src, size: size, count: count); | |
| } | |
| } | |
| [Unity.Burst.BurstCompile] | |
| public struct CopyToJob<T> : IJob where T : unmanaged | |
| { | |
| [ReadOnly] NativeArray<T> Src; | |
| [WriteOnly] NativeArray<T> Dst; | |
| int SrcIndex, DstIndex, Length; | |
| public CopyToJob(NativeArray<T> src, NativeArray<T> dst) | |
| { | |
| this.Src = src; | |
| this.SrcIndex = -1; | |
| this.Dst = dst; | |
| this.DstIndex = -1; | |
| this.Length = -1; | |
| } | |
| public CopyToJob(NativeArray<T> src, int srcIndex, NativeArray<T> dst, int dstIndex, int length) | |
| { | |
| this.Src = src; | |
| this.SrcIndex = srcIndex; | |
| this.Dst = dst; | |
| this.DstIndex = dstIndex; | |
| this.Length = length; | |
| } | |
| void IJob.Execute() | |
| { | |
| if (Length == -1) NativeArray<T>.Copy(Src, Dst); | |
| else NativeArray<T>.Copy(Src, SrcIndex, Dst, DstIndex, Length); | |
| } | |
| } | |
| [Unity.Burst.BurstCompile] | |
| public struct ReleaseGCObjectJob : IJob | |
| { | |
| ulong Handle; | |
| public ReleaseGCObjectJob(ulong handle) => this.Handle = handle; | |
| void IJob.Execute() => UnsafeUtility.ReleaseGCObject(Handle); | |
| } |
Hi @andrew-raphael-lukasik , I've had your gist laying around for a while and finally had a need to use the Fill method. However, it seems to be bugged.
it doesn't actually use the
valuethat is passed in, and instead seems to fillArraywith copies of whatever garbagesrcwas allocated with. I assume you meant to then copyvaluetosrc- though I'm wondering why you're allocating new data as opposed to getting a pointer tovaluedirectly. For example, this does appear to give correct results: