Search Unity

  1. Click here to see what's on sale for the "Best of Super Sale" on the Asset Store
    Dismiss Notice
  2. We are looking for feedback on the naming of a new user research platform that we are working on.
    Dismiss Notice
  3. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

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

Discussion in 'Data Oriented Technology Stack' started by exiguous, Sep 8, 2020.

  1. exiguous


    Nov 21, 2010
    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):
    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
    10.             // Check to make sure the target Entity still exists and has the needed component source:
    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;
    16.             translation.Value = cOMPosition + math.mul( rotation, orbiterpos );
    17.         }).ScheduleParallel();
    18.     }
    19. }
    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


    Sep 2, 2015
    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


    Nov 21, 2010
    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?

  4. brunocoimbra


    Sep 2, 2015
    See that link:

    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);
    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;
    11.                     Translation cOMTransform = GetComponent<Translation>(orbit.centerOfMassEntity);
    12.                     float3 cOMPosition = cOMTransform.Value;
    13.                     positions[entityInQueryIndex] = cOMPosition + math.mul(rotation, orbiterpos);
    14.                 })
    15.                .ScheduleParallel();
    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;
    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
    exiguous likes this.
  5. brunocoimbra


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


    Nov 21, 2010
    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.