После выхода Unity Jobs не сразу стало возможно создавать свои джобы, просто апи было не в публичном доступе. Сейчас же это возможно и давайте разберемся как это делать. Для начала определимся что же мы хотим сделать. Давайте возьмем самый простой вариант и напишем аналог IJob:

public struct Job : IJobSingle {
  public void Execute() {
  }
}

Т.е. мы хотим сделать стандартную однопоточную джобу (многопточные делаются не сильно дольше), что нам для этого необходимо:

1. Интерфейс и скелет

2. Инициализация джобы

3. Сама джоба

С интерфейсом все просто:

[JobProducerType(typeof(IJobSingleExtensions.JobProcess<>))]
public interface IJobSingle {
     void Execute();
}

public static unsafe class IJobSingleExtensions {
     public static JobHandle Schedule<T>(this T jobData, JobHandle inputDeps = default) where T : struct, IJobSingle {}
     internal struct JobProcess<T> where T : struct, IJobSingle {}
}

Обратите внимание, что JobProducerType говорит нам о том, что этот интерфейс не абы кто, а это интерфейс великой джобы ;)

Далее, иницилизация джобы. Мы должны вызвать сообщить шедуллеру, что в принципе у нас есть чего крутить. Для того, чтобы это заработало, нам нужно объявить инициализацию и вызвать ее в методе Schedule: 

public static unsafe class IJobSingleExtensions {
     public static JobHandle Schedule<T>(this T jobData, JobHandle inputDeps = default) where T : struct, IJobSingle {
     var parameters = new JobsUtility.JobScheduleParameters(UnsafeUtility.AddressOf(ref jobData), JobProcess<T>.Initialize(), inputDeps, ScheduleMode.Single);
     return JobsUtility.Schedule(ref parameters);
}

internal struct JobProcess<T> where T : struct, IJobSingle {
     private static System.IntPtr jobReflectionData;
     public static System.IntPtr Initialize() {
          if (jobReflectionData == System.IntPtr.Zero) {
               jobReflectionData = JobsUtility.CreateJobReflectionData(typeof(T), typeof(T), (ExecuteJobFunction)Execute);
          }
          return jobReflectionData;
     }
     
     public delegate void ExecuteJobFunction(ref T jobData, System.IntPtr additionalData, System.IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex);
     public static void Execute(ref T jobData, System.IntPtr additionalData, System.IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex) {
     }
}

Ну и финально - мы добавляем в наш Execute само выполнение:

public static void Execute(ref T jobData, System.IntPtr additionalData, System.IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex) { 
     jobData.Execute();
}
Read More  

Существует метод JobHandle.ScheduleBatchedJobs. Он позволяет "отправить" новую джобу на обработку. Если его не вызвать - джоба будет отправлена "когда-нибудь позже". Но не стоит его писать после шедулинга каждой джобы, т.к. это по сути форсированная отправка из локальной очереди в поток, а это занимает время.

Read More  

Все знают, что есть IJobParallelFor, мы туда передаем 2 параметра - количество элементов и сколько элементов будет попадать в одну итерацию. 

Но давайте представим ситуацию, что мы не знаем сколько элементов у нас будет (например, если у нас предыдущая джоба заполняет коллекцию). Таким образом мы не можем создать джобу. 

На самом деле для такого кейса существуют "отложенные джобы" ScheduleParallelForDeferArraySize. Чтобы завести такую джобу нужно передать указатель на Sequential-структуру, в которой первые 2 поля будут такие: 

T* list; 
int count; 

Т.е. указатель на массив элементов и количество этих самых элементов. Прочитаны эти данные будут только после того как все зависимости джобы будут завершены. 

Read More  

Вы, наверное, слышали про IJobParallelForTransform. 

Это такая джоба, которая с удовольствием даст доступ к массиву трасформов ваших GameObject. 

Но существует несколько интересных моментов, которые повлияют на производительность: 

1. Если вы читаете трасформы и не меняете их - помечайте их как ReadOnly (Запускайте джобу через ScheduleReadOnly), это повлияет на выполнение джобы. 

2. Раскидайте элементы по нескольким рутам иерархии, группировав их по 64/128/256 штук. 

То есть если у вас на сцене в руте 10к GameObject и вы хотите их подвигать, то создайте N пустых GameObject и вложите в них по 64/128/256 ваших объектов. 

Если этого не сделать, то ваша джоба будет всегда ожидаться главным потоком независимо от отсутствия Complete.

Read More