Search Unity

Generic Systems not supported?

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

  1. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    Hey

    I'm making generalized listener feature to trigger some methods on monobehaviour when component is changed. Mostly to change visual state (enable/disable child gameObjects, spawn VFX, etc)

    Usage is like this:
    Code (CSharp):
    1. public class HeatingListener : ListenerBehaviour<Heating, HeatingListener>
    2. {
    3.     public override void OnAdded(Heating value)
    4.     {
    5.         Debug.Log("Heating ADDED");
    6.     }
    7.  
    8.     public override void OnSet(Heating value)
    9.     {
    10.     }
    11.  
    12.     public override void OnRemoved()
    13.     {
    14.         Debug.Log("Heating REMOVED");
    15.     }
    16. }
    It inherits from base Listener:
    Code (CSharp):
    1. public abstract class ListenerBehaviour<T, U> : MonoBehaviour where T: struct, IComponentData where U : ListenerBehaviour<T, U>
    2. {
    3.     public GameObjectEntity Entity;
    4.    
    5.     private void Start()
    6.     {
    7.         World.Active.GetOrCreateManager<ListenerSystem<T, U>>().Enabled = true;
    8.         Entity.EntityManager.AddComponentData(Entity.Entity, new Listen<T, U>{Added = false});
    9.     }
    10.  
    11.     public abstract void OnAdded(T value);
    12.  
    13.     public abstract void OnSet(T value);
    14.  
    15.     public abstract void OnRemoved();
    16. }
    Notice how we create system to update the state in Start()

    It attaches that component to entity we listen to:
    Code (CSharp):
    1. public struct Listen<T, U> : IComponentData where T : struct, IComponentData
    2. {
    3.     public T    LastValue;
    4.     public Bool Added;
    5. }

    And the system itself:
    Code (CSharp):
    1. using Unity.Entities;
    2. using UnityEngine;
    3. using UnityEngine.Experimental.PlayerLoop;
    4.  
    5. [AlwaysUpdateSystem]
    6. [UpdateAfter(typeof(Update))]
    7. public class ListenerSystem<T, U> : ComponentSystem where T : struct, IComponentData where U : ListenerBehaviour<T, U>
    8. {
    9. #pragma warning disable 649
    10.     public struct WithState
    11.     {
    12.         public readonly int Length;
    13.  
    14.         public EntityArray                      Entities;
    15.         public ComponentDataArray<T>            State;
    16.         public ComponentDataArray<Listen<T, U>> LastState;
    17.     }
    18.  
    19.     [Inject] public WithState _withState;
    20.  
    21.     public struct WithoutState
    22.     {
    23.         public readonly int Length;
    24.  
    25.         public EntityArray                      Entities;
    26.         public ComponentDataArray<Listen<T, U>> LastState;
    27.     }
    28.  
    29.     [Inject] public WithoutState _withoutState;
    30. #pragma warning restore 649
    31.  
    32.     private bool _isZeroSized;
    33.     private TypeManager.TypeInfo _type;
    34.  
    35.     protected override void OnCreateManager()
    36.     {
    37.         Debug.Log(typeof(U).Name + " woo");
    38.        
    39.         var typeIndex     = TypeManager.GetTypeIndex<T>();
    40.         var componentType = ComponentType.FromTypeIndex(typeIndex);
    41.         _isZeroSized = componentType.IsZeroSized;
    42.         _type = TypeManager.GetTypeInfo<T>();    
    43.     }
    44.  
    45.     protected override void OnUpdate()
    46.     {
    47.         if (_withState.Entities.Length > 0)
    48.         {
    49.             var entities = _withState.Entities;
    50.             var states = _withState.State;
    51.             var lastStates = _withState.LastState;
    52.  
    53.             for (int i = 0; i < states.Length; i++)
    54.             {
    55.                 var e = entities[i];
    56.                 var state = states[i];
    57.                 var lastState = lastStates[i];
    58.  
    59.                 if (!lastState.Added)
    60.                 {
    61.                     lastState.Added = true;
    62.                     EntityManager.GetComponentObject<U>(e).OnAdded(state);
    63.                     PostUpdateCommands.SetComponent(e, lastState);
    64.                 }
    65.                 else
    66.                 {
    67.                     if (_isZeroSized) continue;
    68.                     if (FastEquality.Equals(ref state, ref lastState.LastValue, _type.FastEqualityTypeInfo)) continue;
    69.                    
    70.                     lastState.LastValue = state;
    71.                    
    72.                     EntityManager.GetComponentObject<U>(e).OnSet(state);
    73.                     PostUpdateCommands.SetComponent(e, lastState);
    74.                 }
    75.             }
    76.         }
    77.  
    78.         if (_withoutState.Entities.Length > 0)
    79.         {
    80.             var entities = _withoutState.Entities;
    81.             var lastStates = _withoutState.LastState;
    82.  
    83.             for (int i = 0; i < lastStates.Length; i++)
    84.             {
    85.                 var e = entities[i];
    86.                 var lastState = lastStates[i];
    87.  
    88.                 if (lastState.Added)
    89.                 {
    90.                     lastState.Added = false;
    91.                    
    92.                     EntityManager.GetComponentObject<U>(e).OnRemoved();
    93.                     PostUpdateCommands.SetComponent(e, lastState);
    94.                 }
    95.             }
    96.         }
    97.     }
    98. }
    Components like Listen<Heating, HeatingListener> are properly displayed in EntityDebugger.

    The problem:
    That system is not receiving [Inject]s.
    That system (generated one) is not shown in EntityDebugger. BUT it exists in World.BehaviourManagers.
    Nothing happens if I call Update on it myself.

    If I derive from it - new system is visible in EntityDebugger and works properly.
     
  2. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
  3. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    Chunk iteration works, but only when I manually call system's Update

    Code (CSharp):
    1. using System;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using UnityEngine;
    5. using UnityEngine.Experimental.PlayerLoop;
    6.  
    7. [AlwaysUpdateSystem]
    8. [UpdateAfter(typeof(Update))]
    9. public sealed class ListenerSystem<T, U> : ComponentSystem where T : struct, IComponentData where U : ListenerBehaviour<T, U>
    10. {
    11.     private bool                 _isZeroSized;
    12.     private TypeManager.TypeInfo _type;
    13.     private ComponentGroup       _query;
    14.  
    15.     protected override void OnCreateManager()
    16.     {
    17.         Debug.Log(typeof(U).Name + " woo");
    18.  
    19.         var typeIndex     = TypeManager.GetTypeIndex<T>();
    20.         var componentType = ComponentType.FromTypeIndex(typeIndex);
    21.         _isZeroSized = componentType.IsZeroSized;
    22.         _type        = TypeManager.GetTypeInfo<T>();
    23.  
    24.         _query = GetComponentGroup(new EntityArchetypeQuery
    25.         {
    26.             All  = new[] {ComponentType.ReadOnly<T>(), ComponentType.Create<Listen<T, U>>()},
    27.             None = Array.Empty<ComponentType>(),
    28.             Any  = Array.Empty<ComponentType>()
    29.         }, new EntityArchetypeQuery
    30.         {
    31.             All  = new[] {ComponentType.Create<Listen<T, U>>()},
    32.             None = new[] {ComponentType.ReadOnly<T>()},
    33.             Any  = Array.Empty<ComponentType>()
    34.         });
    35.     }
    36.  
    37.     protected override void OnUpdate()
    38.     {
    39.         var chunks = _query.CreateArchetypeChunkArray(Allocator.TempJob);
    40.         if (chunks.Length == 0)
    41.         {
    42.             chunks.Dispose();
    43.             return;
    44.         }
    45.  
    46.         var entityType = GetArchetypeChunkEntityType();
    47.         var stateType = GetArchetypeChunkComponentType<T>();
    48.         var lastStateType = GetArchetypeChunkComponentType<Listen<T, U>>();
    49.  
    50.         foreach (var chunk in chunks)
    51.         {
    52.             var count    = chunk.Count;
    53.             var entities = chunk.GetNativeArray(entityType);
    54.             var lastStates   = chunk.GetNativeArray(lastStateType);
    55.             if (chunk.Has(stateType))
    56.             {
    57.                 var states = chunk.GetNativeArray(stateType);
    58.                 for (int i = 0; i < count; i++)
    59.                 {
    60.                     var e         = entities[i];
    61.                     var state     = states[i];
    62.                     var lastState = lastStates[i];
    63.  
    64.                     if (!lastState.Added)
    65.                     {
    66.                         lastState.Added = true;
    67.                         EntityManager.GetComponentObject<U>(e).OnAdded(state);
    68.                         PostUpdateCommands.SetComponent(e, lastState);
    69.                     }
    70.                     else
    71.                     {
    72.                         if (_isZeroSized) continue;
    73.                         if (FastEquality.Equals(ref state, ref lastState.LastValue, _type.FastEqualityTypeInfo))
    74.                             continue;
    75.  
    76.                         lastState.LastValue = state;
    77.  
    78.                         EntityManager.GetComponentObject<U>(e).OnSet(state);
    79.                         PostUpdateCommands.SetComponent(e, lastState);
    80.                     }
    81.                 }
    82.             }
    83.             else
    84.             {
    85.                 for (int i = 0; i < count; i++)
    86.                 {
    87.                     var e         = entities[i];
    88.                     var lastState = lastStates[i];
    89.  
    90.                     if (lastState.Added)
    91.                     {
    92.                         lastState.Added = false;
    93.  
    94.                         EntityManager.GetComponentObject<U>(e).OnRemoved();
    95.                         PostUpdateCommands.SetComponent(e, lastState);
    96.                     }
    97.                 }
    98.             }
    99.         }
    100.  
    101.         chunks.Dispose();
    102.     }
    103. }
     
  4. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Automatic running and instantiation of generic systems doesn't work unless you manually create them AND call ScriptBehaviourUpdateOrder.UpdatePlayerLoop(...):

    Code (CSharp):
    1. World aWorld = //... default or your own world
    2.  
    3. aWorld.GetOrCreateManager<ListenerSystem<Heating,HeatingListener>>();
    4.  
    5. // This sets the system to be Updated and also handle injecting other systems into it.
    6. // Extra injection hookups like GameObjectEntity handling will only work
    7. // like the default world with extra setup
    8. ScriptBehaviourUpdateOrder.UpdatePlayerLoop(theWorldYourSystemWasJustCreatedIn);
    You can supposedly customize the world's PlayerLoop further, but I haven't tried it yet. My setup is a bit different as I'm creating different worlds for different purposes (so they can have different barriers) and then transporting data between them, and the default world's injection hooks don't apply so you have to tell the system to update the world's player loops after adding or removing systems manually. For this case, it's best to setup everything in bootstrap and update the player loop last.
     
    e199 likes this.
  5. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    Worked like a charm, finally I can move to next feature after 7 hours :D
    phew..
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    If you're not dynamically creating these, I find it kind of cleaner (and stops you having to keep updating the player loop which can be very slow if you have ordering) to just create a stub class.

    instead of using a generic class directly,

    GetOrCreateManager<ListenerSystem<Heating,HeatingListener>>();


    can just make a quick class

    Code (CSharp):
    1. public class HeatingSystem : ListenerSystem<Heating,HeatingListener>
    2. {
    3.  
    4. }
    and use that instead. These will be auto created for you and you can just use

    GetOrCreateManager<HeatingSystem>();


    like normal and you don't need to worry about updating the player loop (as it's auto created and added)
     
    Last edited: Dec 1, 2018
    recursive, e199 and eizenhorn like this.
  7. e199

    e199

    Joined:
    Mar 24, 2015
    Posts:
    101
    Yep, that's what I did in the end
    Not that much boilerplate, so I can live with it :D

    Thanks