Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

[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:
    43
    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,648
    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,648
    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:
    43
    Works like a charm! :D

    Thanks!
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    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,648
    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:
    43
    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:
    120
    @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:
    43
    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.