Search Unity

Question How to nest Entities.ForEach in a static function

Discussion in 'Entity Component System' started by hadesfury, May 27, 2020.

  1. hadesfury

    hadesfury

    Joined:
    Jan 23, 2014
    Posts:
    55
    Hello guys,

    Yesterday I found that ComponentSystem is nearly deprecated and that the right way to do is now using SystemBase as parent class for System.

    I just didn’t find a way to update my static utilities classes like the following one

    Code (CSharp):
    1.     public static class InventoryUtilities
    2.     {
    3.         public static Entity GetSelectedItem(EntityQueryBuilder entities, Entity playerEntity)
    4.         {
    5.             Entity selectedItemEntity = Entity.Null;
    6.          
    7.             entities.WithAll<SelectedItemComponent>().ForEach((Entity inventoryElementEntity, ref InventorySlotComponent inventorySlotComponent) =>
    8.             {
    9.                 if (inventorySlotComponent._owner == playerEntity)
    10.                 {
    11.                     selectedItemEntity = inventorySlotComponent._slotContentEntity;
    12.                 }
    13.             });
    14.  
    15.             return selectedItemEntity;
    16.         }
    Being forced to duplicate all those usage is really anoying, and I am quite sure that despite of the quantity of reseach I made, there should be a way to do it.

    Thanks for your help
     
  2. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Extensions method does not seem to work either, the SystemBase does not expose Entities and the ForEachLambdaJobDescription riquire to end with a run/schedule/scheduleparallel invocaion.

    I guess you best bet for now it to convert the usitlit to a IJob or IJobChunk.
     
  3. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    Not as brightest idea and not sure if it's possible, but what about receiving the reference of the system you are calling this method from, an then use that system instance to call GetEntityQuery so you can execute the EntityQuery that you create inside this method?
     
  4. hadesfury

    hadesfury

    Joined:
    Jan 23, 2014
    Posts:
    55
    Does not seems to do the trick ..
    GetEntityQuery() is protected
     
  5. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    Just came across the thread ( started back in mid May ) where this problem has been discussed, as well as planned approach to solve situations similar to this.
     
  6. hadesfury

    hadesfury

    Joined:
    Jan 23, 2014
    Posts:
    55
    Thank you .. It is not as "pure" as a static function but It seems a decent way to reuse
     
  7. joepl

    joepl

    Unity Technologies

    Joined:
    Jul 6, 2017
    Posts:
    87
    hadesfury and WAYNGames like this.
  8. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    It's actually more then just a reuse issue. The core issue is one system per type per world and then tightly coupling everything to that. IJobEntity solves some problems locally and that's good. It doesn't solve the bigger picture that you have a lot of common use cases that just don't abstract well into systems.

    An approach that mostly solves all is to turn systems on their head. The ECS system encapsulates usage of Entities/Job/Dependency but nothing else really. Other then it still has to proxy OnCreate/OnDestroy/OnUpdate to your logical systems. But now you can easily do things like have multiple instances of logical systems.

    I still haven't abstracted this out as cleanly as it could be, like a generic MySystemClass<EcsSystem> which would automatically handle proxying calls to MySystemClass. I'm just creating a method per job on the ECS system that takes a job struct and then runs it using Entities/Job. With my logical system getting a reference to the ECS system.

    One advantage of this pattern over some others is that it mostly lives within the current design. It's unlikely to break and doesn't go against the grain in a way that would cause issues that I can see.
     
  9. aveakrause

    aveakrause

    Joined:
    Oct 3, 2018
    Posts:
    70
    Entities.ForEach also can't exist in a generic class. Which is kinda annoying. Means I have to make a generic base class that makes a call to an abstract method with some params, that get's implemented in a child class that isn't generic where I do the Entities.ForEach.

    It's pretty easy to see how this could be a lot better if Entities.ForEach was allowed to be used in a generic space.

    Here's a generic timer.

    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. public interface IDuration : IComponentData
    4. {
    5.     float secondsRemaining {get; set;}
    6. }
    Code (CSharp):
    1.  
    2. using Unity.Burst;
    3. using Unity.Entities;
    4.  
    5. /// Unity does not allow the use of Entities.ForEach() with generic types, so we need to trick Unity
    6. /// We do the ForEach syntax in the child class where we are not in generic space,
    7. /// but since all of our logic is generic safe, all we need to do is lambda the TickSingleDuration method on the base class inside the ForEach,
    8. /// and pass it the required information from the ForEach syntax.
    9. /// See example use below
    10. public abstract class DurationSystem : SystemBase
    11. {
    12.     protected BeginInitializationEntityCommandBufferSystem entityCommandBufferSystem;
    13.  
    14.     protected override void OnCreate() =>
    15.         entityCommandBufferSystem = World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
    16.  
    17.     protected override void OnUpdate() =>
    18.         TickAllDurations(entityCommandBufferSystem.CreateCommandBuffer(), UnityEngine.Time.deltaTime);
    19.  
    20.     protected abstract void TickAllDurations(EntityCommandBuffer ecb, float deltaTime);
    21.  
    22.     [BurstCompile] protected static DurationStatus TickSingleDuration<T>(
    23.         Entity entity, ref T cooldown, EntityCommandBuffer ecb, float deltaTime) where T : struct, IDuration
    24.     {
    25.         cooldown.secondsRemaining -= deltaTime;
    26.         if(cooldown.secondsRemaining <= 0)
    27.         {
    28.             ecb.RemoveComponent<T>(entity);
    29.             return DurationStatus.Ended;
    30.         }
    31.         return DurationStatus.Ticking;
    32.     }
    33. }
    34.  
    35. public enum DurationStatus
    36. {
    37.     Ticking,
    38.     Ended
    39. }
    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. [System.Serializable]
    4. public struct AbilityCooldown : IDuration
    5. {
    6.     public float secondsRemaining {get; set;}
    7. }
    8.  
    9. public class AbilityCooldownJobSystem : DurationSystem
    10. {
    11.     protected override void TickAllDurations(EntityCommandBuffer ecb, float deltaTime) =>
    12.         Entities.ForEach((Entity entity, ref AbilityCooldown cooldown) =>
    13.             TickSingleDuration(entity, ref cooldown, ecb, deltaTime)
    14.         ).Run();
    15. }
     
    Last edited: Jun 2, 2020
  10. joepl

    joepl

    Unity Technologies

    Joined:
    Jul 6, 2017
    Posts:
    87
    Entities.ForEach does have the limitation that it can't be used with generic types or in generic systems. This is unfortunately due to us having to know/generate all the information related to types at compile time for every scheduling invocation (and this being pretty complicated with IL post-processing). This is unlikely to change soon for Entities.ForEach, but we are looking into changing this with IJobEntity such that generics are possible.
     
    KwahuNashoba and brunocoimbra like this.
  11. hadesfury

    hadesfury

    Joined:
    Jan 23, 2014
    Posts:
    55
    I finally used this solution for now

    Code (CSharp):
    1.         public static Entity GetSelectedItem(EntityManager entityManager, Entity playerEntity)
    2.         {
    3.             Entity selectedItemEntity = Entity.Null;
    4.             EntityQuery entityQuery =  entityManager.CreateEntityQuery(ComponentType.ReadOnly<InventorySlotComponent>());
    5.             NativeArray<InventorySlotComponent> inventorySlotComponents = entityQuery.ToComponentDataArray<InventorySlotComponent>(Allocator.TempJob);
    6.  
    7.             for (int entityIndex = 0; entityIndex < inventorySlotComponents.Length; entityIndex++)
    8.             {
    9.                 InventorySlotComponent inventorySlotComponent = inventorySlotComponents[entityIndex];
    10.  
    11.                 if (inventorySlotComponent._owner == playerEntity)
    12.                 {
    13.                     selectedItemEntity = inventorySlotComponent._slotContentEntity;
    14.                 }
    15.             }
    16.  
    17.             inventorySlotComponents.Dispose();
    18.  
    19.             return selectedItemEntity;
    20.         }