Search Unity

[SOLVED] Generic component type breaks standalone build

Discussion in 'Entity Component System' started by kingstone426, Nov 9, 2019.

  1. kingstone426

    kingstone426

    Joined:
    Jun 21, 2013
    Posts:
    44
    I have implemented a job component system that uses generic jobs and components.
    All works well in the editor but when I try to do a standalone build, it fails with this error:

    Unable to find interface method `Unity.Entities.IJobForEach_C`1<GenericComponent`1<T>>.Execute(ref T0)` from type `GenericJob`1`
    at Unity.Entities.JobForEachExtensions.JobStruct_Process_C`2<GenericJob`1<Unity.Transforms.Translation>,GenericComponent`1<T>>.ExecuteChunk(ref Unity.Entities.JobForEachExtensions.JobStruct_Process_C`2<GenericJob`1<Unity.Transforms.Translation>,GenericComponent`1<T>> jobData, System.IntPtr bufferRangePatchData, int begin, int end, Unity.Entities.ArchetypeChunk* chunks, int* entityIndices)
    at Unity.Entities.JobForEachExtensions.JobStruct_Process_C`2<GenericJob`1<Unity.Transforms.Translation>,GenericComponent`1<T>>.Execute(ref Unity.Entities.JobForEachExtensions.JobStruct_Process_C`2<GenericJob`1<Unity.Transforms.Translation>,GenericComponent`1<T>> jobData, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex)

    The standalone build succeeds if I remove [BurstCompile].
    Build target is Windows x86 Standalone
    I have tried both Mono and IL2CPP backends and I am using:
    Unity 2019.3.0b8
    Burst 1.1.2
    DOTS Windows Platform 0.1.6
    Entities 0.1.1

    This is my code:

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Burst;
    3. using Unity.Jobs;
    4. using Unity.Transforms;
    5.  
    6. [assembly: RegisterGenericComponentType(typeof(GenericJob<Translation>))]
    7. [assembly: RegisterGenericComponentType(typeof(GenericComponent<Translation>))]
    8.  
    9. public class MySystem : JobComponentSystem
    10. {
    11.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    12.     {
    13.         inputDeps = new GenericJob<Translation>().Schedule(this);
    14.         inputDeps.Complete();
    15.         return inputDeps;
    16.     }
    17. }
    18.  
    19. public struct GenericComponent<T> : IComponentData
    20. where T : struct, IComponentData
    21. {
    22.     public T data;
    23. }
    24.  
    25. [BurstCompile]
    26. public struct GenericJob<T> : IJobForEach<GenericComponent<T>> where T : struct, IComponentData
    27. {
    28.     public void Execute(ref GenericComponent<T> componentData)
    29.     {
    30.      
    31.     }
    32. }
     
    Last edited: Nov 9, 2019
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    I do not believe declaring a IJobForEach like that is currently supported by burst. There are currently some limitations on how generics work, specifically with IJobForEach but I can't remember precisely.
     
    Last edited: Nov 9, 2019
    kingstone426 likes this.
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    I should add generics do work on most other job types without issue so while it might be a little more boilerplate you can still get an generic jobs working.

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Burst;
    3. using Unity.Jobs;
    4. using Unity.Transforms;
    5.  
    6. [assembly: RegisterGenericComponentType(typeof(GenericComponent<Translation>))]
    7.  
    8. public class TranslationMySystem : MySystem<Translation>
    9. {
    10. }
    11.  
    12. public class MySystem<T> : JobComponentSystem
    13.     where T : struct, IComponentData
    14. {
    15.     private EntityQuery query;
    16.  
    17.     protected override void OnCreate()
    18.     {
    19.         // data for testing
    20.         this.EntityManager.CreateEntity(typeof(T));
    21.  
    22.         this.query = this.GetEntityQuery(ComponentType.ReadWrite<T>());
    23.     }
    24.  
    25.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    26.     {
    27.         inputDeps = new GenericJob<T>
    28.             {
    29.                 TType = this.GetArchetypeChunkComponentType<T>(),
    30.             }
    31.             .Schedule(this.query, inputDeps);
    32.  
    33.         return inputDeps;
    34.     }
    35. }
    36.  
    37. [BurstCompile]
    38. public struct GenericJob<T> : IJobChunk
    39.     where T : struct, IComponentData
    40. {
    41.     public ArchetypeChunkComponentType<T> TType;
    42.  
    43.     public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    44.     {
    45.         var array = chunk.GetNativeArray(this.TType);
    46.  
    47.         for (var i = 0; i < chunk.Count; i++)
    48.         {
    49.             T componentData = array[i];
    50.         }
    51.     }
    52. }
    53.  
    54. public struct GenericComponent<T> : IComponentData
    55.     where T : struct, IComponentData
    56. {
    57.     public T data;
    58. }
    59.  
     
    florianhanke likes this.
  4. kingstone426

    kingstone426

    Joined:
    Jun 21, 2013
    Posts:
    44
    Works like a charm! :D

    Thanks!
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Keep in mind Burst probably isn't running in a build for generic jobs scheduled by a generic method or system.
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Oh yeah, that's totally true (and I just tested).

    This is not something I have really tried but seemed like a quick solution to throw up but I guess it's not really a solution then!

    However, something like this does work though (and I actually tested it this time.)

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Jobs;
    5. using Unity.Transforms;
    6.  
    7. [assembly: RegisterGenericComponentType(typeof(GenericComponent<Translation>))]
    8.  
    9. public class MySystem : JobComponentSystem
    10. {
    11.     private EntityQuery query;
    12.  
    13.     protected override void OnCreate()
    14.     {
    15.         // data for testing
    16.         using (var n = new NativeArray<Entity>(1000000, Allocator.Temp))
    17.         {
    18.             var a = this.EntityManager.CreateArchetype(typeof(GenericComponent<Translation>));
    19.             this.EntityManager.CreateEntity(a, n);
    20.         }
    21.  
    22.         this.query = this.GetEntityQuery(ComponentType.ReadWrite<GenericComponent<Translation>>());
    23.     }
    24.  
    25.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    26.     {
    27.         inputDeps = new GenericJob<GenericComponent<Translation>>
    28.             {
    29.                 TType = this.GetArchetypeChunkComponentType<GenericComponent<Translation>>(),
    30.             }
    31.             .Schedule(this.query, inputDeps);
    32.  
    33.         return inputDeps;
    34.     }
    35. }
    36.  
    37. [BurstCompile]
    38. public struct GenericJob<T> : IJobChunk
    39.     where T : struct, IComponentData
    40. {
    41.     public ArchetypeChunkComponentType<T> TType;
    42.  
    43.     public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    44.     {
    45.         var array = chunk.GetNativeArray(this.TType);
    46.  
    47.         for (var i = 0; i < chunk.Count; i++)
    48.         {
    49.             T componentData = array[i];
    50.         }
    51.     }
    52. }
    53.  
    54. public struct GenericComponent<T> : IComponentData
    55.     where T : struct, IComponentData
    56. {
    57.     public T data;
    58. }
    59.  
     
  7. kingstone426

    kingstone426

    Joined:
    Jun 21, 2013
    Posts:
    44
    In case anyone stumbles on the same problem, I ended up with a slightly modified version of tertle's code in order to access GenericComponent<T> instead of T in my job:

    Code (csharp):
    1.  
    2. using Unity.Entities;
    3. using Unity.Burst;
    4. using Unity.Collections;
    5. using Unity.Jobs;
    6. using Unity.Transforms;
    7.  
    8. [assembly: RegisterGenericComponentType(typeof(GenericComponent<Translation>))]
    9.  
    10. public class TranslationMySystem : MySystem<Translation>
    11. {
    12. }
    13.  
    14. public class MySystem<T> : JobComponentSystem where T : struct, IComponentData
    15. {
    16.     private EntityQuery query;
    17.  
    18.     protected override void OnCreate()
    19.     {
    20.         // data for testing
    21.         using (var n = new NativeArray<Entity>(1000000, Allocator.Temp))
    22.         {
    23.             var a = this.EntityManager.CreateArchetype(typeof(GenericComponent<T>));
    24.             this.EntityManager.CreateEntity(a, n);
    25.         }
    26.  
    27.         this.query = this.GetEntityQuery(ComponentType.ReadWrite<GenericComponent<T>>());
    28.     }
    29.  
    30.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    31.     {
    32.         inputDeps = new GenericJob<T>
    33.         {
    34.             TType = this.GetArchetypeChunkComponentType<GenericComponent<T>>(),
    35.         }
    36.         .Schedule(this.query, inputDeps);
    37.  
    38.         return inputDeps;
    39.     }
    40. }
    41.  
    42. [BurstCompile]
    43. public struct GenericJob<T> : IJobChunk where T : struct, IComponentData
    44. {
    45.     public ArchetypeChunkComponentType<GenericComponent<T>> TType;
    46.    
    47.     public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    48.     {
    49.         var array = chunk.GetNativeArray(this.TType);
    50.        
    51.         for (var i = 0; i < chunk.Count; i++)
    52.         {
    53.             var genericComponent = array[i];
    54.             genericComponent.metaData++;
    55.             array[i] = genericComponent;
    56.         }
    57.     }
    58. }
    59.  
    60. public struct GenericComponent<T> : IComponentData where T : struct, IComponentData
    61. {
    62.     public T data;
    63.     public int metaData;
    64. }
    Now I can simply declare a class WhateverSystem : MySystem<Whatever> and metadata on all GenericComponent<Whatever> will automatically be processed in parallel.

    Thanks for the help! :D
     
  8. vildauget

    vildauget

    Joined:
    Mar 10, 2014
    Posts:
    121
    @kingstone426 , could you please share your use case ? I ask because I very well understand this must be useful for my procedural generation, I just don't know how yet, trying to wrap my mind around the data design of my project. Thanks.
     
  9. kingstone426

    kingstone426

    Joined:
    Jun 21, 2013
    Posts:
    44
    My particular use case is interpolation, i.e. blending/tweening/lerping between two values:
    Code (csharp):
    1.  
    2. public struct InterpolationComponent<T> : IComponentData where T : IComponentData
    3. {
    4.     public ulong startTime;
    5.     public ulong targetTime;
    6.     public T startValue;
    7.     public T targetValue;
    8.  
    9.     public float percentage;
    10. }
    11.  
    This way, if an entity has several components that require interpolation, I can add separate interpolation components using generics, e.g., InterpolationComponent<Translation> InterpolationComponent<Rotation> etc.

    I am sure there are many other use cases where you will want to attach some MetaData<T> to an entity with T.