Search Unity

Question Get wanted components from entity

Discussion in 'Entity Component System' started by mandicLuka, Nov 13, 2020.

  1. mandicLuka

    mandicLuka

    Joined:
    Aug 20, 2018
    Posts:
    13
    Hi there,

    is there a nicer (and possibly faster) way of getting and modifying multiple components from the entity rather than calling
    Code (CSharp):
    1.  var c1 = EntityManager.GetComponent<C1>(entity)
    2. // do stuff
    3. EntityManager.SetComponent<C1>(entity, v1)
    for each component?

    I am looking for something like:
    Code (CSharp):
    1. EntityManager.WithComponents(entity, (ref C1 c1, in C2 c2) =>
    2. {
    3.     // do something with entity components
    4.  
    5. });
    My use case is the following. I am writing a plugin system for my game. I want the end user to write his own behaviours (actions) for the game. The custom behaviour is composed of wanted parameters and the methods to check some preconditions for each parameter it needs. Here the idea is, for example, that user decides which components parameter entities must have for action to be instantiated.

    I will make sure that action is instantiated only with entities that satisfy preconditions, so I will make sure that entity has all the components that will be used in the action. For that, I think that a method like this would be useful.

    Thanks in advance. Cheers!
     
  2. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Isn't it the whole concept of entity query and Entities.Foreach ??
     
    Antypodish likes this.
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
    And other Jobs with ECS.
     
  4. mandicLuka

    mandicLuka

    Joined:
    Aug 20, 2018
    Posts:
    13
    Yes, but I already have an entity instance. I have no use in iterating over entities.
    One solution I came up with is to Add some kind of temp component to the entity and then use EntityManager.WithAll<TempComponent>() to get the wanted functionality. I find that in the spirit of ECS pattern, but it seems as too much work for such a functionality.
     
  5. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
    Maybe you just want a singleton.
    But it seems, you are going completely against ECS pattern in your approach.
     
  6. mandicLuka

    mandicLuka

    Joined:
    Aug 20, 2018
    Posts:
    13
    Yes, I am aware of that.

    But here is the scenario.

    Code (CSharp):
    1. public struct RefComponent : IComponentData
    2. {
    3.     [RequireComponent(typeof(C1))]
    4.     Entity refEntity
    5. }
    6.  
    7. public struct C1 : IComponentData { }
    8.  
    'refEntity' is the instance of a struct with a reference to some entity with component C1. I ensure that every reference to the entity 'refEntity' has component of type C1.

    Then, someware in code, I iterate over every entity that has RefComponent and want to do something with its reference to other entity:
    Code (CSharp):
    1. EntityManager em = ...;
    2.  
    3. Entities.ForEach((ref RefComponent r) =>
    4. {
    5.     em.WithComponents<C1>(r.refEntity, (in C1 c =>
    6.     {
    7.           // do something
    8.     }
    9. ))
    10. }

    WithComponents method is as described in the first question that would, in my opinion, come in handy in this situation.
     
  7. DK_A5B

    DK_A5B

    Joined:
    Jan 21, 2016
    Posts:
    110
    You could achieve this by doing something like...

    Code (CSharp):
    1. var entitiesToDeref = new NativeList<Entity> (Allocator.TempJob);
    2. var c1s = GetComponentDataFromEntity<C1> ();
    3. // collect all your ref components that you want to dereference
    4. Entities
    5.     .ForEach((ref RefComponent r) => {
    6.         entitiesToDeref.Add (r.refEntity);
    7.     }).Schedule ();
    8.  
    9. // dereference the collected components and perform your operation
    10. Job
    11.     .WithDisposeOnCompletion (entitiesToDeref)
    12.     .WithCode (() => {
    13.        
    14.         for (int k=0; k<entitiesToDeref.Length; k++) {
    15.             var c = c1s[entitiesToDeref[k]];
    16.            
    17.             // do something to c
    18.         }
    19.        
    20.     }).Schedule ();
    However, you're really fighting the framework here. By storing the reference on one Entity (with RefComponent) to another entity (with C1), you're stuck using random access. This is bad for performance because you're not taking advantage of data locality with the cache (which is where a lot of the performance gains come from with DOTS).

    Now, maybe your functionality requires having a reference and you can't avoid it, that happens. But I'd look at whether there's another way to layout your data that you can avoid having to do the dereferencing (i.e. avoid doing random access operations).
     
  8. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
  9. DK_A5B

    DK_A5B

    Joined:
    Jan 21, 2016
    Posts:
    110
    You could also take this approach to try and avoid the random access operation resulting from GetComponentDataFromEntity:

    Code (CSharp):
    1. var entitiesToDeref = new NativeHashSet<Entity> (Allocator.TempJob);
    2.  
    3. // collect all your ref components that you want to dereference
    4. Entities
    5.     .ForEach((in RefComponent r) => {
    6.         entitiesToDeref.Add (r.refEntity);
    7.     }).Schedule ();
    8.    
    9. // iterate over ALL entities with a C1 component
    10. Entities
    11.     .ForEach ((Entity entity, ref C1 c) => {
    12.         // if this entity is one being referred to by a RefComponent
    13.         if (entitiesToDeref.Contains (entity)) {
    14.            
    15.             // do something to c...
    16.         }
    17.        
    18.     }).Schedule ();
    But in this case, you're looping over ALL entities that have a C1 component, not just those that have been reference by a RefComponent. This may actually be faster, because you're using a linear access operation to get C1 components, so you're taking advantage of data locality with ECS, but it will depend on your data. If you have a lot of C1 components and very few RefComponents pointing to them, then the first approach will probably be faster. If most C1 components are pointed to by a RefComponent, then the second approach may be faster.
     
  10. mandicLuka

    mandicLuka

    Joined:
    Aug 20, 2018
    Posts:
    13
    In my scenario, the performance is not an issue, I have to do an operation on the ~10 entities on user input. I am aware that this is not in the spirit of ecs, but I am afraid that there is no other way. Other, performance heavy, operations that are already implemented fully exploit the benefits of DOD. What I have to achieve here is to give an end user stable API for plugin implementation and make sure that he will have to write code with as little boilerplate as possible.


    @DK_A5B your first answer is almost perfect. The only problem is that GetComponentDataFromEntity() can return data for only one component. What I need to do is get data for any number of components of referenced entity, just like in Entities.ForEach()
     
  11. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
  12. DK_A5B

    DK_A5B

    Joined:
    Jan 21, 2016
    Posts:
    110
    You can achieve this by adding GetComponentDataFromEntity() calls for each Component you want to access:

    Code (CSharp):
    1. var entitiesToDeref = new NativeList<Entity> (Allocator.TempJob);
    2. var c1s = GetComponentDataFromEntity<C1> ();
    3. var c2s = GetComponentDataFromEntity<C2> ();
    4. var c3s = GetComponentDataFromEntity<C3> ();
    5.  
    6. // collect all your ref components that you want to dereference
    7. Entities
    8.     .ForEach((ref RefComponent r) => {
    9.         entitiesToDeref.Add (r.refEntity);
    10.     }).Schedule ();
    11. // dereference the collected components and perform your operation
    12. Job
    13.     .WithDisposeOnCompletion (entitiesToDeref)
    14.     .WithCode (() => {
    15.      
    16.         for (int k=0; k<entitiesToDeref.Length; k++) {
    17.             var c1 = c1s[entitiesToDeref[k]];
    18.             var c2 = c2s[entitiesToDeref[k]];
    19.             var c3 = c3s[entitiesToDeref[k]];
    20.          
    21.             // do something with your components
    22.         }
    23.      
    24.     }).Schedule ();
    That said, I'd like to say that I agree with Antypodish that you're fighting the ECS pattern here. I'm showing you one way to achieve what you say you need to be able to do. Only you know your application and what you need. However, I think there is probably a better way to do what you want without going against the framework. I'd encourage you to read through the link Antypodish sent and think about whether there's another way to do what you want.
     
  13. mandicLuka

    mandicLuka

    Joined:
    Aug 20, 2018
    Posts:
    13
    Thank you guys once more for your time.

    From what I have seen so far from unity samples and other people work on blogs and yt, it sometimes is the case that Entities.ForEach() is used when there is for sure only one entity with some component, e.g PlayerTag.

    I don't see how my proposed solution would be breaking the pattern in this case. My use case certainly breaks the pattern, but to get the power of that lambda in the ForEach() for one entity would be very nice.

    Also, not related, but I have seen some implementations of the event system in ecs where one system adds the component to the entity as a tag when event is triggered, and then another system handles the entity and deletes the tag component at the end. Would you say that this is a valid approach for an event system?

    What I want to do can be accomplished following this pattern. I would then just iterate over entities that have that tag component as well as the other wanted components.
     
  14. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Yes but be aware that adding removing component triggers an archetype change so it's not the most performant opération.
    Also you will have a one frame delay every time you add/remove component.
    There was a video talk a while ago now that explained this with a poison damage use case.
     
  15. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    I think I know what you are asking for (in the original post), and indeed it would be more efficient and less code. But right now it does not exist and you are best off using the Get/SetComponent methods on SystemBase which will save you some of that boilerplate. Perhaps you might even consider writing an extension method that lets you collect multiple components at once to save even more typing. It will be interesting to revisit this topic once C# source generators arrive.

    Try not to tag entities if the lifetime of the tag is a frame or less. The memory copies for changing archetypes can be pretty expensive. Of course if this is only on a small number of entities, it might not matter a whole lot.
     
    mandicLuka likes this.
  16. mandicLuka

    mandicLuka

    Joined:
    Aug 20, 2018
    Posts:
    13
    This was exactly what I've had in mind. Also, looking forward to C# source generators.
     
  17. DK_A5B

    DK_A5B

    Joined:
    Jan 21, 2016
    Posts:
    110
    To add to what DreamingInLatios was saying, another way to do this, without incurring the costs with changing Archetypes, is to have a Component that's always present on an Entity with a flag field, and then change that value to trigger the "event" using a Change Filter to pick it up.

    So, instead of doing something like this:

    Code (CSharp):
    1. // COMPONENT DEFINITION:
    2. // definition for event
    3. struct SimpleEvent : IComponentData {
    4. }
    5.  
    6.  
    7. // INSIDE YOUR SYSTEM:
    8. // raise "events" Job
    9. Entities
    10.     .WithStructuralChanges ()
    11.     .ForEach ((Entity entity, ref RefComponent r) => {
    12.         var raiseEvent = false;
    13.        
    14.         // do some calculations that may change raiseEvent
    15.        
    16.         if (raiseEvent) {
    17.             EntityManager.AddComponent<SimpleEvent> (r.refEntity);    // WARNING: this is a structural change, changing the Entity's Archetype
    18.         }
    19.        
    20.     }).Run ();
    21.  
    22. // handle "events" Job
    23. Entities
    24.     .WithStructuralChanges ()
    25.     .WithAll<SimpleEvent> ()
    26.     .ForEach ((Entity entity, in C1 in) => {
    27.         // remove the event component
    28.         EntityManager.RemoveComponent<SimpleEvent> (entity);        // WARNING: this is a structural change, changing the Entity's Archetype
    29.        
    30.         // do you operation on c1...
    31.        
    32.     }).Run ();

    You could do something like this:

    Code (CSharp):
    1. // COMPONENT DEFINITION:
    2. // definition for trigger component - this would be on all of your C1 Entities.
    3. struct Trigger : IComponentData {
    4.     public byte trigger;
    5. }
    6.  
    7. // INSIDE YOUR SYSTEM:
    8. // fire "triggers" Job
    9. Entities
    10.     .ForEach ((Entity entity, ref RefComponent r) => {
    11.         //
    12.         var fireTrigger = false;
    13.  
    14.        // do some calculations that may change fireTrigger
    15.      
    16.        if (fireTrigger) {
    17.            SetComponent (r.refEntity, new Trigger {
    18.                triggered = 1;
    19.            });
    20.        }
    21.  
    22.     }).Run ();
    23.  
    24. // handle "triggers" Job
    25. Entities
    26.     .WithChangeFilter<Trigger> ()
    27.     .ForEach ((Entity entity, in C1 c1, in Trigger t) {
    28.         // you still need to check the trigger value, because the Change Filter
    29.         // operates at the Chunk level, not the Entity level, so it will bring in
    30.         // Entities that didn't change, but were stored in a Chunk with another
    31.         // Entity that was changed.
    32.         if (trigger == 1) {
    33.             // reset your trigger
    34.             t.trigger = 0;
    35.            
    36.             // do your operation on c1...
    37.         }
    38.        
    39.     }).Run ();
    There are some nuances with Change Filters you need to be aware of though. They operate at the Chunk level, not the Entity level. This means a Component will be treated as changed even if it didn't change but it is stored in a Chunk that had another Component that changed. They also consider something to be "changed" if it could have changed (i.e. if it was possible to write to it), even if it didn't actually change the value.
     
    Egad_McDad and mandicLuka like this.