Makes sense, thank you for confirming my suspicion.
Got another question. When overriding an UnsafeList, do I need to dispose the original set of data, then use AddRange with the new set of data? Example:
DataB myData = new DataB(…);
UnsafeList<float3> points = myData.pts;
for(…){…modify points…}
// Override Option A
myData.pts = points;
// Override Option B
myData.pts.Dispose();
myData.pts = points;
// Override Option C
myData.pts.Clear();
myData.pts.Resize(points.length);
myData.pts.AddRangeNoResize(pts);
points.Dispose();
Since you assigned points = myData.pts, you’re modifying the same list. Unless the list is reallocated because it runs out of capacity, in that case you need to re-assign your list back to myData (like in option A). This is a quirk of UnsafeList<T> (the pointer to the allocated list is stored in the struct without any indirection, and by assigning the struct to a variable you’re making a copy of it). NativeList<T> is less error prone because it stores the list data in a separate allocation. Note that if you avoid copying the struct, eg. just modify/resize myData.pts directly, everything will work just fine.
If you want to copy the contents of one list to another list, you can just use Clear and then AddRange. AddRange automatically checks that the list has enough capacity and resizes if necessary. AddRangeNoResize is useful to avoid capacity checks when combining multiple lists - you can set the UnsafeList<T>.Capacity beforehand as an optimization. Keep in mind that Resizesets the length of the list, not capacity (although the capacity will be adjusted if the allocated list is too short). This means that in Option C you’d be appending your points at the end of the list that already contains points.length elements.
By the way, if you want to know how any of the native collections work, I recommend that you use the “go to definition” functionality of your IDE of choice (usually the F12 key). It should take you directly to the implementation in the com.unity.collections package. Some of the code is well-documented. This helped me a lot in deciphering what these methods do. For example, the definition for UnsafeList<T>.AddRange looks like this:
/// <summary>
/// Adds elements from a list to this list.
/// </summary>
/// <remarks>
/// If the list has reached its current capacity, it copies the original, internal array to
/// a new, larger array, and then deallocates the original.
/// </remarks>
/// <typeparam name="T">Source type of elements</typeparam>
/// <param name="list">Other container to copy elements from.</param>
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
public void AddRange<T>(UnsafeList list) where T : struct
{
AddRange(UnsafeUtility.SizeOf<T>(), UnsafeUtility.AlignOf<T>(), list.Ptr, list.Length);
}
That’s what I was using, but I just get a summary of that file. But I realize now I can just open the file in full after navigating the Packages directory. Probably an IDE setting somewhere to change this behavior. Thank you.
This is why I strongly recommend any unity dev switch to rider or at least use resharper with vs. It will take you to full source code with documentation instead of a summary .
Alternatively you can check “Generate .csproj files for registery packages” in Preferences/External Tools. And you will be able to see source code without using rider.
I have a bunch of extension classes for each type of native container to handle this for me. Just copy paste the following for each native container type you want to work and change the type.
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
namespace Engine.Common.Extensions
{
public static class NativeArrayExtensions
{
public static void DisposeIfCreated<T>(this NativeArray<T> array) where T : unmanaged
{
if (array.IsCreated)
array.Dispose();
}
public static JobHandle DisposeIfCreated<T>(this NativeArray<T> array, JobHandle jobHandle) where T : unmanaged
{
if (array.IsCreated)
return array.Dispose(jobHandle);
return jobHandle;
}
public static void DisposeAllIfCreated<T>(this NativeArray<T> array) where T : unmanaged, INativeDisposable
{
if (!array.IsCreated)
return;
for (int i = 0; i < array.Length; i++)
{
var item = array[i];
item.Dispose();
}
array.Dispose();
}
public static JobHandle DisposeAllIfCreated<T>(this NativeArray<T> array, JobHandle jobHandle)
where T : unmanaged, INativeDisposable
{
if (!array.IsCreated)
return jobHandle;
for (int i = 0; i < array.Length; i++)
{
var item = array[i];
jobHandle = item.Dispose(jobHandle);
}
return array.Dispose(jobHandle);
}
public static unsafe UnsafeList<T> AsUnsafeList<T>(this NativeArray<T> array) where T : unmanaged
{
return new UnsafeList<T>((T*)array.GetUnsafePtr(), sizeof(T) * array.Length);
}
}
}