Search Unity

Implementing Unity's ECS.JobComponentSystem

Discussion in 'Data Oriented Technology Stack' started by JohnHudeski, Jan 12, 2018.

  1. JohnHudeski

    JohnHudeski

    Joined:
    Nov 18, 2017
    Posts:
    108
    Since I cannot quite wait for the next beta release I decided to Implement my own version of Unity's ECS and I tried to keep it as close to the snippets shown in the example

    But I am a bit stuck when it comes to the JobComponentSystem. It is where ECS and the Job system meet
    and the difficulty comes with implementing these two functions

    Code (CSharp):
    1.  
    2. void AddDependency(JobHandle handle);
    3. JobHandle GetDependency();
    4.  
    5. //Example usage
    6. AddDependency( job.Schedule(m_Transforms, GetDependency()) );
    7.  
    One can imagine AddDependency is simply a wrapper for the Job.Schedule functions.
    Job.Schedule is an extension function that uses JobsUtility to schedule parameters

    Code (CSharp):
    1. namespace Unity.Jobs
    2. {
    3.     /// <summary>
    4.     ///   <para>Extension methods for Jobs using the IJob interface.</para>
    5.     /// </summary>
    6.     public static class IJobExtensions
    7.     {
    8.  
    9.         public static JobHandle Schedule<T>(this T jobData, JobHandle dependsOn = default(JobHandle)) where T : struct, IJob
    10.         {
    11.             JobsUtility.JobScheduleParameters jobScheduleParameters = new JobsUtility.JobScheduleParameters(UnsafeUtility.AddressOf<T>(ref jobData), IJobExtensions.JobStruct<T>.Initialize(), dependsOn, ScheduleMode.Batched);
    12.             return JobsUtility.Schedule(ref jobScheduleParameters);
    13.         }
    14.     }
    15. }

    So it can be faked, to an extent. But Get dependency is not as straight forward

    Is JobComponent actually a job that gets schedule on initialization or when it registers to some MainThread system then stores its own handle?


    I'll post further insights as they come along.
     
  2. JohnHudeski

    JohnHudeski

    Joined:
    Nov 18, 2017
    Posts:
    108
    I think this topic is in the wrong section but I'll continue.

    My original idea looks very cyclic but here it is.

    Code (CSharp):
    1.  
    2. public class JobComponentSystem : ComponentSystem , IJob
    3. {
    4.     JobHandle? dependency; //cached?
    5.  
    6.     public void Init()
    7.     {
    8.         dependency = this.Schedule();
    9.     }
    10.  
    11.     protected virtual void OnUpdate()
    12.     {
    13.         dependency.Complete();
    14.     }
    15.  
    16.     public void Execute()//Called by the main manager?
    17.     {
    18.         OnUpdate(); //This calls the child most update? or we are in cyclic hell
    19.     }
    20.  
    21.     public void AddDependency(JobHandle handle)
    22.     {
    23.     }
    24.  
    25.     JobHandle GetDependency()
    26.     {
    27.         if(dependency == null)
    28.         {
    29.             //Create one?
    30.         }
    31.     }
    32. }
    33.  
    I later realized that in one of the Unites Joachim spoke of making the code writing part easier (less boiler plate) to speed up the coding process and automate job scheduling.

    Code (CSharp):
    1. //For simplicity and auto scheduling
    2. public struct GravityMinionJob : IJobProcessComponentData<MinionVelocity>, IAutoComponentSystemJob
    3. {
    4.     float dt;
    5.     Vector3 gravity;
    6.  
    7.     //probably called before scheduling
    8.     public void Prepare()
    9.     {
    10.         dt = Time.deltaTime;
    11.         gravity = Vector3.up * -9.81f;
    12.     }
    13.  
    14.     //Can have multiple components that refer to the same entity, How?
    15.     public void Execute(ref MinionVelocity minion)
    16.     {
    17.         minion.velocity += gravity * dt;
    18.     }
    19. }
    20.  

    I tried using it as an inspiration so my first assumption was

    Code (CSharp):
    1. //assumptions
    2. public interface IAutoComponentSystemJob
    3. {
    4.     void Prepare(); //Maybe not
    5. }
    6.  
    7. public interface IJobProcessComponentData<T> where T : IComponentData, stuct
    8. {
    9.     void Execute(ref T t);
    10. }
    11.  
    But then Joachim said we can have multiple components
    Code (CSharp):
    1.  
    2. //Allowing multiple components
    3. public interface IJobProcessComponentData<T, U, V>
    4. {
    5.     void Execute(ref T t);
    6.     void Execute(ref U u);
    7.     void Execute(ref V v);
    8. }

    It seemed like a dead end so I assume they must be using some nifty extension methods to keep everything neat similar to IJobExtensions as he constantly reference how tightly packed everything was

    Code (CSharp):
    1. /// <summary>
    2.     ///   <para>Extension methods for Jobs using the IJob interface.</para>
    3.     /// </summary>
    4.     public static class IJobExtensions
    5.     {
    6.         [StructLayout(LayoutKind.Sequential, Size = 1)]
    7.         private struct JobStruct<T> where T : struct, IJob
    8.         {
    9.             public delegate void ExecuteJobFunction(ref T data, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex);
    10.  
    11.             public static IntPtr jobReflectionData;
    12.  
    13.             [CompilerGenerated]
    14.             private static IJobExtensions.JobStruct<T>.ExecuteJobFunction genericCache0;
    15.  
    16.             public static IntPtr Initialize()
    17.             {
    18.                 if (IJobExtensions.JobStruct<T>.jobReflectionData == IntPtr.Zero)
    19.                 {
    20.                     Type tType = typeof(T);
    21.                     if (IJobExtensions.JobStruct<T>.genericCache0 == null)
    22.                     {
    23.                         IJobExtensions.JobStruct<T>.genericCache0 = new IJobExtensions.JobStruct<T>.ExecuteJobFunction(IJobExtensions.JobStruct<T>.Execute);
    24.                     }
    25.                     IJobExtensions.JobStruct<T>.jobReflectionData = JobsUtility.CreateJobReflectionData(tType, IJobExtensions.JobStruct<T>.genericCache0, null, null);
    26.                 }
    27.                 return IJobExtensions.JobStruct<T>.jobReflectionData;
    28.             }
    29.  
    30.             public static void Execute(ref T data, IntPtr additionalPtr, IntPtr bufferRangePatchData, ref JobRanges ranges, int jobIndex)
    31.             {
    32.                 data.Execute();
    33.             }
    34.         }
    35.  
    36.         public static JobHandle Schedule<T>(this T jobData, JobHandle dependsOn = default(JobHandle)) where T : struct, IJob
    37.         {
    38.             JobsUtility.JobScheduleParameters jobScheduleParameters = new JobsUtility.JobScheduleParameters(UnsafeUtility.AddressOf<T>(ref jobData), IJobExtensions.JobStruct<T>.Initialize(), dependsOn, ScheduleMode.Batched);
    39.             return JobsUtility.Schedule(ref jobScheduleParameters);
    40.         }
    41.  
    42.         public static void Run<T>(this T jobData) where T : struct, IJob
    43.         {
    44.             JobsUtility.JobScheduleParameters jobScheduleParameters = new JobsUtility.JobScheduleParameters(UnsafeUtility.AddressOf<T>(ref jobData), IJobExtensions.JobStruct<T>.Initialize(), default(JobHandle), ScheduleMode.Run);
    45.             JobsUtility.Schedule(ref jobScheduleParameters);
    46.         }
    47.     }