Search Unity

Generic listeners for visuals

Discussion in 'Entity Component System' started by e199, Nov 30, 2018.

  1. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    Hey

    So I had an idea to easily get events about data changes to monobehaviours to change visuals/audio/whatever

    We have abstract listener
    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3. using UnityEngine;
    4.  
    5. public abstract class ListenerBehaviour<T> : MonoBehaviour where T: struct, IComponentData, IEquatable<T>
    6. {
    7.     public GameObjectEntity Entity;
    8.    
    9.     private void Start()
    10.     {
    11.         World.Active.GetOrCreateManager<ListenerSystem<T>>();
    12.         Entity.EntityManager.AddComponentData(Entity.Entity, new Listen<T>{Added = false});
    13.     }
    14.  
    15.     public abstract void OnAdded(T value);
    16.  
    17.     public abstract void OnSet(T value);
    18.  
    19.     public abstract void OnRemoved();
    20. }
    It attaches really simple component to mark what we are listening for
    Code (CSharp):
    1. public struct Listen<T> : IComponentData where T : IComponentData, IEquatable<T>
    2. {
    3.     public T LastValue;
    4.     public bool Added;
    5. }
    And lastly I have generic system to trigger state changes in that visual monobehaviour
    Code (CSharp):
    1. using System;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4.  
    5. [UpdateBefore(typeof(EndFrameBarrier))]
    6. public class ListenerSystem<T> : ComponentSystem where T : struct, IComponentData, IEquatable<T>
    7. {
    8. #pragma warning disable 649
    9.     private struct WithState
    10.     {
    11.         [ReadOnly] public EntityArray                          Entities;
    12.         [ReadOnly] public ComponentDataArray<T>                State;
    13.         public            ComponentDataArray<Listen<T>>        LastState;
    14.         public            ComponentArray<ListenerBehaviour<T>> Listener;
    15.     }
    16.  
    17.     [Inject] private WithState _withState;
    18.  
    19.     private struct WithoutState
    20.     {
    21.         [ReadOnly] public EntityArray                          Entities;
    22.         public            ComponentDataArray<Listen<T>>        LastState;
    23.         public            ComponentArray<ListenerBehaviour<T>> Listener;
    24.     }
    25.  
    26.     [Inject] private WithoutState _withoutState;
    27. #pragma warning restore 649
    28.  
    29.     private bool _isZeroSized;
    30.  
    31.     protected override void OnCreateManager()
    32.     {
    33.         var typeIndex     = TypeManager.GetTypeIndex<T>();
    34.         var componentType = ComponentType.FromTypeIndex(typeIndex);
    35.         _isZeroSized = componentType.IsZeroSized;
    36.     }
    37.  
    38.     protected override void OnUpdate()
    39.     {
    40.        
    41.         if (_withState.State.Length > 0)
    42.         {
    43.             var entities = _withState.Entities;
    44.             var states = _withState.State;
    45.             var lastStates = _withState.LastState;
    46.             var listeners = _withState.Listener;
    47.  
    48.             for (int i = 0; i < states.Length; i++)
    49.             {
    50.                 var e = entities[i];
    51.                 var state = states[i];
    52.                 var lastState = lastStates[i];
    53.                 var listener = listeners[i];
    54.  
    55.                 if (!lastState.Added)
    56.                 {
    57.                     lastState.Added = true;
    58.                    
    59.                     listener.OnAdded(state);
    60.                     PostUpdateCommands.SetComponent(e, lastState);
    61.                 }
    62.                 else
    63.                 {
    64.                     if (_isZeroSized) continue;
    65.                     if (state.Equals(lastState.LastValue)) continue;
    66.  
    67.                     lastState.LastValue = state;
    68.                    
    69.                     listener.OnSet(state);
    70.                     PostUpdateCommands.SetComponent(e, lastState);
    71.                 }
    72.             }
    73.         }
    74.  
    75.         if (_withoutState.LastState.Length > 0)
    76.         {
    77.             var entities = _withoutState.Entities;
    78.             var lastStates = _withoutState.LastState;
    79.             var listeners  = _withoutState.Listener;
    80.  
    81.             for (int i = 0; i < lastStates.Length; i++)
    82.             {
    83.                 var e = entities[i];
    84.                 var lastState = lastStates[i];
    85.                 var listener  = listeners[i];
    86.  
    87.                 if (lastState.Added)
    88.                 {
    89.                     lastState.Added = false;
    90.                    
    91.                     listener.OnRemoved();
    92.                     PostUpdateCommands.SetComponent(e, lastState);
    93.                 }
    94.             }
    95.         }
    96.     }
    97. }

    But I have a problem caused by where T : struct, IComponentData, IEquatable<T>
    ArgumentException: Listen`1[Flamed] is an IComponentData, and thus must be blittable (No managed object is allowed on the struct).


    All those IEquatable<T> are needed for one line of code
    if (state.Equals(lastState.LastValue)) continue;


    Is there a way to generically check if they contain same data?
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    bool is not blittable
     
    e199 likes this.
  3. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    Stepped into newbie trap, thanks
     
  4. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Hey, I am making a lib with same purpose as yours! The key to check changed per data is the
    FastEquality
    class.

    OnCreateManager : cache TypeInfo

    stateTypeInfo = TypeManager.GetTypeInfo<STATE>();


    This contains "FastEqualityTypeInfo" inside. It is like a layout map of a type which provides instant equality test for one type without writing IEquatable but instead it iterates through memory layout and compare byte values.

    When you want to compare :

    bool reallyChanged = !FastEquality.Equals(ref state, ref previousState, stateTypeInfo.FastEqualityTypeInfo);


    You are not using the outer TypeInfo, there is one different TypeInfo inside it.

    *Note that you cannot bring FastEqualityTypeInfo to the job. It uses managed array. I am thinking about writing a job-compatible version, not sure if it is possible or not but it would enable batch equality tests for the whole chunk for my lib.
     
    Last edited: Nov 30, 2018
    e199 likes this.
  5. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    Awesome! Thanks!
    I will try it a bit later