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. Dismiss Notice

Question How to write reusable code with burst compiler/ ecs when an interface doesn't work?

Discussion in 'Entity Component System' started by Botaurus, Feb 7, 2023.

  1. Botaurus

    Botaurus

    Joined:
    Feb 20, 2013
    Posts:
    81
    Still wrapping my head around how to stay burst compiler-compatible. Whats the best way to rewrite this code so that the some code is reused? Any way to use generics? As I make more tweens, it would be nice to not have the same copy/pasted code in each job so its easier to update. I can split some of it out into static methods, but wondering the best way to handle the small type differences. I naively tried adding an interface to the tween components, but that resulting in boxing which isn't burst compatible.
    Thank you

    Code (CSharp):
    1.     public struct TweenPosition : IComponentData
    2.     {
    3.         public int Ease;
    4.         public float Duration;
    5.         public float3 Target;
    6.         public float3 _start;
    7.         public float _t;
    8.     }
    9.    
    10.     public struct TweenScale : IComponentData
    11.     {
    12.         public int Ease;
    13.         public float Duration;
    14.         public float Target;
    15.         public float _start;
    16.         public float _t;
    17.     }
    18.    
    19.     [BurstCompile]
    20.     public partial struct TweenPositionJob : IJobEntity
    21.     {
    22.         public float DeltaTime;
    23.         public EntityCommandBuffer.ParallelWriter commands;
    24.  
    25.         // use [ChunkIndexInQuery] instead of [EntityIndexInQuery] as you only need unique indices per chunk for ECB to work correctly
    26.         // chunk indices are significantly cheaper for Unity to calculate
    27.         [BurstCompile]
    28.         void Execute([ChunkIndexInQuery]int chunkIdx, Entity entity, RefRW<TweenPosition> tween, TransformAspect trans)
    29.         {
    30.             if (tween.ValueRO._t < -0.000001f)
    31.             {
    32.                 tween.ValueRW._t = 0.0f;
    33.                 tween.ValueRW._start = trans.LocalPosition;
    34.             }
    35.  
    36.             if (tween.ValueRO._t < tween.ValueRO.Duration)
    37.             {
    38.                 tween.ValueRW._t += DeltaTime;
    39.                 if (tween.ValueRO._t >= tween.ValueRO.Duration)
    40.                 {
    41.                     commands.AddComponent(chunkIdx, entity, new TweenEventComplete());
    42.                     tween.ValueRW._t = tween.ValueRO.Duration;
    43.                 }
    44.  
    45.                 float normT = tween.ValueRO._t / tween.ValueRO.Duration;
    46.                 trans.LocalPosition = math.lerp(tween.ValueRO._start, tween.ValueRO.Target,
    47.                     TweenFunctions.Ease(normT, tween.ValueRO.Ease));
    48.             }
    49.         }
    50.     }
    51.    
    52.     [BurstCompile]
    53.     public partial struct TweenScaleJob : IJobEntity
    54.     {
    55.         public float DeltaTime;
    56.         public EntityCommandBuffer.ParallelWriter commands;
    57.  
    58.         // use [ChunkIndexInQuery] instead of [EntityIndexInQuery] as you only need unique indices per chunk for ECB to work correctly
    59.         // chunk indices are significantly cheaper for Unity to calculate
    60.         [BurstCompile]
    61.         void Execute([ChunkIndexInQuery]int chunkIdx, Entity entity, RefRW<TweenScale> tween, TransformAspect trans)
    62.         {
    63.             if (tween.ValueRO._t < -0.000001f)
    64.             {
    65.                 tween.ValueRW._t = 0.0f;
    66.                 tween.ValueRW._start = trans.LocalScale;
    67.             }
    68.  
    69.             if (tween.ValueRO._t < tween.ValueRO.Duration)
    70.             {
    71.                 tween.ValueRW._t += DeltaTime;
    72.                 if (tween.ValueRO._t >= tween.ValueRO.Duration)
    73.                 {
    74.                     commands.AddComponent(chunkIdx, entity, new TweenEventComplete());
    75.                     tween.ValueRW._t = tween.ValueRO.Duration;
    76.                 }
    77.  
    78.                 float normT = tween.ValueRO._t / tween.ValueRO.Duration;
    79.                 trans.LocalScale = math.lerp(tween.ValueRO._start, tween.ValueRO.Target,
    80.                     TweenFunctions.Ease(normT, tween.ValueRO.Ease));
    81.             }
    82.         }
    83.     }
     
  2. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    195
    I find myself using static methods a lot. Another approach, for some use cases where statics are restricting, is using a struct which basically just holds a modular method that can be created outside of a job then used inside a job...this can sometimes avoid too much boilerplate. Other than that, can't really fight your way out of using some boilerplate code in ECS.
     
    Botaurus likes this.
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Interface-constrained generics are valid with Burst. For your use case, you probably want an interface-constrained generic static method. However, it is also possible to have interface-constrained generic jobs (with the exception of IJobEntity) as long as a concrete type of a struct defining the job exists somewhere in the code so that it shows up in the TypeSpec tables. More on that here: https://github.com/Dreaming381/BurstGenericsTest
     
    toomasio, amarcolina and Botaurus like this.
  4. Botaurus

    Botaurus

    Joined:
    Feb 20, 2013
    Posts:
    81
    thanks for the feedback, I ended up just using a couple static methods. Not sure how to use an interface-constrained generic static method without getting a boxing error

    Code (CSharp):
    1.    
    2. public struct TweenBasic
    3. {
    4.     public int Ease;
    5.     public float Duration;
    6.     public float _t;
    7. }
    8.  
    9. public struct TweenPosition : IComponentData
    10.     {
    11.         public TweenBasic Tween;
    12.         public float3 Target;
    13.         public float3 _start;
    14.     }
    15.  
    16. public struct TweenScale : IComponentData
    17. {
    18.     public TweenBasic Tween;
    19.     public float Target;
    20.     public float _start;
    21.  
    22. }
    23.  
    24.     public struct _tweenFuncs
    25.     {
    26.         public static bool _basicStart(ref TweenBasic tween)
    27.         {
    28.             if (tween._t < -0.000001f)
    29.             {
    30.                 tween._t = 0.0f;
    31.                 return true;
    32.             }
    33.             return false;
    34.         }
    35.      
    36.         public static bool _basic(ref TweenBasic tween, EntityCommandBuffer.ParallelWriter commands, float deltaTime, int chunkIdx, Entity entity)
    37.         {
    38.             bool doTween = false;
    39.             if (tween._t < tween.Duration)
    40.             {
    41.                 tween._t += deltaTime;
    42.                 if (tween._t >= tween.Duration)
    43.                 {
    44.                     commands.AddComponent(chunkIdx, entity, new TweenEventComplete());
    45.                     tween._t = tween.Duration;
    46.                 }
    47.  
    48.                 float normT = tween._t / tween.Duration;
    49.                 doTween = true;
    50.             }
    51.             return doTween;
    52.         }
    53.     }
    54.  
    55.     [BurstCompile]
    56.     public partial struct TweenPositionJob : IJobEntity
    57.     {
    58.         public float DeltaTime;
    59.         public EntityCommandBuffer.ParallelWriter commands;
    60.         [BurstCompile]
    61.         void Execute([ChunkIndexInQuery]int chunkIdx, Entity entity, RefRW<TweenPosition> tween, TransformAspect trans)
    62.         {
    63.             if(_tweenFuncs._basicStart(ref tween.ValueRW.Tween))
    64.                 tween.ValueRW._start = trans.LocalPosition;
    65.  
    66.             if (_tweenFuncs._basic(ref tween.ValueRW.Tween, commands, DeltaTime, chunkIdx, entity))
    67.             {
    68.                 float normT = tween.ValueRO.Tween._t / tween.ValueRO.Tween.Duration;
    69.                 trans.LocalPosition = math.lerp(tween.ValueRO._start, tween.ValueRO.Target,
    70.                     TweenFunctions.Ease(normT, tween.ValueRO.Tween.Ease));
    71.             }
    72.         }
    73.     }
    74.  
    75.     [BurstCompile]
    76.     public partial struct TweenScaleJob : IJobEntity
    77.     {
    78.         public float DeltaTime;
    79.         public EntityCommandBuffer.ParallelWriter commands;
    80.         [BurstCompile]
    81.         void Execute([ChunkIndexInQuery]int chunkIdx, Entity entity, RefRW<TweenScale> tween, TransformAspect trans)
    82.         {
    83.             if(_tweenFuncs._basicStart(ref tween.ValueRW.Tween))
    84.                 tween.ValueRW._start = trans.LocalScale;
    85.  
    86.             if (_tweenFuncs._basic(ref tween.ValueRW.Tween, commands, DeltaTime, chunkIdx, entity))
    87.             {
    88.                 float normT = tween.ValueRO.Tween._t / tween.ValueRO.Tween.Duration;
    89.                 trans.LocalScale = math.lerp(tween.ValueRO._start, tween.ValueRO.Target,
    90.                     TweenFunctions.Ease(normT, tween.ValueRO.Tween.Ease));
    91.             }
    92.         }
    93.     }