Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feature Request Can we ever expect something like Interface boxing workflows?

Discussion in 'Burst' started by HellGate94, May 26, 2021.

  1. HellGate94

    HellGate94

    Joined:
    Sep 21, 2017
    Posts:
    132
    With that i mean things like NativeArray<IProcessor>. i am well aware that this is quite a stretch as it requires a type and types are classes and so on but thats simply by far the most limiting thing with burst right now.

    FunctionPointers with void* data is the closest thing to it but is incredible messy and quickly becomes unmanageable

    just want to know if something like this is considered for the future and i keep my current slow workaround for the time being or if i should just switch to something else altogether like a external cpp library
     
  2. sheredom

    sheredom

    Unity Technologies

    Joined:
    Jul 15, 2019
    Posts:
    300
    Short term answer: this isn't being considered.

    Medium term answer: this isn't being considered.

    Long term answer (assuming infinite resources and nothing more pressing to do!): we could maybe think about this.

    It adds a lot of complexity to the compiler and, in my opinion, takes us a step away from the default-good-performance we've followed with HPC# and Burst.

    I'm curious though - what are you actually doing where doing an array of virtual dispatches is required?
     
  3. HellGate94

    HellGate94

    Joined:
    Sep 21, 2017
    Posts:
    132
    i guessed so

    well it's not the first time i encountered this. the first time it was for pathfinding where each unit type has a list of tile type handlers that calculate the cost based on what the unit is capable of like a fish has a cost of infinite for everything but water tiles, things like lava has a cost based on how high the units hp is assuming it does x damage per sec and so on. basically each tile type has a different cost calculator

    i put that on hold but right now i came back to this issue when generating a mesh from a volume of a type and data struct.
    again trying to make it data orientated so the type is connected to a scriptable object that has a list of data interpreters and they basically decide what mesh to use. they can be as simple as data = index of variation meshes to data defines rotation of the mesh to add or even a combination of both. (not a great description but i hope it makes somewhat sense)

    the only alternative i can think of is scheduling many many different types of jobs but that already moves most of the work to c# and the main thread so not a good alternative

    i know that this is not amazing for performance but still worlds better than a mono c# equivalent solution
     
    Last edited: May 26, 2021
    sheredom likes this.
  4. HellGate94

    HellGate94

    Joined:
    Sep 21, 2017
    Posts:
    132
    with that i was talking about virtual dispatches / interfaces. made it sound like multiple jobs for each thing is "ok" but my early test showed that this going to end up worse than just mono c#
     
  5. IgreygooI

    IgreygooI

    Joined:
    Mar 13, 2021
    Posts:
    48
    Use a static dispatch pattern. Implement something like a tagged union that consists of all the type variants, and hard code a call graph. This is really tedious to do, but I found it to be the close thing to polymorphism in HPC#.
     
  6. HellGate94

    HellGate94

    Joined:
    Sep 21, 2017
    Posts:
    132
    do you have an example on what you mean with "static dispatch pattern"? as i said before i tried void* data with function pointers that know what data is fed into it and it sounds similar to what you suggest (just void* instead of union)
     
  7. IgreygooI

    IgreygooI

    Joined:
    Mar 13, 2021
    Posts:
    48
    The function TickState() uses a static dispatcher pattern. I implemented everything inline here, but you could implement the methods in each variant type.

    Meanwhile, I just found out there is a code gen tool design for this: https://forum.unity.com/threads/sources-available-dots-polymorphic-components.1132123/

    Code (CSharp):
    1. [BurstCompile]
    2. [StructLayout(LayoutKind.Explicit)]
    3. public struct TranslationState : IComponentData
    4. {
    5.     [FieldOffset(0)] public TranslationStateKind Kind;
    6.     #region Fields
    7.     [FieldOffset(4)] private LinearTranslation m_linearTranslation;
    8.     [FieldOffset(4)] private Abstain m_Abstain;
    9.     [FieldOffset(4)] private TimedAbstain m_timedAbstain;
    10.     #endregion
    11.     #region Safe Accessors and Mutators
    12.     // safe interfaces for access variants
    13.     public LinearTranslation LinearTranslation
    14.     {
    15.         get
    16.         {
    17.             // disable this for release build
    18.             if (Kind != TranslationStateKind.LinearTranslation)
    19.             {
    20.                 throw new InvalidOperationException();
    21.             }
    22.             return m_linearTranslation;
    23.         }
    24.         set
    25.         {
    26.             Kind = TranslationStateKind.LinearTranslation;
    27.             m_linearTranslation = value;
    28.         }
    29.     }
    30.     public Abstain Abstain
    31.     {
    32.         get
    33.         {
    34.             // disable this for release build
    35.             if (Kind != TranslationStateKind.Abstain)
    36.             {
    37.                 throw new InvalidOperationException();
    38.             }
    39.             return m_Abstain;
    40.         }
    41.         set
    42.         {
    43.             Kind = TranslationStateKind.Abstain;
    44.             m_Abstain = value;
    45.         }
    46.     }
    47.     public TimedAbstain TimedAbstain
    48.     {
    49.         get
    50.         {
    51.             // disable this for release build
    52.             if (Kind != TranslationStateKind.TimedAbstain)
    53.             {
    54.                 throw new InvalidOperationException();
    55.             }
    56.             return m_timedAbstain;
    57.         }
    58.         set
    59.         {
    60.             Kind = TranslationStateKind.TimedAbstain;
    61.             m_timedAbstain = value;
    62.         }
    63.     }
    64.     #endregion
    65.     #region Safe Constructor
    66.     public TranslationState(LinearTranslation linearTranslation) : this()
    67.     {
    68.         Kind = TranslationStateKind.LinearTranslation;
    69.         m_linearTranslation = linearTranslation;
    70.     }
    71.     public TranslationState(Abstain abstain) : this()
    72.     {
    73.         Kind = TranslationStateKind.Abstain;
    74.         m_Abstain = abstain;
    75.     }
    76.     public TranslationState(TimedAbstain timedAbstain) : this()
    77.     {
    78.         Kind = TranslationStateKind.TimedAbstain;
    79.         m_timedAbstain = timedAbstain;
    80.     }
    81.     #endregion
    82.     public bool TickState(ref float deltaTime, ref Translation translation)
    83.     {
    84.         switch (Kind)
    85.         {
    86.             case TranslationStateKind.Abstain:
    87.                 return true;
    88.             case TranslationStateKind.TimedAbstain:
    89.                 if (m_timedAbstain.RemainingTime <= deltaTime)
    90.                 {
    91.                     deltaTime -= m_timedAbstain.RemainingTime;
    92.                     m_timedAbstain.RemainingTime = 0.0f;
    93.                     return true;
    94.                 }
    95.                 else
    96.                 {
    97.                     m_timedAbstain.RemainingTime -= deltaTime;
    98.                     deltaTime = 0.0f;
    99.                     return false;
    100.                 }
    101.             case TranslationStateKind.LinearTranslation:
    102.                 if (m_linearTranslation.RemainingTime <= deltaTime)
    103.                 {
    104.                     deltaTime -= m_linearTranslation.RemainingTime;
    105.                     translation.Value += m_linearTranslation.RemainingTranslation;
    106.                
    107.                     m_linearTranslation.RemainingTime = 0.0f;
    108.                     m_linearTranslation.RemainingTranslation = float3.zero;
    109.                     return true;
    110.                 }
    111.                 else
    112.                 {
    113.                     var deltaTranslation = m_linearTranslation.RemainingTranslation * deltaTime /
    114.                                            m_linearTranslation.RemainingTime;
    115.                     m_linearTranslation.RemainingTime -= deltaTime;
    116.                     m_linearTranslation.RemainingTranslation -= deltaTranslation;
    117.                     deltaTime = 0.0f;
    118.                     translation.Value += deltaTranslation;
    119.                     return false;
    120.                 }
    121.             default:
    122.                 throw new ArgumentOutOfRangeException();
    123.         }
    124.     }
    125. }