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.
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);
}
it doesn't actually use the value
that is passed in, and instead seems to fill Array
with copies of whatever garbage src
was allocated with. I assume you meant to then copy value
to src
- though I'm wondering why you're allocating new data as opposed to getting a pointer to value
directly. For example, this does appear to give correct results:
public static unsafe void Fill<T>(this NativeArray<T> Array, T value) where T : unmanaged
{
void* dst = NativeArrayUnsafeUtility.GetUnsafePtr<T>(Array);
int size = UnsafeUtility.SizeOf<T>();
int count = Array.Length;
void* src = UnsafeUtility.AddressOf(ref value); // Get the address of the value directly
UnsafeUtility.MemCpyReplicate(destination: dst, source: src, size: size, count: count);
}
Check this out: