How to sort a NativeList which is used in a previous job?

I have a list of indices i’m filtering, and after that i would like to sort them. Something like:

    void OnEnable()
    {
        var rng = new Unity.Mathematics.Random(123);
        NativeList<int> indices = new NativeList<int>(10, Allocator.TempJob);
        for (int i = 0; i < 10; i++)
            indices.Add(rng.NextInt(100));

        Debug.Log($"Before: {string.Join(',', indices.AsArray())}");

        var handle = new FilterOdd().ScheduleFilter(indices, 32);     
        handle = indices.SortJob().Schedule(handle);

        handle.Complete();

        Debug.Log($"After: {string.Join(',', indices.AsArray())}");

        indices.Dispose();
    }


    struct FilterOdd : IJobParallelForFilter
    {
        public bool Execute(int index) => index % 2 == 0;
    }

This throws the following error:

Which i dont really understand, since i pass in the previous handle. The error also doesnt even mention any of the sort jobs.

When i sort indices.AsDeferredArray instead the results simply are invalid, not sorted. It does work when i call complete in between the jobs, but obviously i’d rather not.

Using the non-job variant, e.g. indices.Sort does work as expected.

A kind user on Discord helped me! I’m still a bit puzzled how it manages to do this, but constructing the job before filtering does make it work. E.g.:

Works, but this:

Doestnt. I’m not sure why, and it makes my code a bit harder to read. Ah well.

It looks like a bug
do your report it?

SortJob calls GetUnsafePtr(), which checks the safety handle at job creation. (Dependencies aren’t known until the job is scheduled.) Since the filter job is already scheduled, the safety check throws an error.

1 Like

Thanks for the explanation! Seems like a slightly overzealous safety, but i take those over race conditions any day of the week.

1 Like

Upon another look, I think SortJob<T>(this NativeList<T> list) with a NativeList is likely to result in bugs, because it also gets list.Length() immediately, at job creation. That’s probably not intended, such as if you populate the list inside the first job. Instead, you want a job similar to this:

public struct SortJob<T> : IJob where T : unmanaged, IComparable<T>
{
    public NativeList<T> list;
 
    public void Execute()
    {
        list.Sort();
    }
}

edit: However, the built-in struct SortJob is internally parallelized so it is better when you do know the length in advance.

edit2: In fact, Unity.Collections.SortJob<T>(this NativeList<T> list) totally bypasses the safety system after the job is created. There is no safety check that a SortJob doesn’t overwrite another job, so long as it is created first. I wouldn’t use it.

1 Like

I just came back to ask exactly this, because i fell straight into this trap. Thanks so much!

I was indeed seeing exactly this behaviour, for example:

  1. Make a list with indices 0…8
  2. Filter odd entries
  3. Sort

Observe sort is being called for odd, removed, indices.

void OnEnable()
   {
       NativeList<int> indices = new NativeList<int>(10, Allocator.TempJob);
      
       for (int i = 0; i < 8; i++)
           indices.Add(i);
       Debug.Log($"Start indices: {string.Join(',', indices.AsArray())}");
       var sortJob = indices.SortJob(new Comparer());
       JobHandle handle = default;
      
       handle = new FilterOddJob().ScheduleFilter(indices, 32);
       handle.Complete();
       Debug.Log($"After filtering: {string.Join(',', indices.AsArray())}");

       handle = sortJob.Schedule(handle);      
       handle.Complete();

       indices.Dispose();
   }
  
   [BurstCompile]
   struct FilterOddJob : IJobParallelForFilter
   {
       public bool Execute(int index) => index % 2 == 0;
   }
  
   struct Comparer : IComparer<int>
   {
       public int Compare(int x, int y)
       {
           Debug.Log($"Comparing {x} with {y}!");
           return x.CompareTo(y);
       }
   }

I have also reported this as a bug, but perhaps its a new feature request. A DeferredSortJob.

1 Like

Do away with the built in sort job and just schedule your own IJob and pass it the list and call list.AsArray().Sort()

2 Likes

Wasted some time on this to. I don’t understand why this was implemented this way. It is so annyoing.

2 Likes