Search Unity

Entities.ForEach cannot use component access method HasComponent with the same type

Discussion in 'Entity Component System' started by exiguous, Sep 8, 2020.

  1. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    Hey,
    I want to calculate elliptical orbits for my celestials around an arbitrary center of mass. So imagine a binary system where both stars orbit around a center of mass entity (which has just a position and a tag). Also some planets orbit around the same center. One planet orbits around one of the stars (which is also a center of mass then). And a moon orbits around that planet. So I just want to specify for a given entity which other entity it orbits around. I do this by storing the entity in the OrbitParameterData : IComponentData.

    However, I have problems retriving the Translation component of the center of mass entity with GetComponent as I get the following error:
    I understand the error that since I access the translation component to set the new position of my current entity I cannot (somehow) access the translation component of another entity. The reason for this is beyond my understanding.
    Here is the offending code:
    Code (csharp):
    1.  
    2. public class OrbitSystem : SystemBase
    3. {
    4.     protected override void OnUpdate()
    5.     {
    6.        Entities.ForEach((ref Translation translation, ref OrbitParameterData orbit) =>
    7.         {
    8.             // the orbit calculation happens here, it gives me a float3 which should be added to the center of mass's position and written to this entity's position
    9.  
    10.             // Check to make sure the target Entity still exists and has the needed component source: https://docs.unity3d.com/Packages/com.unity.entities@0.14/manual/ecs_lookup_data.html
    11.             if (!HasComponent<Translation>(orbit.centerOfMassEntity))   // <- this is complained about
    12.                 return;
    13.             Translation cOMTransform = GetComponent<Translation>(orbit.centerOfMassEntity);    // this too when I remove HasComponent above
    14.             float3 cOMPosition = cOMTransform.Value;
    15.  
    16.             translation.Value = cOMPosition + math.mul( rotation, orbiterpos );
    17.         }).ScheduleParallel();
    18.     }
    19. }
    20.  
    I have also tried ComponentDataFromEntity<Translation> allTranslations = GetComponentDataFromEntity<Translation>(true); outside of the foreach but it suffers from the same problem. And I have searched the error text but got no information which help me resolve this.

    So my questions are:
    How can I refer to another entities translation component in my system which shall set the translation of each entity?
    Am I thinking "the wrong (OOP)" way?
    Is there a better approach obvious?
    Is this restriction "justified"? I don't see what could go wrong. The error remains when I do Schedule instead of ScheduleParallel so even if one thread writes to the data is forbidden? Can you give me an example why this is forbidden?
    Since I only need read access of the center of mass's entity translation component can I somehow convince Unity to give me at least that?

    So I'm pretty stumped and hope someone has insights for me. Anyway, many thanks in advance.
     
  2. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    You can't use ComponentDataFromEntity with a component being passed in the in Query (Get/Set/HasComponent just generates CDFE code for you when using Schedule/ScheduleParallel).

    Here is the fixed code (may have typos):
    Code (CSharp):
    1. public class OrbitSystem : SystemBase
    2. {
    3.     protected override void OnUpdate()
    4.     {
    5.        Entities.ForEach((Entity entity, ref OrbitParameterData orbit) =>
    6.         {
    7.             if (!HasComponent<Translation>(orbit.centerOfMassEntity))
    8.                 return;
    9.             Translation cOMTransform = GetComponent<Translation>(orbit.centerOfMassEntity);
    10.             float3 cOMPosition = cOMTransform.Value;
    11.             SetComponent<Translation>(entity, new Translation() { Value = cOMPosition + math.mul( rotation, orbiterpos ) });
    12.         }).Schedule();
    13.     }
    14. }
     
    exiguous likes this.
  3. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    Thanks Bruno.
    That was fast. And more importantly, it works. But when I use ScheduleParallel instead of schedule it does not work anymore. Since most entities in the game will be orbiters I would really like this system to run multithreaded.

    With ScheduleParallel the "test Entity" does not move anymore and I get this error consecutively in playmode:
    Which is the same error I got when using ComponentDataFromEntity<Translation> allTranslations = GetComponentDataFromEntity<Translation>(true); method outside of ForEach. Maybe I should mention that I'm a total noob in ECS and am just starting with it. And I'm on Linux Editor 2020.1 with latest packages (Entities 0.14.0 - preview 18).

    So how can I declare the translation (of the referenced Entity) readonly to satisfy the compiler?
    What does CDFE mean?
    Does the "return" only cancel the current iteration (like a continue in a for loop)? So are other Entities still processed when one is missing?
    Where can I learn such "restrictions" from? Documentation is sparse. Is reacting to errors and warnings the "way to go" for ECS still?

    Thanks
     
  4. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    See that link: https://docs.unity3d.com/Packages/c.../ecs_entities_foreach.html#supported-features

    CDFE is ComponentDataFromEntity (Has/Set/GetComponent uses it under the hood inside Entities.ForEach.Schedule/ScheduleParallel) and ScheduleParallel can't write to it (SetComponent, in your case).

    What you can do is split the job into 2, this way you could do something like that:

    Code (CSharp):
    1.             NativeArray<float3> positions = new NativeArray<float3>(_entityQuery.CalculateEntityCount(), Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    2.  
    3.             Entities
    4.                .WithNativeDisableParallelForRestriction(positions)
    5.                .WithStoreEntityQueryInField(ref _entityQuery)
    6.                .ForEach((int entityInQueryIndex, ref OrbitParameterData orbit) =>
    7.                 {
    8.                     if (!HasComponent<Translation>(orbit.centerOfMassEntity))
    9.                         return;
    10.  
    11.                     Translation cOMTransform = GetComponent<Translation>(orbit.centerOfMassEntity);
    12.                     float3 cOMPosition = cOMTransform.Value;
    13.                     positions[entityInQueryIndex] = cOMPosition + math.mul(rotation, orbiterpos);
    14.                 })
    15.                .ScheduleParallel();
    16.  
    17.             Entities
    18.                .WithReadOnly(positions)
    19.                .WithDeallocateOnJobCompletion(positions)
    20.                .ForEach((int entityInQueryIndex, ref Translation translation, in OrbitParameterData orbit) =>
    21.                 {
    22.                     if (!HasComponent<Translation>(orbit.centerOfMassEntity))
    23.                         return;
    24.  
    25.                     translation.Value = positions[entityInQueryIndex];
    26.                 })
    27.                .ScheduleParallel();
    EDIT: untested, may have flaws there, but I think that it should be a good starting point
     
  5. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    About that, yes, you can see "return" being like "continue" in a for loop.
     
    exiguous likes this.
  6. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    Thanks alot Bruno. I had to "wrestle" the example code a bit. But that was very helpful and insightful. Now I understand the syntax and workings of ECS a bit better. And my system is working properly and multithreaded too. Now I can start to procedurally generate some solar systems which will probably spawn more questions.
    Thanks again.
     
    brunocoimbra likes this.
  7. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    Ugh.... that's so ugly... but it works. I wish there was a cleaner way to solve the problem.