Search Unity

StateComponent - Reactive System

Discussion in 'Entity Component System' started by Spy-Shifty, Jun 1, 2018.

  1. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Hi,

    As I've noticed, you've added the reactive system behaviour to ecs.
    https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/ReleaseNotes.md

    As I figured out you provide a new SetFilterChanged method to the ComponentGroup class
    I imagin the usage is the following:
    Code (CSharp):
    1. someGroup = GetComponentGroup(typeof(ComponentA), typeof(ComponentB));
    2.  
    3. // give me only thouse entities who has changes on ComponentA
    4. someGroup.SetFilterChanged(typeof(ComponentA));
    5.  
    6. // or is it more like:
    7. // give me only thouse entities which has added ComponentA
    8. someGroup.SetFilterChanged(typeof(ComponentA));
    9.  
    10. // give me only thouse entities which has removed ComponentA
    11. someGroup.SetFilterChanged(ComponentType.Subtractive<NetworkSync>());
    12.  
    13. // give me only thouse entities who has changes on ComponentA AND ComponentB  (is it AND or OR?)
    14. someGroup.SetFilterChanged(new ComponentType[] { typeof(ComponentA), typeof(ComponentB) });
    15.  
    Could you explane how to use it?
    What happens on:
    • Component added to an entity
    • Component removed from an entity
    • Component setted to an entity
    • Entity was added with component
    • Entity got removed with component

    Is there a posibility to retrieve the SystemStateComponent? So that I can see if it was removed, added or setted?

    Can I check for created / removed entities?


    Thanks in advance!
     
  2. Arakon

    Arakon

    Joined:
    Mar 8, 2014
    Posts:
    23
    I'm also interested in some more information on this. I can't seem to find it referenced in any documentation or samples or other forum posts.
     
  3. dadude123

    dadude123

    Joined:
    Feb 26, 2014
    Posts:
    789
    @Joachim_Ante or one of the guys on his team is most likely writing a forum or blog post about it right now or very soon.

    It's a feature that many people want to use and I'm sure they are aware of that.
     
  4. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Ok I've made some tests:
    Code:
    Code (CSharp):
    1.  
    2. public struct ComponentA : IComponentData {
    3.  
    4. }
    5.  
    6. public struct ComponentB : IComponentData {
    7.     public int Value;
    8. }
    9.  
    10. public class TestUserInputSystem : ComponentSystem {
    11.  
    12.     struct Data {
    13.         public ComponentDataArray<ComponentA> data;
    14.         public int Length;
    15.         public EntityArray entity;
    16.     }
    17.     [Inject] Data data;
    18.  
    19.     protected override void OnCreateManager(int capacity) {
    20.         Enabled = true;
    21.  
    22.         EntityManager.CreateEntity(typeof(ComponentA));
    23.     }
    24.  
    25.     protected override void OnUpdate() {
    26.         for (int i = 0; i < data.Length; i++) {
    27.  
    28.             if (Input.GetKeyDown(KeyCode.Alpha1) && !EntityManager.HasComponent<ComponentB>(data.entity[i])) {
    29.                 PostUpdateCommands.AddComponent(data.entity[i], new ComponentB());
    30.             }
    31.             if (Input.GetKeyDown(KeyCode.Alpha2) && EntityManager.HasComponent<ComponentB>(data.entity[i])) {
    32.                 PostUpdateCommands.RemoveComponent<ComponentB>(data.entity[i]);
    33.             }
    34.  
    35.             if (Input.GetKeyDown(KeyCode.Alpha5) && EntityManager.HasComponent<ComponentB>(data.entity[i])) {
    36.                 PostUpdateCommands.SetComponent(data.entity[i], new ComponentB() { Value = 1});
    37.             }
    38.         }
    39.  
    40.         for (int i = 0; i < data.Length; i++) {
    41.             if (Input.GetKeyDown(KeyCode.Alpha4)) {
    42.                 PostUpdateCommands.DestroyEntity(data.entity[i]);
    43.                 break;
    44.             }
    45.         }
    46.  
    47.         if (Input.GetKeyDown(KeyCode.Alpha3)) {
    48.             PostUpdateCommands.CreateEntity();
    49.             PostUpdateCommands.AddComponent(new ComponentB());
    50.             PostUpdateCommands.AddComponent(new ComponentA());
    51.         }
    52.     }
    53. }
    54.  
    55. public class ReactiveSystem : ComponentSystem {
    56.  
    57.  
    58.     ComponentGroup reactiveGroup;
    59.     protected override void OnCreateManager(int capacity) {
    60.         reactiveGroup = GetComponentGroup(typeof(ComponentB), typeof(ComponentA));
    61.     }
    62.  
    63.     protected override void OnUpdate() {
    64.  
    65.         reactiveGroup.ResetFilter();
    66.         Debug.Log("Full: " + reactiveGroup.CalculateLength());
    67.  
    68.         reactiveGroup.SetFilterChanged(ComponentType.Create<ComponentB>());
    69.         Debug.Log("Additive: " + reactiveGroup.CalculateLength());
    70.  
    71.         reactiveGroup.SetFilterChanged(ComponentType.Subtractive<ComponentB>());
    72.         Debug.Log("Subtractive: " + reactiveGroup.CalculateLength());
    73.  
    74.         reactiveGroup.SetFilterChanged(new ComponentType[] { ComponentType.Create<ComponentA>(), ComponentType.Create<ComponentB>() });
    75.         Debug.Log("Combined: " + reactiveGroup.CalculateLength());
    76.     }
    77. }
    This is the result:

    There is no difference on
    ComponentType.Create<ComponentB>())
    ComponentType.Subtractive<ComponentB>())


    Sadly, there is no filter for removed components...

    The filter only reactes on:
    Components got added to the group or the value was changed.

    This is OR combined.
    Code (CSharp):
    1. reactiveGroup.SetFilterChanged(new ComponentType[] { ComponentType.Create<ComponentA>(), ComponentType.Create<ComponentB>() });


    What I would like to see in the future:
    A posibility to see components that which got removed from a entity.

    How would this look like?
    Code (CSharp):
    1. reactiveGroup.SetFilterChanged(ComponentType.Subtractive<ComponentB>());
    2.  
    3.         ComponentDataArray<ComponentB> componentBs = reactiveGroup.GetComponentDataArray<ComponentB>();
    4.         EntityArray entities = reactiveGroup.GetEntityArray();
    5.         for (int i = 0; i < componentBs.Length; i++) {
    6.             ComponentB compB = componentBs[i];
    7.             Entity entity = entities[i];
    8.             // ...
    9.         }
    10.  
    The deleted components will be saved in e.g. a separate array.
    They are really removed from the entity but I can access them a last time.
    Something like this.

    Usecase?
    Well I've a network component sync system. I wan't to check if a component was removed, so that I can remove this component on the remote part as well.
    Something like that.
     
    5argon and isbdnt like this.
  5. mike_acton

    mike_acton

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    110
    What is an ISystemStateComponentData?
    It's the same as an IComponentData but the lifetime rules are different. Specifically, when an entity is destroyed, all IComponentData are removed, but ISystemStateComponentData are not.

    This allows for systems to track or maintain state in a way that can be cleaned up reliably.
    By way of example, imagine a system which wants to store a hash table of Foo component entities and needs to add and remove them from the hash table reliably without rebuilding the whole table every frame. The system use a FooState system state component to track it's status.

    1. Foo exists, but FooState does not. Foo was added by some other system, and so needs to be initialized. Add Foo to the hash table and add FooState component.
    2. Foo exists, and FooState exists. Foo is being tracked.
    3. Foo does not exist, but FooState exists. Foo was deleted by some other system, indicating desire to remove it. (Perhaps by destroying the entity) FooSystem can now clean up by removing the entity from the hash table and removing the FooState component.
     
  6. wusticality

    wusticality

    Joined:
    Dec 15, 2016
    Posts:
    71
    Hey @mike_acton - what about ISystemStateSharedComponentData? I'm also curious if you guys would be willing to add components for systems themselves (something like ISystemComponentData). For example, you may need to load a bunch of data that a system uses but you don't want to hardcode it into the system itself, but instead parameterize the system's internal state. An example might be a system that renders sprites, but you set which atlases to use in a ISystemComponentData. Obviously this can be jerry rigged by just querying for the system and setting some internal state at bootstrap time, but it's kind of gross. Thoughts?
     
  7. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    I've built a small example based on the snippets of code Joachim showed at the Unity Berlin talk.
    The Add and Remove "triggers" work, but when I try to schedule the Job for changes Unity just crashes.
    When I remove the [ChangedFilter] Attribute it runs without a crash. I'll probably figure out later what I am doing wrong.

    Code (CSharp):
    1. [UnityEngine.ExecuteInEditMode]
    2. [UpdateAfter(typeof(Transform2DSystem))]
    3. public class ReactiveSystem : ComponentSystem
    4. {
    5.     struct InternalScale2DState : ISystemStateComponentData
    6.     {
    7.         public int internalID;
    8.     }
    9.  
    10.     struct Added
    11.     {
    12.         public EntityArray entities;
    13.         public ComponentDataArray<Scale2D> scales;
    14.         public SubtractiveComponent<InternalScale2DState> missing;
    15.     }
    16.     [Inject] Added added;
    17.  
    18.  
    19.     struct Removed
    20.     {
    21.         public EntityArray entities;
    22.         public ComponentDataArray<InternalScale2DState> internals;
    23.         public SubtractiveComponent<Scale2D> missing;
    24.     }
    25.     [Inject] Removed removed;
    26.      
    27.     struct Scale2DJob : IJobProcessComponentData<Scale2D>
    28.     {
    29.         public void Execute([ChangedFilter]ref Scale2D scale)
    30.         {
    31.         }
    32.     }
    33.  
    34.     protected override void OnUpdate()
    35.     {
    36.         for (int i = 0; i < added.entities.Length; ++i)
    37.         {
    38.             InternalScale2DState internalState = new InternalScale2DState();
    39.             PostUpdateCommands.AddComponent(added.entities[i], internalState);
    40.             UnityEngine.Debug.Log("InternalScale2DState added.");
    41.         }
    42.  
    43.         for (int i = 0; i < removed.entities.Length; ++i)
    44.         {
    45.             PostUpdateCommands.RemoveComponent<InternalScale2DState>(removed.entities[i]);
    46.             UnityEngine.Debug.Log("InternalScale2DState removed.");
    47.         }
    48.  
    49.         Scale2DJob job = new Scale2DJob();
    50.         job.Schedule(this).Complete();
    51.     }
    52. }
     
    noio likes this.
  8. mike_acton

    mike_acton

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    110
    > Hey @mike_acton - what about ISystemStateSharedComponentData?

    Yes. That's upcoming.
     
    MNNoxMortem likes this.
  9. dt665m

    dt665m

    Joined:
    Aug 15, 2015
    Posts:
    11

    I saw the use of the [ChangedFilter] attribute in the ecs tests before the berlin talk and figured it's not ready in the current version. Unity just hangs without errors if that attribute is there.
     
  10. PanMadzior

    PanMadzior

    Joined:
    Jun 4, 2015
    Posts:
    10
    I've managed to do a system that reacts to a component changes using ISystemStateComponentData. I haven't been doing any performance tests yet, but for the time there's no official way to do that kind of system I think it is a good method. Basically, I make a private ISystemStateComponentData struct with a public field of the component that I'm interested to react to.
    There's some example code for this (sorry for any possible errors - I wrote it in the Notepad++ :), but you get the idea):

    Code (CSharp):
    1. public struct SomeComponent : IComponentData
    2. {
    3.     public int someValue;
    4. }
    5.  
    6. public class SomeSystem : ComponentSystem
    7. {
    8.     private struct CompState : ISystemStateComponentData {
    9.         public SomeComponent value;
    10.     }
    11.    
    12.     private struct AddedData {
    13.         public int Length;
    14.         public EntityArray entities;
    15.         public ComponentDataArray<SomeComponent> someComponent;
    16.         public SubtractiveComponent<CompState> compState;
    17.     }
    18.    
    19.     private struct RemovedData {
    20.         public int Length;
    21.         public EntityArray entities;
    22.         public SubtractiveComponent<SomeComponent> someComponent;
    23.         public ComponentDataArray<CompState> compState;
    24.     }
    25.    
    26.     private struct ChangedData {
    27.         public int Length;
    28.         public EntityArray entities;
    29.         public ComponentDataArray<SomeComponent> someComponent;
    30.         public ComponentDataArray<CompState> compState;
    31.     }
    32.    
    33.     [Inject]
    34.     private AddedData added;
    35.     [Inject]
    36.     private RemovedData removed;
    37.     [Inject]
    38.     private ChangedData changed;
    39.    
    40.     protected override void OnUpdate() {
    41.         // added
    42.         for(int i = 0; i < added.Length; i++){
    43.             // do some stuff
    44.             PostUpdateCommands.AddComponentData( added.entities[i], new CompState() { value = added.someComponent[i] } );
    45.         }
    46.        
    47.         // removed
    48.         for(int i = 0; i < removed.Length; i++){
    49.             // do some stuff
    50.             PostUpdateCommands.RemoveComponent<CompState>( removed.entities[i] );
    51.         }
    52.        
    53.         // changed
    54.         for(int i = 0; i < added.Length; i++){
    55.             if( changed.compState[i].value != changed.someComponent[i] ) {
    56.                 // there are some changed to the component, do some stuff
    57.                 PostUpdateCommands.SetComponentData( changed.entities[i], new CompState() { value = changed.someComponent[i] ] );
    58.             } else {
    59.                 // there are no changes
    60.             }
    61.         }
    62.     }
    63. }
     
    T-Zee and rigidbuddy like this.
  11. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    changed data can be handled by using

    Code (CSharp):
    1. someGroup.SetFilterChanged(typeof(ComponentA));
    this also recognizes adds..
     
  12. PanMadzior

    PanMadzior

    Joined:
    Jun 4, 2015
    Posts:
    10
    Yeah, I've been playing with this filter but I couldn't make it work. First of all, I find [Inject] being more clear to me, but [ChangedFilter] on ComponentDataArray<T> seems to be not working atm. I made it run with the SetFilterChanged method, but instead of components that changed, I got all the entities with specified component. In other words - the system was reacting to changes, but I was not able to tell which entities are actually changed. Or is this a correct behavior? (if so I would still need to filter the entities if they are actually changed)
     
  13. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Normally, you should only receive entities that have had a change to the filtered component since the last update.
     
    PanMadzior likes this.
  14. PanMadzior

    PanMadzior

    Joined:
    Jun 4, 2015
    Posts:
    10
    Good to know. I'll investigate it later. Maybe there is a stupid bug in my code. o_O
     
  15. JeffreyDufseth

    JeffreyDufseth

    Joined:
    Jul 11, 2017
    Posts:
    2
    Heya. I'm having similar issues with Inject. I'm using entities 0.0.12-preview 8. It just seems to run every time. Here's the component group:

    Code (CSharp):
    1. struct MyData
    2.     {
    3.         public readonly int Length;
    4.         [ReadOnly] public EntityArray EntityArray;
    5.         [ReadOnly] public ComponentDataArray<MyComponentA> MyComponentAArray;
    6.         [ReadOnly, ChangedFilter] public ComponentDataArray<MyComponentB> MyComponentBArray;
    7.     }
    8.     [Inject] MyData m_myData;
    I'm not sure if I'm doing something wrong, or if the ReactiveSystems aren't in this version yet. Anything jump out?
     
  16. Fabrice_Lete

    Fabrice_Lete

    Unity Technologies

    Joined:
    May 5, 2018
    Posts:
    55
    Right now, [ChangedFilter] only works with IJobProcessComponentData, e.g.

    Code (CSharp):
    1. public void Execute([ChangedFilter] ref MyComponentData data)
    The granularity is chunks, and it will react to things that have been potentially modified (in other words, things that have been accessed as writable).
     
    5argon and GarthSmith like this.
  17. JeffreyDufseth

    JeffreyDufseth

    Joined:
    Jul 11, 2017
    Posts:
    2
    Oh sweet, thanks!
     
  18. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    Just a note, you cannot use ReadOnly when you use ChangedFilter. I assume this is because Unity is resetting some kind of flag or something on the component that has changed when the Execute method runs, but I'm not sure about this.
     
  19. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    That should work fine. Do you have an example of where that doesn't work?
     
  20. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    1,191
    @Joachim_Ante

    Yes. You should be able to drop the file I uploaded onto a game object in a new scene and hit Play. I just put everything into that one file to make things easier. I am manually triggering my System updates when the E key is pressed on the keyboard, so maybe the issue has something to do with that.

    The test is working with 10 entities, each is given an Index component with an int value between 1 and 10. Each entity also has an Even component, Odd component, and ChangeDetected component. Basically all that I am doing is selecting which entities I want to change (even or odd) via the inspector (so there are four permutations, change none, change even only, change odd only, or change both). Entities with even Indexes have their Even component changed when that option is selected in the inspector. Same for entities with odd Indexes and their Odd component. This is done via the ChangeSystem, and before its Update method is called each execution cycle, I load the inspector options via a public LoadMethod, so that I can change which entities are being updated each execution cycle.

    The DetectSystem is the reactive system that is meant to detect when an Even or Odd component changes. I built this tests because I wanted to see what happens when you have more than one component whose changes you are monitoring. Is the job triggered when either component changes, or do both have to change? That is the reason for having two different components that are being reacted to.

    Anyway, it just writes a new ChangeDetected component to the entity with a value of 1 (vs its default value of 0), so then the PrintResultsSystem just checks if any entities have a ChangeDetected value of 1. If so, then a change has successfully been detected. The ChangeDetected values are reset to 0 after that, so the next execution starts with a clean slate.

    Now, the issue that is happening is the change is correctly being detected, but when I set the inspector so even and odd are not changed in the next execution cycle, it is still detecting a change. I.E., you can reproduce the error by:
    1. Enabling the "Change Even" option in the inspector.
    2. Enter Play Mode and press E to execute the systems, notice the printout in the console says Change detected.
    3. Disable the "Change Even" option in the inspector (while still in Play Mode).
    4. Press E again and notice the printout still says Change detected.
    I wouldn't doubt that there is potentially a bug in my own code, however if you remove the ReadOnly attributes from the Even and Odd components in the ChangeSystem, the faulty behaviour dissapears. Following the steps above will produce a Change detected printout followed by a No Change Detected printout.

    Since I have you hear, I have to bring up another potential bug. You will notice in the file I provided that there is a commented out alternate DetectSystem, one which uses ComponentGroup and the SetFilterChanged method (in combination with a IJobParallelFor, of course) to detect changes. It works, however there is a one frame delay in some cases where a change is not detected. To reproduce:

    1. Enter Play Mode with "Change Even" and "Change Odd" both disabled in the inspector.
    2. Press E to execute. Notice the printout correctly says No Change Detected.
    3. Enable "Change Even" or "Change Odd", or both.
    4. Press E. Notice the printout incorrectly reads No Change Detected.
    5. Press E again. Now it detects the change.
    I thought perhaps it was the scheduling of a job of length 0 (since in the first case no changes exist and thus the changes array will have a length of 0), but I added an early exit when the length is 0 and the problem still persist. I've seen others calling SetFilterChanged every OnUpdate call, but that didn't seem to effect anything. There could very well be a bug in my code here, but I can't find it. The fact that the other DetectSystem works flawlessly (when ReadOnly is not used) also implies there either isn't a bug, or it is limited to this DetectSystem.
     

    Attached Files:

    Last edited: Aug 21, 2018
  21. gromilQaaaa

    gromilQaaaa

    Joined:
    Oct 28, 2013
    Posts:
    14
    Is there a way to use ISystemStateComponentData without Inject? How do we deal with component remove tracking when Inject will be deprecated?
     
  22. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    As it is a IComponentData too you can use it everywhere you use non system state components, eg ComponentGroup, EntityArchetypeQuery and IJobProcessComponentData
     
    leni8ec likes this.