Nested Unsafe Collections Disposal Question

If I invoke Dispose() on an instance of MyDataSet, will Dispose() be invoked recursively by default or will I need to handle that manually?

public struct DataA : IDisposable {
   
    UnsafeList<DataB> subDatas;
   
    public void Dispose(){
        subDatas.Dispose();
    }
}

public struct DataB : IDisposable {
   
    UnsafeList<float3> pts;
   
    public  void Dispose(){
        pts.Dispose();
    }
}

public struct MyDataSet : IDisposable {

    UnsafeList<DataA> datas;
   
    public void Dispose(){
        datas.Dispose();
    }
}

The Dispose method is not magical in any way compared to other methods you write. You need to dispose all allocations explicitly ( for now ).

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();

I’m not sure that I understand your question correctly, but here we go.

DataB myData = new DataB(…);
UnsafeList<float3> points = myData.pts;
for(…){…modify points…}

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 Resize sets 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.

1 Like

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 .

4 Likes

Were I to have the money for it, I would have abandoned VS years ago.

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.

7501220--924194--upload_2021-9-16_22-57-6.png

5 Likes

Perfect. Thank you.

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);
        }
    }
}
1 Like