Search Unity

Resolved Reactive system for everyone

Discussion in 'Entity Component System' started by genaray, Sep 23, 2021.

  1. genaray

    genaray

    Joined:
    Feb 8, 2017
    Posts:
    191
    If you ever wanted to REACT on added or removed components, then this is for you !

    With the power of an reactive system, you will be listening to added or removed components in no time ! And the best... its easy to use and saves a lot of boilerplate code.

    Features ?
    • Listen for added Components via mainthread callbacks
    • Listen for removed Components via mainthread callbacks
    • Iterate over added Components via component markers and Entities.ForEach
    • Iterate over removed Components via component markers and Entities.ForEach
    • Uses IStateComponents to copy the component each frame, great for disposals
    Whats still missing ?
    • Listen for changes via callbacks
    Why isnt it working as expected ?
    • Make sure the created entities and added components were created by the
      BeginInitializationEntityCommandBufferSystem. Entities and their components need to exist since the start of the frame. When they appear somewhere in between, the reactive system is not capable of tracking those changes from the start which might cause problems.

    Code (CSharp):
    1.  
    2. [assembly: RegisterGenericComponentType(typeof(ReactiveSystem<Inventory, InventoryAdded, InventoryRemoved>.State))]
    3. [assembly: RegisterGenericComponentType(typeof(ManagedReactiveSystem<GameObject, GameObjectAdded, GameObjectRemoved>.State))]
    4.  
    5. namespace Script.Client.ECS.Systems.Reactive {
    6.  
    7.     // Fast, multithreaded for burst compatible components
    8.  
    9.     // This is added to the entity if the desired component was added to mark it
    10.     [BurstCompile]
    11.     public struct InventoryAdded : IComponentData { }
    12.  
    13.     // This is added to the entity if the desired component was removed
    14.     [BurstCompile]
    15.     public struct InventoryRemoved: IComponentData { }
    16.  
    17.     // Creating an system which attaches "InventoryAdded" once the Inventory component was added and "InventoryRemoved" once it was removed.
    18.     public class InventoryReactiveSystem : ReactiveSystem<Inventory, InventoryAdded, InventoryRemoved> {}
    19.  
    20.     // Also works for managed objects, a bit slower
    21.     [BurstCompile]
    22.     public struct GameObjectAdded : IComponentData { }
    23.  
    24.     [BurstCompile]
    25.     public struct GameObjectRemoved : IComponentData { }
    26.  
    27.     public class GameObjectReactiveSystem : ManagedReactiveSystem<GameObject, GameObjectAdded, GameObjectRemoved> {}
    28. }
    29.  
    And then you are ready to either hook up callbacks ( running on the mainthread ).
    Or you can use the component markers to loop over the entities which works great with schedule or scheduleParallel.

    Code (CSharp):
    1.  
    2.  
    3. myReactiveSystemReference.OnAdded += (ref Entity en, ref Inventory ia) => {
    4.    // Execute logic, inventory was just added
    5. };
    6.  
    7. myReactiveSystemReference.OnRemoved += (ref Entity, in Inventory ia) => {
    8.    // Execute logic, inventory was just removed
    9. };
    10.  
    11. Entities.ForEach((ref Inventory inv, ref InventoryAdded iva) => {
    12.    // Runs once, some cool logic to trigger something
    13. }).Schedule();
    14.  
    15. Entities.ForEach((ref Entity en, ref InventoryRemoved iva) => {
    16.    // Runs once, great for triggering logic or cleaning up stuff.
    17.    var state = GetComponent<InventoryReactiveSystem.State>(en);
    18.    state.component.items.Dispose();
    19. }).Schedule();
    And here is the reactive system :)
    If you find any improvements in useability, performance or whatever... Just tell me.

    But i hope this helps people.

    Code (CSharp):
    1.  
    2. /// <summary>
    3. /// A delegate being invoked once the <see cref="ReactiveSystem{TComponent,TAdded,TRemoved}"/> added an component
    4. /// </summary>
    5. /// <typeparam name="T"></typeparam>
    6. public delegate void OnAdded<T>(ref Entity entity, ref T addedComponent) where T : struct, IComponentData;
    7.  
    8. /// <summary>
    9. /// A delegate being invoked once the <see cref="ReactiveSystem{TComponent,TAdded,TRemoved}"/> removed an component
    10. /// </summary>
    11. /// <typeparam name="T"></typeparam>
    12. public delegate void OnRemoved<T>(ref Entity entity, in T removedComponent) where T : struct, IComponentData;
    13.  
    14. /// <summary>
    15. /// A delegate being invoked once the <see cref="ReactiveSystem{TComponent,TAdded,TRemoved}"/> added an component
    16. /// </summary>
    17. /// <typeparam name="T"></typeparam>
    18. public delegate void OnAddedClass<T>(ref Entity entity, ref T addedComponent) where T : class;
    19.  
    20. /// <summary>
    21. /// A delegate being invoked once the <see cref="ReactiveSystem{TComponent,TAdded,TRemoved}"/> removed an component
    22. /// </summary>
    23. /// <typeparam name="T"></typeparam>
    24. public delegate void OnRemovedClass<T>(ref Entity entity, in T removedComponent) where T : class;
    25.  
    26. /// <summary>
    27. ///     There no callbacks or listeners for added/removed components on <see cref="Entity" />'s
    28. ///     Thats where this system comes in using <see cref="ISystemStateComponentData" /> for simulating those callbacks inside the ecs.
    29. ///     <typeparam name="Component">The component we wanna listen to</typeparam>
    30. ///     <typeparam name="Added">The component which indicates that our component has been added, gets attached for one frame to the entity</typeparam>
    31. ///     <typeparam name="Removed">The component which indicates that our component was removed, gets attached for one frame to the entity</typeparam>
    32. /// </summary>
    33. [UpdateInGroup(typeof(InitializationSystemGroup))]
    34. public abstract class ReactiveSystem<TComponent, TAdded, TRemoved> : JobComponentSystem where TComponent : struct, IComponentData where TAdded : struct, IComponentData where TRemoved : struct,IComponentData {
    35.  
    36.     private EndInitializationEntityCommandBufferSystem atFrameStartBuffer;
    37.  
    38.     public OnAdded<TComponent> OnAdded;
    39.     public OnRemoved<TComponent> OnRemoved;
    40.  
    41.     protected EntityQuery newEntities;
    42.     protected EntityQuery entitiesWithAdded;
    43.     protected EntityQuery entitiesWithStateOnly;
    44.     protected EntityQuery toRemoveEntities;
    45.  
    46.     protected EntityQuery copyEntities;
    47.  
    48.     protected override void OnCreate() {
    49.         base.OnCreate();
    50.  
    51.         atFrameStartBuffer = World.GetOrCreateSystem<EndInitializationEntityCommandBufferSystem>();
    52.  
    53.         OnAdded += (ref Entity en, ref TComponent component) => { };
    54.         OnRemoved += (ref Entity en, in TComponent component) => { };
    55.      
    56.         // Query to get all newly created entities, without being marked as added
    57.         newEntities = GetEntityQuery(new EntityQueryDesc {
    58.             All = new[] {ComponentType.ReadOnly<TComponent>()},
    59.             None = new[] {ComponentType.ReadOnly<TAdded>(), ComponentType.ReadOnly<State>()}
    60.         });
    61.      
    62.         // Query of all entities which where added this frame
    63.         entitiesWithAdded = GetEntityQuery(new EntityQueryDesc {
    64.             All = new[] { ComponentType.ReadOnly<State>(), ComponentType.ReadOnly<TAdded>()},
    65.             None = new[] {ComponentType.ReadOnly<TRemoved>()}
    66.         });
    67.      
    68.         // Query of all entities which where added this frame
    69.         entitiesWithStateOnly = GetEntityQuery(new EntityQueryDesc {
    70.             All = new[] { ComponentType.ReadOnly<State>()},
    71.             None = new[] {ComponentType.ReadOnly<TComponent>(), ComponentType.ReadOnly<TAdded>(), ComponentType.ReadOnly<TRemoved>()}
    72.         });
    73.  
    74.         // Query of all entities which where removed this frame
    75.         toRemoveEntities = GetEntityQuery(new EntityQueryDesc {
    76.             All = new[] {ComponentType.ReadOnly<State>(), ComponentType.ReadOnly<TRemoved>()},
    77.             None = new[] {ComponentType.ReadOnly<TComponent>(), ComponentType.ReadOnly<TAdded>()}
    78.         });
    79.      
    80.         // Query entities which require a copy of the state each frame
    81.         copyEntities = GetEntityQuery(new EntityQueryDesc {
    82.             All = new[] {ComponentType.ReadOnly<TComponent>(), ComponentType.ReadWrite<State>()}
    83.         });
    84.     }
    85.  
    86.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    87.      
    88.         var ecb = atFrameStartBuffer.CreateCommandBuffer();
    89.         var ecbParallel = atFrameStartBuffer.CreateCommandBuffer().AsParallelWriter();
    90.  
    91.         var addedEntityCount = newEntities.CalculateEntityCount();
    92.         var removedEntityCound = entitiesWithStateOnly.CalculateEntityCount();
    93.      
    94.         var added = new NativeList<Transmution>(addedEntityCount, Allocator.TempJob);
    95.         var removed = new NativeList<Transmution>(removedEntityCound, Allocator.TempJob);
    96.  
    97.         // Add the added component job
    98.         var addedJob = new AddedJob{
    99.             ecb = ecbParallel,
    100.             entityHandle = GetEntityTypeHandle(),
    101.             componentHandle = GetComponentTypeHandle<TComponent>(true),
    102.             added = added.AsParallelWriter()
    103.         };
    104.         addedJob.ScheduleParallel(newEntities, inputDeps).Complete();
    105.      
    106.         // Call the reactor
    107.         for (var index = 0; index < added.Length; index++) {
    108.  
    109.             var addedTransmution = added[index];
    110.             OnAdded(ref addedTransmution.entity, ref addedTransmution.component);
    111.             ecb.SetComponent(addedTransmution.entity, addedTransmution.component);
    112.         }
    113.      
    114.         // Add the remove component job
    115.         var removeJob = new RemovedJob{
    116.             ecb = ecbParallel,
    117.             entityHandle = GetEntityTypeHandle(),
    118.             componentHandle = GetComponentTypeHandle<State>(true),
    119.             reactors = removed.AsParallelWriter()
    120.         };
    121.         removeJob.ScheduleParallel(entitiesWithStateOnly, inputDeps).Complete();
    122.      
    123.         // Call the reactor to inform about removed
    124.         for (var index = 0; index < removed.Length; index++) {
    125.  
    126.             var removedTransmution = removed[index];
    127.             OnRemoved(ref removedTransmution.entity, in removedTransmution.component);
    128.         }
    129.      
    130.         // Remove the added component
    131.         var removeAddedJob = new RemoveAddedJob{
    132.             ecb = ecbParallel,
    133.             entityHandle = GetEntityTypeHandle(),
    134.         };
    135.         inputDeps = removeAddedJob.ScheduleParallel(entitiesWithAdded, inputDeps);
    136.  
    137.         // Remove the removed component
    138.         var removeRemovedJob = new RemoveRemovedJob{
    139.             ecb = ecbParallel,
    140.             entityHandle = GetEntityTypeHandle(),
    141.         };
    142.         inputDeps = removeRemovedJob.ScheduleParallel(toRemoveEntities, inputDeps);
    143.      
    144.         // Create job to copy the TComponent into the state
    145.         var copyJob = new CopyJob {
    146.             ComponentTypeHandle = GetComponentTypeHandle<TComponent>(true),
    147.             StateTypeHandle = GetComponentTypeHandle<State>()
    148.         };
    149.         inputDeps = copyJob.ScheduleParallel(copyEntities, inputDeps);
    150.  
    151.         // Dispose and add make ecb concurrent
    152.         atFrameStartBuffer.AddJobHandleForProducer(inputDeps);
    153.         added.Dispose();
    154.         removed.Dispose();
    155.      
    156.         return inputDeps;
    157.     }
    158.  
    159.     /// <summary>
    160.     /// A job which runs asynchron and copies the <see cref="TComponent"/> into the <see cref="State"/> in an fast and efficient, generic way.
    161.     /// </summary>
    162.     [BurstCompile]
    163.     private struct AddedJob : IJobChunk {
    164.  
    165.         public EntityCommandBuffer.ParallelWriter ecb;
    166.  
    167.         [ReadOnly] public EntityTypeHandle entityHandle;
    168.         [ReadOnly] public ComponentTypeHandle<TComponent> componentHandle;
    169.      
    170.         public NativeList<Transmution>.ParallelWriter added;
    171.  
    172.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    173.  
    174.             // Get original component and the state array
    175.             var entityArray = chunk.GetNativeArray(entityHandle);
    176.             var componentArray = chunk.GetNativeArray(componentHandle);
    177.  
    178.             // Copy the component into the state
    179.             for (var i = 0; i < chunk.Count; i++) {
    180.  
    181.                 var entity = entityArray[i];
    182.                 var component = componentArray[i];
    183.              
    184.                 var transmution = new Transmution {entity = entity, component = component};
    185.                 added.AddNoResize(transmution);
    186.  
    187.                 ecb.AddComponent(chunkIndex, entity, new TAdded());
    188.                 ecb.AddComponent(chunkIndex, entity, new State {component = component});
    189.             }
    190.         }
    191.     }
    192.  
    193.     /// <summary>
    194.     /// A job which removes the <see cref="TAdded"/> from the entity because its a one frame marker
    195.     /// </summary>
    196.     [BurstCompile]
    197.     private struct RemoveAddedJob : IJobChunk {
    198.  
    199.         public EntityCommandBuffer.ParallelWriter ecb;
    200.      
    201.         [ReadOnly] public EntityTypeHandle entityHandle;
    202.  
    203.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    204.  
    205.             // Get original component and the state array
    206.             var entityArray = chunk.GetNativeArray(entityHandle);
    207.  
    208.             // Remove the added
    209.             for (var i = 0; i < chunk.Count; i++) {
    210.  
    211.                 var entity = entityArray[i];
    212.                 ecb.RemoveComponent<TAdded>(chunkIndex, entity);
    213.             }
    214.         }
    215.     }
    216.  
    217.     /// <summary>
    218.     /// A job which runs asynchron and copies the <see cref="TComponent"/> into the <see cref="State"/> in an fast and efficient, generic way.
    219.     /// </summary>
    220.     [BurstCompile]
    221.     private struct RemovedJob : IJobChunk {
    222.      
    223.         public EntityCommandBuffer.ParallelWriter ecb;
    224.      
    225.         [ReadOnly] public EntityTypeHandle entityHandle;
    226.         [ReadOnly] public ComponentTypeHandle<State> componentHandle;
    227.  
    228.         public NativeList<Transmution>.ParallelWriter reactors;
    229.  
    230.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    231.  
    232.             // Get original component and the state array
    233.             var entityArray = chunk.GetNativeArray(entityHandle);
    234.             var componentArray = chunk.GetNativeArray(componentHandle);
    235.  
    236.             // Copy the component into the state
    237.             for (var i = 0; i < chunk.Count; i++) {
    238.  
    239.                 var entity = entityArray[i];
    240.                 var state = componentArray[i];
    241.                 var oldState = state.component;
    242.              
    243.                 var transmution = new Transmution {entity = entity, component = oldState};
    244.                 reactors.AddNoResize(transmution);
    245.  
    246.                 ecb.AddComponent(chunkIndex, entity, new TRemoved());
    247.             }
    248.         }
    249.     }
    250.  
    251.     /// <summary>
    252.     /// A job which runs asynchron and copies the <see cref="TComponent"/> into the <see cref="State"/> in an fast and efficient, generic way.
    253.     /// </summary>
    254.     [BurstCompile]
    255.     private struct RemoveRemovedJob : IJobChunk {
    256.      
    257.         public EntityCommandBuffer.ParallelWriter ecb;
    258.      
    259.         [ReadOnly] public EntityTypeHandle entityHandle;
    260.  
    261.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    262.  
    263.             // Get original component and the state array
    264.             var entityArray = chunk.GetNativeArray(entityHandle);
    265.  
    266.             // Copy the component into the state
    267.             for (var i = 0; i < chunk.Count; i++) {
    268.  
    269.                 var entity = entityArray[i];
    270.                 ecb.RemoveComponent<TRemoved>(chunkIndex, entity);
    271.                 ecb.RemoveComponent<State>(chunkIndex, entity);
    272.             }
    273.         }
    274.     }
    275.  
    276.     /// <summary>
    277.     /// A job which runs asynchron and copies the <see cref="TComponent"/> into the <see cref="State"/> in an fast and efficient, generic way.
    278.     /// </summary>
    279.     [BurstCompile]
    280.     private struct CopyJob : IJobChunk {
    281.      
    282.         [ReadOnly] public ComponentTypeHandle<TComponent> ComponentTypeHandle;
    283.         public ComponentTypeHandle<State> StateTypeHandle;
    284.  
    285.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    286.  
    287.             // Get original component and the state array
    288.             var componentArray = chunk.GetNativeArray(ComponentTypeHandle);
    289.             var stateArray = chunk.GetNativeArray(StateTypeHandle);
    290.          
    291.             // Copy the component into the state
    292.             for (var i = 0; i < chunk.Count; i++) {
    293.  
    294.                 var component = componentArray[i];
    295.                 stateArray[i] = new State {component = component};
    296.             }
    297.         }
    298.     }
    299.  
    300.     /// <summary>
    301.     /// The internal state to mark entities
    302.     /// </summary>
    303.     [BurstCompile]
    304.     public struct State : ISystemStateComponentData {
    305.         public TComponent component;
    306.     }
    307.  
    308.     /// <summary>
    309.     /// An struct which represents an transmution of the <see cref="TComponent"/> which was either added or removed.
    310.     /// </summary>
    311.     public struct Transmution {
    312.         public Entity entity;
    313.         public TComponent component;
    314.     }
    315. }
    316.  
    317. /// <summary>
    318. ///     There no callbacks or listeners for added/removed components on <see cref="Entity" />'s
    319. ///     Thats where this system comes in using <see cref="ISystemStateComponentData" /> for simulating those callbacks inside the ecs.
    320. ///     <typeparam name="Component">The component we wanna listen to</typeparam>
    321. ///     <typeparam name="Added">The component which indicates that our component has been added, gets attached for one frame to the entity</typeparam>
    322. ///     <typeparam name="Removed">The component which indicates that our component was removed, gets attached for one frame to the entity</typeparam>
    323. /// </summary>
    324. [UpdateInGroup(typeof(InitializationSystemGroup))]
    325. public abstract class ManagedReactiveSystem<TComponent, TAdded, TRemoved> : JobComponentSystem where TComponent : class where TAdded : struct, IComponentData where TRemoved : struct, IComponentData {
    326.  
    327.      private EndInitializationEntityCommandBufferSystem atFrameStartBuffer;
    328.  
    329.     public OnAddedClass<TComponent> OnAdded;
    330.     public OnRemovedClass<TComponent> OnRemoved;
    331.  
    332.     protected EntityQuery newEntities;
    333.     protected EntityQuery entitiesWithAdded;
    334.     protected EntityQuery entitiesWithStateOnly;
    335.     protected EntityQuery toRemoveEntities;
    336.  
    337.     protected EntityQuery copyEntities;
    338.  
    339.     protected override void OnCreate() {
    340.         base.OnCreate();
    341.  
    342.         atFrameStartBuffer = World.GetOrCreateSystem<EndInitializationEntityCommandBufferSystem>();
    343.  
    344.         OnAdded += (ref Entity en, ref TComponent component) => { };
    345.         OnRemoved += (ref Entity en, in TComponent component) => { };
    346.      
    347.         // Query to get all newly created entities, without being marked as added
    348.         newEntities = GetEntityQuery(new EntityQueryDesc {
    349.             All = new[] {ComponentType.ReadOnly<TComponent>()},
    350.             None = new[] {ComponentType.ReadOnly<TAdded>(), ComponentType.ReadOnly<State>()}
    351.         });
    352.      
    353.         // Query of all entities which where added this frame
    354.         entitiesWithAdded = GetEntityQuery(new EntityQueryDesc {
    355.             All = new[] { ComponentType.ReadOnly<State>(), ComponentType.ReadOnly<TAdded>()},
    356.             None = new[] {ComponentType.ReadOnly<TRemoved>()}
    357.         });
    358.      
    359.         // Query of all entities which where added this frame
    360.         entitiesWithStateOnly = GetEntityQuery(new EntityQueryDesc {
    361.             All = new[] { ComponentType.ReadOnly<State>()},
    362.             None = new[] {ComponentType.ReadOnly<TComponent>(), ComponentType.ReadOnly<TAdded>(), ComponentType.ReadOnly<TRemoved>()}
    363.         });
    364.  
    365.         // Query of all entities which where removed this frame
    366.         toRemoveEntities = GetEntityQuery(new EntityQueryDesc {
    367.             All = new[] {ComponentType.ReadOnly<State>(), ComponentType.ReadOnly<TRemoved>()},
    368.             None = new[] {ComponentType.ReadOnly<TComponent>(), ComponentType.ReadOnly<TAdded>()}
    369.         });
    370.      
    371.         // Query entities which require a copy of the state each frame
    372.         copyEntities = GetEntityQuery(new EntityQueryDesc {
    373.             All = new[] {ComponentType.ReadOnly<TComponent>(), ComponentType.ReadWrite<State>()}
    374.         });
    375.     }
    376.  
    377.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    378.      
    379.         var startCommandBuffer = atFrameStartBuffer.CreateCommandBuffer();
    380.  
    381.         var newEntitiesIterator = newEntities.GetArchetypeChunkIterator();
    382.         var entitiesWithAddedIterator = entitiesWithAdded.GetArchetypeChunkIterator();
    383.         var entitiesWithStateOnlyIterator = entitiesWithStateOnly.GetArchetypeChunkIterator();
    384.         var toRemoveEntitiesIterator = toRemoveEntities.GetArchetypeChunkIterator();
    385.         var copyEntitiesIterator = copyEntities.GetArchetypeChunkIterator();
    386.  
    387.         // Add the added component job
    388.         var addedJob = new AddedJob{
    389.             entityManager = EntityManager,
    390.             ecb = startCommandBuffer,
    391.             entityHandle = GetEntityTypeHandle(),
    392.             componentHandle = EntityManager.GetComponentTypeHandle<TComponent>(true),
    393.             reactor = OnAdded
    394.         };
    395.         addedJob.RunWithoutJobs(ref newEntitiesIterator);
    396.      
    397.         // Add the remove component job
    398.         var removeJob = new RemovedJob{
    399.             entityManager = EntityManager,
    400.             ecb = startCommandBuffer,
    401.             entityHandle = GetEntityTypeHandle(),
    402.             componentHandle = EntityManager.GetComponentTypeHandle<State>(true),
    403.             reactor = OnRemoved
    404.         };
    405.         removeJob.RunWithoutJobs(ref entitiesWithStateOnlyIterator);
    406.      
    407.         // Remove the added component
    408.         var removeAddedJob = new RemoveAddedJob{
    409.             ecb = startCommandBuffer,
    410.             entityHandle = GetEntityTypeHandle(),
    411.         };
    412.         removeAddedJob.RunWithoutJobs(ref entitiesWithAddedIterator);
    413.      
    414.         // Remove the removed component
    415.         var removeRemovedJob = new RemoveRemovedJob{
    416.             ecb = startCommandBuffer,
    417.             entityHandle = GetEntityTypeHandle(),
    418.         };
    419.         removeRemovedJob.RunWithoutJobs(ref toRemoveEntitiesIterator);
    420.      
    421.         // Create job to copy the TComponent into the state
    422.         var copyJob = new CopyJob {
    423.             entityManager = EntityManager,
    424.             componentTypeHandle = EntityManager.GetComponentTypeHandle<TComponent>(true),
    425.             stateTypeHandle = EntityManager.GetComponentTypeHandle<State>(false)
    426.         };
    427.         copyJob.RunWithoutJobs(ref copyEntitiesIterator);
    428.      
    429.         return inputDeps;
    430.     }
    431.  
    432.     /// <summary>
    433.     /// A job which runs asynchron and copies the <see cref="TComponent"/> into the <see cref="State"/> in an fast and efficient, generic way.
    434.     /// </summary>
    435.     private struct AddedJob : IJobChunk {
    436.  
    437.         public EntityManager entityManager;
    438.         public EntityCommandBuffer ecb;
    439.  
    440.         [ReadOnly] public EntityTypeHandle entityHandle;
    441.         [ReadOnly] public ComponentTypeHandle<TComponent> componentHandle;
    442.  
    443.         public OnAddedClass<TComponent> reactor;
    444.  
    445.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    446.  
    447.             // Get original component and the state array
    448.             var entityArray = chunk.GetNativeArray(entityHandle);
    449.             var componentArray = chunk.GetManagedComponentAccessor(componentHandle, entityManager);
    450.  
    451.             // Copy the component into the state
    452.             for (var i = 0; i < chunk.Count; i++) {
    453.  
    454.                 var entity = entityArray[i];
    455.                 var component = componentArray[i];
    456.              
    457.                 reactor(ref entity, ref component);
    458.  
    459.                 ecb.AddComponent(entity, new TAdded());
    460.                 ecb.AddComponent(entity, new State {component = component});
    461.             }
    462.         }
    463.     }
    464.  
    465.     /// <summary>
    466.     /// A job which removes the <see cref="TAdded"/> from the entity because its a one frame marker
    467.     /// </summary>
    468.     private struct RemoveAddedJob : IJobChunk {
    469.      
    470.         public EntityCommandBuffer ecb;
    471.         [ReadOnly] public EntityTypeHandle entityHandle;
    472.      
    473.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    474.  
    475.             // Get original component and the state array
    476.             var entityArray = chunk.GetNativeArray(entityHandle);
    477.  
    478.             // Remove the added
    479.             for (var i = 0; i < chunk.Count; i++) {
    480.  
    481.                 var entity = entityArray[i];
    482.                 ecb.RemoveComponent<TAdded>(entity);
    483.             }
    484.         }
    485.     }
    486.  
    487.     /// <summary>
    488.     /// A job which runs asynchron and copies the <see cref="TComponent"/> into the <see cref="State"/> in an fast and efficient, generic way.
    489.     /// </summary>
    490.     private struct RemovedJob : IJobChunk {
    491.  
    492.         public EntityManager entityManager;
    493.         public EntityCommandBuffer ecb;
    494.      
    495.         [ReadOnly] public EntityTypeHandle entityHandle;
    496.         [ReadOnly] public ComponentTypeHandle<State> componentHandle;
    497.  
    498.         public OnRemovedClass<TComponent> reactor;
    499.      
    500.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    501.  
    502.             // Get original component and the state array
    503.             var entityArray = chunk.GetNativeArray(entityHandle);
    504.             var componentArray = chunk.GetManagedComponentAccessor(componentHandle, entityManager);
    505.  
    506.             // Copy the component into the state
    507.             for (var i = 0; i < chunk.Count; i++) {
    508.  
    509.                 var entity = entityArray[i];
    510.                 var state = componentArray[i];
    511.                 var oldState = state.component;
    512.  
    513.                 reactor(ref entity, in oldState);
    514.  
    515.                 ecb.AddComponent(entity, new TRemoved());
    516.             }
    517.         }
    518.     }
    519.  
    520.      /// <summary>
    521.     /// A job which runs asynchron and copies the <see cref="TComponent"/> into the <see cref="State"/> in an fast and efficient, generic way.
    522.     /// </summary>
    523.      private struct RemoveRemovedJob : IJobChunk {
    524.      
    525.         public EntityCommandBuffer ecb;
    526.         [ReadOnly] public EntityTypeHandle entityHandle;
    527.  
    528.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    529.  
    530.             // Get original component and the state array
    531.             var entityArray = chunk.GetNativeArray(entityHandle);
    532.  
    533.             // Copy the component into the state
    534.             for (var i = 0; i < chunk.Count; i++) {
    535.  
    536.                 var entity = entityArray[i];
    537.                 ecb.RemoveComponent<TRemoved>(entity);
    538.                 ecb.RemoveComponent<State>(entity);
    539.             }
    540.         }
    541.     }
    542.  
    543.     /// <summary>
    544.     /// A job which runs asynchron and copies the <see cref="TComponent"/> into the <see cref="State"/> in an fast and efficient, generic way.
    545.     /// </summary>
    546.     private struct CopyJob : IJobChunk {
    547.  
    548.         public EntityManager entityManager;
    549.      
    550.         [ReadOnly] public ComponentTypeHandle<TComponent> componentTypeHandle;
    551.         public ComponentTypeHandle<State> stateTypeHandle;
    552.  
    553.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    554.  
    555.             // Get original component and the state array
    556.             var componentArray = chunk.GetManagedComponentAccessor(componentTypeHandle, entityManager);
    557.             var stateArray = chunk.GetManagedComponentAccessor(stateTypeHandle, entityManager);
    558.          
    559.             // Copy the component into the state
    560.             for (var i = 0; i < chunk.Count; i++) {
    561.  
    562.                 var component = componentArray[i];
    563.                 stateArray[i].component = component;
    564.             }
    565.         }
    566.     }
    567.  
    568.     /// <summary>
    569.     /// The internal state to mark entities
    570.     /// </summary>
    571.     public class State : ISystemStateComponentData {
    572.         public TComponent component;
    573.     }
    574. }
    575.  
     
    Last edited: Sep 27, 2021
  2. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    genaray likes this.
  3. genaray

    genaray

    Joined:
    Feb 8, 2017
    Posts:
    191
    I updated my reactive system and changed its foundation a bit... its now based on yours and combines our both approaches in an pretty efficient way.

    You can either register callbacks/delegates being invoked.
    Or you can use the component based way to iterate over them instead.

    @Unity please add such a system by yourself into the ecs framework... its a pain to come up with an own solution. Or take mine, feel free to test it.
     
    Last edited: Sep 26, 2021
  4. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Hello,

    I still have several remakes :

    1 ) the base generic system should still be abstract
    2 ) the base reactive system should probably not be in the initialization group as it will run every frame not only once on startup (at least that is my interpretation)
    3 ) you are still using jobcomponentsystem which seem to indicate an older version of the entities package, you should probably switch to SystemBase (or ISystemBase if you feel adventurous)
    4 ) Please note that the callback will be executed on the main thread.
    In some case it can be useful to bridge with mono behavior code for instance.
    But if you are full DOTS it's probably better to call the reactor from within the add/remove job instead of populating a temporary array and delegating the job to the main thread.
    5 ) For the component based reaction you may introduce a one frame delay depending on what buffer you use and where you put your reacting system.
     
    genaray likes this.
  5. genaray

    genaray

    Joined:
    Feb 8, 2017
    Posts:
    191
    Thanks ! :) Im gonna take a look at that once im home.

    So this reply does not cover all remakes yet.

    Are you sure that it will run every frame ? I actually picked the init group because it adds the most flexibility. Most systems that react on those changes may lie within the simulation, so i thought the init group is probably the best place for such an reactive system.

    Especially because it applies the changes before the frame starts without a frame delay. So the simulation group systems can run directly with the applied entity structure.

    Actually i wanted the callbacks be mono compatible. But yes, i really should try to invoke the callbacks directly in the jobs. Or to add an alternative. I just didnt knew if this would have worked because of the burst compatibility. Im gonna try that.
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,267
    InitializationSystemGroup runs every frame very early in the frame (before FixedUpdate, Update, and even scene streaming). It is "frame initialization". I like to treat the whole period as a mega sync point by stuffing all the systems that do instantiation and such in there and finish it with a transform system update (which does its own structural changes whenever there are other structural changes since the last update).
     
    WAYNGames, apkdev and genaray like this.
  7. genaray

    genaray

    Joined:
    Feb 8, 2017
    Posts:
    191
    Thanks for the clarification ! Thats why i thought its a great place to put the reactive systems in... To apply the transforms before the "frame" which should already make use of the transformed entities.
     
  8. genaray

    genaray

    Joined:
    Feb 8, 2017
    Posts:
    191
    So i had some time and updated the systems :) I also added an reactive system for managed component objects...
    Its much slower than the burst reactive systems, but could come in handy for people which often work with a hybrid approach.

    1. Updated them to be abstract, which of course makes much more sense
    2. I tested it myself and it looks like its not running every frame... atleast my systems editor window tells me so.
    3. On my list ^^
    4. Actually its pretty handy that these are getting executed on the main thread. For example great if you want to update the ui of an entity once a change occured. This way the ui can listen to the reactive system. Nevertheless i looked into an addition to those existing callbacks... the solution i found was using FunctionPointer<T> to delegates, but those were pretty restricted and not flexible enough. So i ended up ditching that idea.
    5. Hmmm... i actually think it makes more sense to apply the transformations directly instead of waiting one frame. Especially when the system lies within the init group. Im also not sure if its that easy to add such a delay. Do you think that this one frame delay might be required to unleash the full power of such an reactive system ?
     
    Last edited: Sep 27, 2021
  9. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    About the delay it is not required it's just the potential side effect of using the command buffer.
    If you run the reactive systems then the reacting system and finally playback the command you queued in the reactive system you will get a one frame delay.
    If you run the reactive system then playback the command and finally run the reacting system you won't have a delay.

    About 4 the full dots idea is more to make one sub system per reactor (like in my system) Otherwise even if all reactor match the same interface if they have different sizes you can't put them in a native array to run on the job. If you reactor data is static you could maybe work with blobasset tricks but it's most likely not static.