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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

ReactiveDots - reactive systems for DOTS

Discussion in 'Entity Component System' started by PanMadzior, Jun 19, 2022.

  1. PanMadzior

    PanMadzior

    Joined:
    Jun 4, 2015
    Posts:
    10
    Hello folks!

    Since the beginning of the DOTS ECS, I always wanted an easy way to create reactive systems without much boilerplate code. Recently I've been experimenting with this topic once again and I ended up with something that might be worth sharing.

    ReactiveDots is a small package that will enable you to make reactive systems and component event listeners easily with a help of Roslyn source generators!

    ReactiveDots features are highly inspired by another ECS framework, Entitas, which I've used every day for the last few years. In my spare time, I'm slowly learning DOTS. ReactiveDots is supposed to help move my workflows from Entitas to DOTS.

    I highly recommend looking at Github readme as I left there much more information about this package. Also, I'm open to suggestions and discussion since I'm not much experienced with DOTS.

    [Github repository]

    What is a reactive system?
    This is a system that can react when a specified component is added/changed/removed from an entity since the last system update. A deeper explanation of how it works can be found in the GitHub readme.

    Take a look at this example from a sample project. When an entity changes its MoveDirection component value, it bounced from something. BounceCountSystem is listening to changes of said component and increases the value of Bounces component.

    Code (CSharp):
    1. // React when a MoveDirection component is added/removed/changed.
    2. [ReactiveSystem( typeof(MoveDirection), typeof(MoveDirectionReactive) )]
    3. public partial class BounceCountSystem : SystemBase
    4. {
    5.     // Cache reactive data in a separate system state component.
    6.     public struct MoveDirectionReactive : ISystemStateComponentData
    7.     {
    8.         public ComponentReactiveData<MoveDirection> Value;
    9.     }
    10.  
    11.     protected override void OnUpdate()
    12.     {
    13.         // Update reactive data with an auto-generated extension method.
    14.         Dependency = this.UpdateReactive( Dependency );
    15.      
    16.         Entities.ForEach(
    17.             ( ref Bounces bounces, in MoveDirectionReactive moveDirReactive ) =>
    18.         {
    19.             // React only to the changes in the MoveDirection component
    20.             // and ignore when it is added to or removed from an entity.
    21.             if ( moveDirReactive.Value.Changed )
    22.                 bounces.Value += 1;
    23.         } ).ScheduleParallel();
    24.     }
    25. }
    What is a component event listener?
    It is an additional feature built on top of the reactive systems. With a simple attribute, you can mark specified components and source generators will generate code that will allow you to listen to component adds/changes/removes outside of the ECS systems. For example, you can define listeners in your UI code to reflect gameplay changes on UI.

    Code (CSharp):
    1. [ReactiveEvent] // Mark component with ReactiveEvent attribute.
    2. public struct Foo : IComponentData
    3. {
    4.     public int Value;
    5. }
    6.  
    7. public class Bar : MonoBehaviour,
    8.     IAnyFooAddedListener, // Implement added/changed/removed auto generated interface.
    9.     IAnyFooChangedListener,
    10.     IAnyFooRemovedListener
    11. {
    12.     private void Awake()
    13.     {
    14.         var entityManager  = World.DefaultGameObjectInjectionWorld.EntityManager;
    15.         // Create listener entity.
    16.         var listenerEntity = entityManager.CreateEntity();
    17.         // Add auto-generated event components to register your listeners.
    18.         entityManager.AddComponentData( listenerEntity,
    19.            new AnyFooAddedListener() { Value = this } );
    20.         entityManager.AddComponentData( listenerEntity,
    21.            new AnyFooRemovedListener() { Value = this } );
    22.         entityManager.AddComponentData( listenerEntity,
    23.            new AnyFooChangedListener() { Value = this } );
    24.     }
    25.  
    26.     // React when any entity got Foo component added...
    27.     public void OnAnyBouncesAdded( Entity entity, Foo foo, World world )
    28.     {
    29.         Debug.Log( $"New foo component with value={foo.Value}" );
    30.     }
    31.  
    32.     // ...or changed...
    33.     public void OnAnyBouncesChanged( Entity entity, Foo foo, World world )
    34.     {
    35.         Debug.Log( $"Foo component updated value={foo.Value}" );
    36.     }
    37.  
    38.     // ...or removed.
    39.     public void OnAnyBouncesRemoved( Entity entity, World world )
    40.     {
    41.         Debug.Log( "Foo component removed!" );
    42.     }  
    43. }
     
  2. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    910
    I've only skimmed parts of the code so I can confirm that you are relying on structural changes for events which is absolutely not recommended. Terrible scaling issues.

    Still, one of the better implementations of this type that could be improved once the enable/disabled components are out in 1.0. And really nice codegen!
     
    PanMadzior likes this.
  3. PanMadzior

    PanMadzior

    Joined:
    Jun 4, 2015
    Posts:
    10
    Thanks for checking out!

    Yeah, structural changes when adding and removing cache components is my main concern now. This is something I definitely want to work on when I got some time. Delegating structural changes with command buffers might be a valid option, but I think that you won't be able to get info about adds and removes of the component in the same frame that these happen. Enableable components should help with that.

    I also assumed that adding and removing reactive components will be fairly rare in a game's logic and most of the time you will want to only check if the component has changed. Reality might be much different tho!
     
  4. tobiasaxenbeck

    tobiasaxenbeck

    Joined:
    Jul 4, 2022
    Posts:
    2
    Hey PanMadzior, with Entities 1.0 being out now and enable/disable components being part of it, any plans to make use of them to improve on the structural changes issue?
     
  5. PanMadzior

    PanMadzior

    Joined:
    Jun 4, 2015
    Posts:
    10
    Hi tobiasaxenbeck! It is something I would love to do when I have some extra free time. However, I cannot give you an ETA. I haven't had a chance to put my hands on 1.0 yet.