Search Unity

How to access random entity's component inside ForEach

Discussion in 'Entity Component System' started by Zolden, Apr 26, 2020.

  1. Zolden

    Zolden

    Joined:
    May 9, 2014
    Posts:
    141
    I'd like to implement some communication between entity components. So, inside ForEach loop I'd like current entity to look up data from random entities by entity index. What would be the most natural way to do this?
     
  2. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    GetComponent<>()
     
  3. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    987
    You use ComponentDataFromEntity. It's like a Dictionary<Entity, ComponentType>.
     
  4. joepl

    joepl

    Unity Technologies

    Joined:
    Jul 6, 2017
    Posts:
    87
    In SystemBase, the following method is the preferred way:
    protected internal T GetComponent<T>(Entity entity) where T : struct, IComponentData

    This will call through EntityManager if called outside of an Entities.ForEach and will create a ComponentDataFromEntity for you (and access through that) if called in an Entities.ForEach.

    https://docs.unity3d.com/Packages/c...emBase_GetComponent__1_Unity_Entities_Entity_
     
  5. LudiKha

    LudiKha

    Joined:
    Feb 15, 2014
    Posts:
    140
    If you want to do it in a job:

    Code (CSharp):
    1. var componentDataFromEntity = EntityManager.GetComponentDataFromEntity<IComponentData>();
    2.  
    3. // Run job
    4. Entity otherEntity = ... //get the other entity
    5. var componentData = componentDataFromEntity [otherEntity];
    6. //Write if changed
    7. componentDataFromEntity[otherEntity] = componentData;
    8.  
     
  6. Zolden

    Zolden

    Joined:
    May 9, 2014
    Posts:
    141
    Ragoo

    Thank you, this was what I've tried before creating this thread. This caused some errors, even though I googled what might have caused those errors, but it didn't help. So I though there might be better way, that at least works.


    Thank you, this finally worked for me.

    Though, I have a couple of questions.

    GetComponent<>() method requires Entity parameter to access a component. But in my task entities interact through indexes, so I have to get entity by index. Don't mind the simplified code, I'd like to know if the principle in correct. Would this method of getting entity by index is correct. Or there might be a better way?

    Code (CSharp):
    1. protected override void OnUpdate() {
    2.     NativeArray<Entity> entities = EntityManager.GetAllEntities(Unity.Collections.Allocator.Persistent);
    3.     Entities.ForEach(
    4.         (Entity e, int entityInQueryIndex, ref component1 c1) => {
    5.             c1.someField = GetComponent<component2>(entities[c1.intField]).someField;
    6.         }
    7.     ).ScheduleParallel();
    8.     entities.Dispose();
    9. }
    Can it be that creating NativeArray<Entity> entities array is abundant measure, and ForEach() actually somehow has an inner list of entities, that I could access by index?

    And another question is this. How slow is GetComponent<>() operation? Won't it ruin the whole performance benefits of using ECS? Won't it block Burst compiler from doing its job?
     
  7. joepl

    joepl

    Unity Technologies

    Joined:
    Jul 6, 2017
    Posts:
    87
    I don't think you would want to use GetAllEntities and then access it by index. This would only work if your indices remained static if I understand correctly. Any change to the order or number of entities in your world could break that.

    Is there any reason you can't just store the Entity itself in component1? That should be much safer and is how this API is typically used. An Entity itself is pretty much just a safe index/id that has some extra versioning information.
     
  8. Zolden

    Zolden

    Joined:
    May 9, 2014
    Posts:
    141
    joepl

    In this particular case each entity's component has to communicate with randomly chosen entities's components. So, while in ForEach, I get a random integer, and then get the entity from the array. I understand it's unsafe in some cases, but in my current task the number of entities of the archetype is constant. I just want to build a working prototype and see what problems will I meet along the way. So, I might use this array lookup method. Though, I remember I've read in docs that queries have an inner array of entities. Is it correct for ForEach loop? Can I use it instead of the explicitly created one?

    Btw, there's another question about how entities should ideally interact. I plan to simulate interacting particles, so I'll use another method of interaction between entities. Each particle will have to be related to a cell of 3d grid, based on its coordinates, and then each particle will look up only the particles from its grid and the adjacent ones. What would be the preferred way to do this within current or later version of ECS?

    And another question if you please. Can we perform atomic operations? I'd like all parallel jobs to increment one number. Is it possible?
     
  9. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Use
    .WithStoreEntityQueryInField(ref _query)
    and call
    var entitiesArr = _query.ToEntityArray(Allocator)
    (ToEntityArrayAsync if you using not main thread Run) outside of ForEach and just use that entitiesArr inside ForEach (don't forget WithReadOnly and don't forget Dispose that array after ForEach - also if Run - just Dispose if you scheduling ForEach thus use Dependency as argument for Dispose)
     
    Zolden likes this.
  10. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    Will it work with the buffer or is there something like that for the buffers?
     
  11. joepl

    joepl

    Unity Technologies

    Joined:
    Jul 6, 2017
    Posts:
    87
    @Zolden

    > Though, I remember I've read in docs that queries have an inner array of entities. Is it correct for ForEach loop? Can I use it instead of the explicitly created one?

    Queries hold onto a list of their matching archetypes and the chunks for those archetypes, so the answer is essentially yes. You can use EntityQuery.CalculateEntityCount and EntityQuery.ToEntityArray/ToEntityArrayAsync to get the count of and arrays of, matching entities.

    > Each particle will have to be related to a cell of 3d grid, based on its coordinates, and then each particle will look up only the particles from its grid and the adjacent ones. What would be the preferred way to do this within current or later version of ECS?

    I'd take a look at Boids in the samples. There should be an example of something similar to this (with a spatial data structure).

    > Can we perform atomic operations? I'd like all parallel jobs to increment one number. Is it possible?

    I don't believe so currently, though I think some work is being done to provide a mechanism for this.

    @Krajca

    > Will it work with the buffer or is there something like that for the buffers?

    Not yet but we should be adding similar support for SystemBase.GetBuffer (and patch it in Entities.ForEach).
     
    Zolden and Krajca like this.
  12. Zolden

    Zolden

    Joined:
    May 9, 2014
    Posts:
    141
    @joepl

    I have a question. Why does this code work so slow?
    There are one million entities. And it works with 2 fps.

    Let me explain what it supposed to do. I'm trying to simulate an epidemic in agent based manner. So each entity is a person, and in every update each person interacts with a few random persons.

    It's only partially done, but the main part already works too slow. Why is that?

    Code (CSharp):
    1. public class personSystem : SystemBase {
    2.  
    3.     [NativeDisableContainerSafetyRestriction]
    4.     NativeArray<Unity.Mathematics.Random> randomNumbers;
    5.  
    6.     NativeArray<Entity> entities;
    7.  
    8.     protected override void OnCreate() {
    9.         base.OnCreate();
    10.         randomNumbers = new NativeArray<Unity.Mathematics.Random>(JobsUtility.MaxJobThreadCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
    11.         for (int i = 0; i < JobsUtility.MaxJobThreadCount; i++)
    12.             randomNumbers[i] = new Unity.Mathematics.Random((uint)UnityEngine.Random.Range(int.MinValue, int.MaxValue));
    13.     }
    14.  
    15.     protected override void OnStartRunning() {
    16.         base.OnStartRunning();
    17.         entities = EntityManager.GetAllEntities(Unity.Collections.Allocator.Persistent);
    18.     }
    19.  
    20.     protected override void OnUpdate() {
    21.         var rna = randomNumbers;
    22.         var ea = entities;
    23.         Entities.ForEach(
    24.             (Entity e, int entityInQueryIndex, int nativeThreadIndex, ref catchedInfection ci) => {
    25.                 for (int i = 0; i < 10; i++) {
    26.                     var rn = rna[nativeThreadIndex];
    27.                     int catchFrom = rn.NextInt(1, 1000000);
    28.                     rna[nativeThreadIndex] = rn;
    29.                     //catchFrom = 8;
    30.                     personComponent pc = GetComponent<personComponent>(ea[catchFrom]);
    31.                     if (
    32.                         pc.stage == (int)stages.incubation && pc.daysBeforeStageChange <= 2 ||
    33.                         pc.stage == (int)stages.ill && (pc.maxDaysCurrentStage - pc.daysBeforeStageChange) <= 2)
    34.                     {
    35.                         ci.catched = true;
    36.                     }
    37.                 }
    38.             }
    39.         ).ScheduleParallel();
    40.     }
    41.  
    42.     protected override void OnDestroy() {
    43.         base.OnDestroy();
    44.         entities.Dispose();
    45.         randomNumbers.Dispose();
    46.     }
    47. }
    Fun fact: if I uncomment this line:

    catchFrom = 8;


    fps will jump to 40. Just by using a constant index instead of random one, I increase performance by factor of 20. What is this supposed to mean?
     
  13. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    You are random-access memory bound. By making the index constant, the CPU only loads that constant index value rather than all possible values it would have been loading otherwise.

    Out of curiosity, how much better or worse is using Schedule instead of ScheduleParallel?
     
  14. Zolden

    Zolden

    Joined:
    May 9, 2014
    Posts:
    141
    Interesting! And looks like it's not only ECS related feature. I tried to implement exactly the same task with pure Mono. And the result is: 0.6 fps for random indexes and 3.2 for constant. Though, looks like ECS finds better use of constant index, it has shown 20x faster work with constant instead of about 5x for mono.

    ScheduleParallel is supposed to be about a number of CPU cores times better than Schedule. But only in case it's not bottlenecked by main thread activity. In this particular case main thread has much more stuff to do than the cores. Probably because of memcopies. So, when I use Schedule it's 0.7 fps for random index and 12 for constant. Instead of 2/40 for ScheduleParallel.

    Anyway. Time to implement this task for the third time. With the glorious compute shader. Let's see how hard will it kick ECS's ass.
     
  15. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Main reason I asked was I was suspicious of a false-sharing issue exaggerating the issue. That doesn't seem to be the case with those numbers.
     
  16. joepl

    joepl

    Unity Technologies

    Joined:
    Jul 6, 2017
    Posts:
    87
    @Zolden DreamingImLatios is right. That code will effectively jump around when loading other components from entities in your use of GetComponent. ECS (and DOTS specifically) is particularly good at running through linear sets of the same components, as soon as you start accessing components on other Entities randomly, you will be in cache-miss city and your performance will suffer accordingly.

    Some things that could help:
    - Pre-sort your entities somehow and access them in that manner. Maybe use a shared component or spatial data structure to run through entities that are considered "nearby" and able to infect (and then change these every few frames to simulate people moving around). The Boids sample is once again is probably a good place to look for something similar.
    - Pack your data tighter in your components. The smaller your data, the more should fit into the cache and prevent misses.

    You'll likely have to find some clever way of pre-arranging your PersonComponents so that when your run through your Entities.ForEach you are not doing random indexing there. Otherwise you will be preventing the CPU from pre-fetching your data and helping you out.
     
    Zolden likes this.
  17. Zolden

    Zolden

    Joined:
    May 9, 2014
    Posts:
    141
    @joepl

    Thank you for explanations, I'm now closer to understanding how to use DOTS properly. Even though it has shown rather moderate performance advantage over mono for this particular implementation of my task, it still might be a good approach for tasks that can be easily morphed into linear sets of data.

    Oh, and to compare performance, I just implemented the same pandemics task with compute shader. Same random lookup has been used. And it worked with 330 fps. Which speaks for itself.
     
  18. joepl

    joepl

    Unity Technologies

    Joined:
    Jul 6, 2017
    Posts:
    87
    Actually re-examining your loop, there is a fair amount you could do:
    - Hoist most of this code out of your inner for loop. You may have to pre-fill an array of random numbers but most of that code doesn't depend on your inner loop.

    [LIST=1]
    [*]var rn = rna[nativeThreadIndex];
    [*] int catchFrom = rn.NextInt(1, 1000000);
    [*] rna[nativeThreadIndex] = rn;
    [/LIST]

    - Turn off safety checks. Both your containers and the random methods have safety checks and this will eat up quite a bit of time.
    - Early out once your entity has caught the infection. Even better, use a tag component to indicate that the entity has caught the disease and an EntityCommandBuffer to add that component (and also early out). That way you won't be running through this loop for entities that have already caught the tag component. You'll want your Entities.ForEach to use WithNone(MyCaughtComponent) so it doesn't run through the onces that already have that component.
    - Run in a player build with release codegen (or at least in editor in release mode in 2020.1). Running in Editor in 2019.3 will be using Mono/debug, which isn't the fastest option.

    Using a data structure more suited for algorithm will help the most (ideally some sort of spatial partitioning one if that makes sense), but these things are all things to try. Also, glad to hear the compute shader solution is already working well!
     
    Zolden likes this.
  19. Jyskal

    Jyskal

    Joined:
    Dec 4, 2014
    Posts:
    28
    Here is an idea on spatial partitioning which makes a lot of sense in your use-case:

     
    joepl likes this.