NativeList<T>.AsArray() Bug?

NativeArray from NativeList.AsArray() access fail when the use after other NativeList allocation.

Code First! it can work as a Editor Test, with Unity.Collections referenced

using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Collections;

namespace Tests
{
    public class CollectionTest
    {

        [Test]
        public void ListAsArrayBug()
        {
            Unity.Mathematics.Random rand = new Unity.Mathematics.Random((uint)System.Math.Abs(System.DateTime.Now.Millisecond));
            var list = new NativeList<int>(Allocator.Temp);
            var size = rand.NextInt(1, 50);
            for (int i = 0; i < size; i++) list.Add(rand.NextInt(11));
            var asArray = list.AsArray();
            var sb = new System.Text.StringBuilder("Initial As Array|");
            for (int i = 0; i < size; i++) sb.Append($"{asArray[i]}|");
            Debug.Log(sb.ToString());
            sb.Clear();

            var otherSize = rand.NextInt(1, 50);
            var otherList = new NativeList<int>(4, Allocator.Temp);
            for (int i = 0; i < size; i++) otherList.Add(rand.NextInt(11));
            var e = Assert.Catch(() =>
            {
                sb.Append("As Array After Other List Allocate|");
                for (int i = 0; i < size; i++) sb.Append($"{asArray[i]}|");
                Debug.Log(sb.ToString());
            });
            Debug.Log("Array Access Failed");
            Debug.Log(e);
            sb.Clear();

            sb.Append("the List After Other List Allocate|");
            for (int i = 0; i < size; i++) sb.Append($"{list[i]}|");
            Debug.Log(sb.ToString());
            sb.Clear();

            sb.Append("New As Array After Other List Allocate|");
            asArray = list.AsArray();
            for (int i = 0; i < size; i++) sb.Append($"{asArray[i]}|");
            Debug.Log(sb.ToString());
            sb.Clear();

            sb.Append("via As Array After Other List Allocate|");
            for (int i = 0; i < size; i++) sb.Append($"{list.AsArray()[i]}|");
            Debug.Log(sb.ToString());

            if (list.IsCreated) list.Dispose();
            if (otherList.IsCreated) otherList.Dispose();
        }
    }
}

So it looks like the AsArray return by NativeList<T>.AsArray() can only be used “temporarily”.
By design, the AsArray should be invalidated when the original NativeList is manipulated.
But, from the code above the AsArray is invalidated even if some other irrelevant list is manipulated.

Is this a bug? or is it designed to work this way?
If this is by design what is the full scope that could invalidate an AsArray?

1 Like

Code Updated

using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Collections;

namespace Tests
{
    public class CollectionTest
    {

        [Test]
        public void ListAsArrayBug()
        {
            Unity.Mathematics.Random rand = new Unity.Mathematics.Random((uint)System.Math.Abs(System.DateTime.Now.Millisecond));
            Debug.Log("No Inserted OP");
            ListAsArrayBug(rand, 0);
            Debug.Log("Inserted Native List OP");
            ListAsArrayBug(rand, 1);
            Debug.Log("Inserted Native Array OP");
            ListAsArrayBug(rand, 2);
        }

        void ListAsArrayBug(Unity.Mathematics.Random rand, int InsertedOpType)
        {
            //make NativeList and Use it AsArray
            var list = new NativeList<int>(Allocator.Temp);
            var size = rand.NextInt(1, 50);
            for (int i = 0; i < size; i++) list.Add(rand.NextInt(11));
            var asArray = list.AsArray();

            //Print the AsArray
            var sb = new System.Text.StringBuilder("Initial As Array|");
            for (int i = 0; i < size; i++) sb.Append($"{asArray[i]}|");
            Debug.Log(sb.ToString());
            sb.Clear();

            //Insert some operation
            int otherSize;
            NativeList<int> otherList = default;
            NativeArray<int> otherArray = default;
            switch (InsertedOpType)
            {
                case 1:
                    otherSize = rand.NextInt(1, 50);
                    otherList = new NativeList<int>(4, Allocator.Temp);
                    for (int i = 0; i < otherSize; i++) otherList.Add(rand.NextInt(11));
                    break;

                case 2:
                    otherSize = rand.NextInt(1, 50);
                    otherArray = new NativeArray<int>(otherSize, Allocator.Temp);
                    for (int i = 0; i < otherSize; i++) otherArray[i] = (rand.NextInt(11));
                    break;
            }

            //Try Access the AsArray again after interted OP
            try
            {
                sb.Append("AsArray After Inerted OP|");
                for (int i = 0; i < size; i++) sb.Append($"{asArray[i]}|");
                Debug.Log(sb.ToString());
            }
            catch (System.Exception e)
            {
                Debug.Log("Array Access Failed:"+e.ToString());
            }
            sb.Clear();

            //Print original list
            sb.Append("the List After Inerted OP|");
            for (int i = 0; i < size; i++) sb.Append($"{list[i]}|");
            Debug.Log(sb.ToString());
            sb.Clear();

            //Print original list as a new AsArray
            sb.Append("New AsArray After Inerted OP|");
            asArray = list.AsArray();
            for (int i = 0; i < size; i++) sb.Append($"{asArray[i]}|");
            Debug.Log(sb.ToString());
            sb.Clear();

            //Print original list pre element via AsArray... just because I'm mad with this bug...
            sb.Append("via AsArray After Inerted OP|");
            for (int i = 0; i < size; i++) sb.Append($"{list.AsArray()[i]}|");
            Debug.Log(sb.ToString());

            if (list.IsCreated) list.Dispose();
            if (otherList.IsCreated) otherList.Dispose();
            if (otherArray.IsCreated) otherList.Dispose();

        }
    }
}

So far AsArray is invalided only by NativeList operations.
I’m pretty sure it’s a bug now…

Settle my curiosity, try TempJob allocator instead

This is an issue in the Temp allocator. Changing to tempjob will fix the issue.

1 Like