NativeList clarification

Hi, guys. I’ve realized recently that I might be using nativeLists wrong, and was hoping for some clarification. Using the following code as an example, I was under the impression that with a persistent nativeList, when you reassigned the list (as in line 5), it disposed of the previous one to clear up memory. Is this correct, or do I need to explicitly dispose of the list and create a new one?

NativeList<float3> newList= new NativeList<float3>(Allocator.Persistent);

    private void Update()
    {
        newList = someOtherList;
    }

.

A NativeList is just a pointer to unmanaged memory. So when you assign something to a NativeList variable, you are overwriting the pointer, leaving the original buffer unreferenced but still allocated. That’s a memory leak.

The behavior you assumed is common in C++, and in managed C# the garbage collector would automatically clean up the memory leak. But here you need to dispose the NativeList manually.

2 Likes

Ok, thanks for that. So, would that look something like this?

NativeList<float3> newList= new NativeList<float3>(Allocator.Persistent);

    private void Update()
    {
        newList.Clear();
        newList = new NativeList<float3>(Allocator.Persistent);

        newList = someOtherList;
    }

When working with garbage collected (managed) types we are used to them being cleared up for us once nothing is holding a reference to them. However native collections (like NativeList) are not garbage collected and so we have to clear them up ourselves.

The most common way to do that is like this: someCollection.Dispose()

A case that often comes up is that you have a native collection that you pass to a job but that you don’t need afterwards. You could do this:

var someJobHandle = someJob.Schedule();
... and later ...
someJobHandle.Complete();
someCollection.Dispose()

But there is often a cleaner way. Some collections allow you to pass a JobHandle to their Dispose method. This will free if once the job completes.

var someJobHandle = someJob.Schedule();
someCollection.Dispose(someJobHandle); // will be disposed when the job completes
... and later ...
someJobHandle.Complete();

There is also an attribute that can free some collections when a job completes, but I don’t recall a place where that is more useful than the Dispose(JobHandle) overload.

1 Like

One extra, slightly related, thing that can be confusing is that Dispose will likely not cause IsCreated to become false.

The reason is a little technical. Native collections are structs that hold references to unmanaged memory (memory not managed by the GC). Because they are structs they are ‘value types’ in c# and so are copied on assignment (The memory they point to is not copied however). When you call Dispose on a NativeCollection it will do the cleanup and some internal stuff that means IsCreated will be false. However other copies of the struct will still have IsCreated as true.

Now this is not a big deal as the memory has been disposed, the other copies of the struct aren’t valid any more. However I thought I’d bring it up as I remember this confusing me when I first saw it in the debugger when learning about native collections.

1 Like

No. Now you have 2 dangling lists (memory leaks), and you just cleared one of them for no reason, and created a new one (also for reason)

NativeList<float3> newList= new NativeList<float3>(Allocator.Persistent);

    private void Update()
    {
        newList.Dispose();
        newList = someOtherList; //where did you allocate someOtherList? What Allocator was used?
    }
1 Like

Great answers, guys! Much appreciated. I definitely feel like I’m getting closer to understanding what’s going on. I still feel like there is one more piece to the puzzle I’m missing though. So, using the following code as an example, have I created two separate lists with two separate memory allocations that just happen to hold the same information so that when I dispose of newList, someOtherList remains untouched? Or when I set one list equal to the other am I actually pointing it towards the same chunk of memory so that when I dispose of either newList or someOtherList, both are empty?

    NativeList<float3> newList = new NativeList<float3>(Allocator.Persistent);
    NativeList<float3> someOtherList = new NativeList<float3>(Allocator.Persistent);
    //Add some stuff to someOtherList

    private void Update()
    {       
        newList = someOtherList;
        newList.Dispose();  //does this clear both newList and someOtherList?
    }

That will delete only the NativeList first assigned tosomeOtherList and leak the one first assigned to newList.

You need to understand that a NativeList stores a pointer (aka: an address) to memory. Setting newList = someOtherList only makes newList now also point to the address that is stored someOtherList, it does not change anything in someOtherList, and does not do anything to the original pointer held by newList, and definitely does not about copying the contents from a memory address to another.

1 Like

Gotcha, that makes sense. So really to setup separate identical list in a separate chunk of memory, you would need to do something like this?

        NativeList<float3> newList = new NativeList<float3>(Allocator.Persistent);
        NativeList<float3> someOtherList = new NativeList<float3>(Allocator.Persistent);
        //Add some stuff to someOtherList

        private void Update()
        {
            newList.Clear();
            foreach (float3 position in someOtherList) 
            { //Copies someOtherList over without pointing newList to that chunk of memory?
                newList.Add(position);
            }
        }
2 Likes

Yes. Or AddRange().

1 Like