Search Unity

IJobProcessComponentData with generics

Discussion in 'Entity Component System' started by GilCat, Dec 16, 2018.

  1. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    I'm getting a weird error when i try to use generics with IJobProcessComponentData or IJobProcessComponentDataWithEntity i can use generics on other types of jobs but not this ones.
    I get ArgumentNullException: String reference not set to an instance of a String. as soon as i run the scene. I don't even have to use that specific job just nee to have it on a system.

    Here is an example of a problematic job.
    Code (CSharp):
    1.   [BurstCompile]
    2.   struct SampleJob02<T> : IJobProcessComponentData<T, Position> where T : struct, IComponentData {
    3.     public void Execute(ref T data0, ref Position data1) {
    4.       // *******************************
    5.       // Apply some transformations here
    6.       // *******************************
    7.     }
    8.   }
    Bellow is the full stack of the error
    ArgumentNullException: String reference not set to an instance of a String.
    Parameter name: s
    System.Text.Encoding.GetBytes (System.String s) (at <ac823e2bb42b41bda67924a45a0173c3>:0)
    Unity.Entities.TypeManager.CalculateMemoryOrdering (System.Type type) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Types/TypeManager.cs:178)
    Unity.Entities.TypeManager.BuildComponentType (System.Type type) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Types/TypeManager.cs:206)
    Unity.Entities.TypeManager.CreateTypeIndexThreadSafe (System.Type type) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Types/TypeManager.cs:159)
    Unity.Entities.TypeManager.GetTypeIndex (System.Type type) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Types/TypeManager.cs:122)
    Unity.Entities.ComponentType..ctor (System.Type type, Unity.Entities.ComponentType+AccessMode accessModeType) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Types/ComponentType.cs:73)
    Unity.Entities.IJobProcessComponentDataUtility.GetComponentTypes (System.Type jobType, System.Type interfaceType, System.Int32& processCount, Unity.Entities.ComponentType[]& changedFilter) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/IJobProcessComponentData.cs:244)
    Unity.Entities.IJobProcessComponentDataUtility.GetComponentTypes (System.Type jobType) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/IJobProcessComponentData.cs:219)
    Unity.Entities.JobProcessComponentDataExtensions.GetComponentGroupForIJobProcessComponentData (Unity.Entities.ComponentSystemBase system, System.Type jobType) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/IJobProcessComponentData.cs:397)
    Unity.Entities.ComponentSystemBase.InjectNestedIJobProcessComponentDataJobs () (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/ComponentSystem.cs:113)
    Unity.Entities.ComponentSystemBase.OnBeforeCreateManagerInternal (Unity.Entities.World world) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/ComponentSystem.cs:104)
    Unity.Entities.JobComponentSystem.OnBeforeCreateManagerInternal (Unity.Entities.World world) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/ComponentSystem.cs:522)
    Unity.Entities.ScriptBehaviourManager.CreateInstance (Unity.Entities.World world) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/ScriptBehaviourManager.cs:21)
    Unity.Entities.World.CreateManagerInternal (System.Type type, System.Object[] constructorArguments) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Injection/World.cs:137)
    Unity.Entities.World.GetOrCreateManagerInternal (System.Type type) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Injection/World.cs:165)
    Unity.Entities.World.GetOrCreateManager (System.Type type) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Injection/World.cs:218)
    Unity.Entities.DefaultWorldInitialization.GetBehaviourManagerAndLogException (Unity.Entities.World world, System.Type type) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs:21)
    UnityEngine.Debug:LogException(Exception)
    Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities/Stubs/Unity/Debug.cs:25)
    Unity.Entities.DefaultWorldInitialization:GetBehaviourManagerAndLogException(World, Type) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs:25)
    Unity.Entities.DefaultWorldInitialization:CreateBehaviourManagersForMatchingTypes(Boolean, IEnumerable`1, World) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs:75)
    Unity.Entities.DefaultWorldInitialization:Initialize(String, Boolean) (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs:57)
    Unity.Entities.AutomaticWorldBootstrap:Initialize() (at Library/PackageCache/com.unity.entities@0.0.12-preview.21/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs:11)

    Is this a bug?
    I'm using Unity 2018.3.0f2 with Entities package preview 21

    Thank you
     
  2. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Has anyone else experienced this?
     
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    Strings are not permitted in burst. Generally in whole ECS, as far I am concerned.
    I think you should not use T, but predefined IComponentData.
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I've used generic jobs like this before fine. Can you post the full job and the component data
     
  5. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Here it is:
    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Mathematics;
    5. using Unity.Transforms;
    6.  
    7. public interface IFloatValue {
    8.   float GetValue();
    9. }
    10.  
    11. public struct MyComponentData : IComponentData, IFloatValue {
    12.   public float Value;
    13.  
    14.   public float GetValue() {
    15.     return Value;
    16.   }
    17. }
    18.  
    19. public class SampleSystem : JobComponentSystem
    20. {
    21.   [BurstCompile]
    22.   struct SampleJob<T> : IJobProcessComponentData<T, Position> where T : struct, IComponentData, IFloatValue {
    23.     public void Execute(ref T data0, ref Position data1) {
    24.       data1.Value = new float3(data0.GetValue());
    25.     }
    26.   }
    27.  
    28.   protected override JobHandle OnUpdate(JobHandle inputDeps) {
    29.     inputDeps = new SampleJob<MyComponentData>().Schedule(this, inputDeps);
    30.     return base.OnUpdate(inputDeps);
    31.   }
    32. }
    33.  
     
  6. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    191
    @tertle @GilCat I am curious what problem you two are trying to solve with generics?
     
  7. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Sounds like some boilerplate.
     
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    An example.

    Code (CSharp):
    1.     /// <summary>
    2.     /// Remove a component from an entity.
    3.     /// </summary>
    4.     public struct RemoveComponentJob<T> : IJob
    5.         where T : struct, IComponentData
    6.     {
    7.         public EntityCommandBuffer EntityCommandBuffer;
    8.  
    9.         public NativeQueue<Entity> Queue;
    10.  
    11.         /// <inheritdoc />
    12.         public void Execute()
    13.         {
    14.             while (this.Queue.TryDequeue(out var entity))
    15.             {
    16.                 this.EntityCommandBuffer.RemoveComponent<T>(entity);
    17.             }
    18.         }
    19.     }
    Though I've realized since most of my generic jobs use EntityCommandBuffer I'm not sure I've ever tried it with burst.

    -edit-

    here's another similar from my batch system that was posted a few days ago

    Code (CSharp):
    1.         private class EventBatch<T> : IEventBatch
    2.             where T : struct, IComponentData
    3.         {
    4.             // ...
    5.  
    6.             [BurstCompile]
    7.             private struct SetJob : IJob
    8.             {
    9.                 public NativeQueue<T> Queue;
    10.  
    11.                 public NativeArray<ArchetypeChunk> Chunks;
    12.  
    13.                 public NativeUnit<int> ChunkIndex;
    14.  
    15.                 public NativeUnit<int> EntityIndex;
    16.  
    17.                 public ArchetypeChunkComponentType<T> ComponentType;
    18.  
    19.                 /// <inheritdoc />
    20.                 public void Execute()
    21.                 {
    22.                     for (; this.ChunkIndex.Value < this.Chunks.Length; this.ChunkIndex.Value++)
    23.                     {
    24.                         var chunk = this.Chunks[this.ChunkIndex.Value];
    25.  
    26.                         var components = chunk.GetNativeArray(this.ComponentType);
    27.  
    28.                         var intLocalIndex = this.EntityIndex.Value;
    29.  
    30.                         while (this.Queue.TryDequeue(out var item) && intLocalIndex < components.Length)
    31.                         {
    32.                             components[intLocalIndex++] = item;
    33.                         }
    34.  
    35.                         this.EntityIndex.Value = intLocalIndex < components.Length ? intLocalIndex : 0;
    36.                     }
    37.                 }
    38.             }
    39.         }
    This one is in a generic class though instead of being a specific generic job (don't try to understand it lol. I should probably add a comment before i forget what it does.)
     
  9. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Those work fine for me too the only ones that doesn't are when those generic are used in IJobProcessComponentData or IJobProcessComponentDataWithEntity. Burst or without Burst
    Perhaps it is the case. I sure understand that this is most likely never needed but the thing is that the error exist when they are applied.
    I would use this to get references from a hashmap outside.
    Here is something resembling what i use:
    Code (CSharp):
    1.  
    2. public interface IHashValue {
    3.   int GetValue();
    4. }
    5.  
    6. public struct Node : IComponentData, IHashValue {
    7.   public int Value;
    8.   public int GetValue() {
    9.     return Value;
    10.   }
    11. }
    12.  
    13. public struct Connection : IComponentData, IHashValue {
    14.   public int Value;
    15.   public Entity Source;
    16.   public Entity Target;
    17.  
    18.   public int GetValue() {
    19.     return Value;
    20.   }
    21. }
    22.  
    23. public class SampleSystem : JobComponentSystem
    24. {
    25.   /// <summary>
    26.   /// Hash map to the model objects
    27.   /// </summary>
    28.   public static Dictionary<int, ViZObject> HashMap = new Dictionary<int, ViZObject>();
    29.  
    30.   [BurstCompile]
    31.   struct CalcJob<T> : IJobProcessComponentData<T> where T : struct, IComponentData, IHashValue {
    32.     public void Execute(ref T hashData) {
    33.       var myModelObj = HashMap[hashData.GetValue()];
    34.       // Calculate 3D visualization for the object model
    35.       myModelObj.CalcModel();
    36.     }
    37.   }
    38.  
    39.   protected override JobHandle OnUpdate(JobHandle inputDeps) {
    40.     inputDeps = new CalcJob<Node>().Schedule(this, inputDeps);
    41.     inputDeps = new CalcJob<Connection>().Schedule(this, inputDeps);
    42.     return base.OnUpdate(inputDeps);
    43.   }
    44. }
    45. }
    What using generics solves here is the need to create one job for calculating the 3D model Node and another for Connection. Boilerplate or not i think it is handy in very few cases.
    And yes the design was not thinking in ECS when this was created a few years ago, so it's being ported and in the future will most likely wont need to use generics.

    Thanks for all the very helpful insights!
     
  10. VildNinja

    VildNinja

    Joined:
    Jan 17, 2013
    Posts:
    8
    IJobProcessComponentData is different than the other jobs, since the struct declaration is used to create the component group needed to run the job. For all other jobs the group has to be setup from outside. So guessing you hit an edge case they haven't fully implemented yet.