Search Unity

Change component data in job

Discussion in 'Entity Component System' started by smarrog, May 11, 2020.

  1. smarrog

    smarrog

    Joined:
    Jan 18, 2016
    Posts:
    9
    Hello,
    How can I change translation of one entity according to translation of another one in parallel.
    Is seems to me that the problem now is that I mark ComponentDataFromEntity with Translations as readonly and after that it is changed in last line of job. Am I right ?

    Thank you.

    Code (CSharp):
    1. public class MoveToTargetSystem : SystemBase {
    2.     protected override void OnUpdate() {
    3.         var entityTranslations = GetComponentDataFromEntity<Translation>();
    4.  
    5.         Entities
    6.             .WithReadOnly(entityTranslations)
    7.             .ForEach((Entity entity, int entityInQueryIndex, ref Translation translation, in Target target, in MoveData moveData) => {
    8.                 var targetTranslation = entityTranslations[target.Entity];
    9.                 var distance = math.distance(targetTranslation.Value, translation.Value);
    10.                 if (distance <= 1f) {
    11.                     return;
    12.                 }
    13.  
    14.                 var direction = math.normalize(targetTranslation.Value - translation.Value);
    15.                 translation.Value += direction * moveData.MoveSpeed * Time.DeltaTime; // this place
    16.             }).ScheduleParallel();
    17.     }
    18. }

    error DC0002: Entities.ForEach Lambda expression invokes 'get_Time' on a ComponentSystemBase which is a reference type. This is only allowed with .WithoutBurst() and .Run().
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Above the Entities.ForeEach, add
    Code (CSharp):
    1. float dt = Time.DeltaTime;
    Then replace Time.DeltaTime with dt inside your Entities.ForEach.

    The reason you need to do this is because Time is a property, not a field, so for the lambda to capture it, it has to capture the class which is not supported in Burst.
     
    longsl, MentalGames and smarrog like this.
  3. smarrog

    smarrog

    Joined:
    Jan 18, 2016
    Posts:
    9
    Thank you it helps to compile, but in runtime I got this error:

    InvalidOperationException: The writable NativeArray <>c__DisplayClass_OnUpdate_LambdaJob0.JobData._lambdaParameterValueProviders.forParameter_translation._type is the same NativeArray as <>c__DisplayClass_OnUpdate_LambdaJob0.JobData.entityTranslations, two NativeArrays may not be the same (aliasing).
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    You can't use ComponentDataFromEntity<Translation> and Translation as a ForEach argument at the same time.
     
    smarrog likes this.
  5. smarrog

    smarrog

    Joined:
    Jan 18, 2016
    Posts:
    9
    So now I am fully stuck, can you please give me a tip how I can change Translation and at the same time get readonly reference to target's Translation?
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    So at the cost of having to use Schedule instead of ScheduleParallel, you can remove the Translation argument from the ForEach and use SystemBase's GetComponent/SetComponent methods instead of ComponentDataFromEntity.

    Another option would be to create a temporary NativeArray containing each Entity's target Translation in one Entities.ForEach pass, and then writing the new translations in a second pass.
     
    smarrog likes this.
  7. smarrog

    smarrog

    Joined:
    Jan 18, 2016
    Posts:
    9
    Thank you for your help.
    I have implement both variants, they both works, but second one (with parallel) is twice slower than first one - is it ok or I have done smth wrong?

    Code (CSharp):
    1. public class MoveToTargetSystem : SystemBase {
    2.     protected override void OnUpdate() {
    3.         var deltaTime = Time.DeltaTime;
    4.  
    5.         Entities
    6.             .ForEach((Entity entity, int entityInQueryIndex, in Target target, in MoveData moveData) => {
    7.                 var translation = GetComponent<Translation>(entity);
    8.                 var targetTranslation = GetComponent<Translation>(target.Entity);
    9.                 var distance = math.distance(targetTranslation.Value, translation.Value);
    10.                 if (distance <= 1f) {
    11.                     return;
    12.                 }
    13.  
    14.                 var direction = math.normalize(targetTranslation.Value - translation.Value);
    15.                 translation.Value += direction * moveData.MoveSpeed * deltaTime;
    16.                 SetComponent(entity, translation);
    17.             }).Schedule();
    18.     }
    19. }
    Code (CSharp):
    1. public class MoveToTargetSystem : SystemBase {
    2.     private EntityQuery _query;
    3.    
    4.     protected override void OnCreate() {
    5.         _query = GetEntityQuery(
    6.             ComponentType.ReadOnly<Target>(),
    7.             ComponentType.ReadOnly<MoveData>());
    8.     }
    9.  
    10.     protected override void OnUpdate() {
    11.         var deltaTime = Time.DeltaTime;
    12.         var entityTranslations = GetComponentDataFromEntity<Translation>();
    13.         var targetTranslations = new NativeArray<Translation>(_query.CalculateEntityCount(), Allocator.TempJob);
    14.  
    15.         Entities
    16.             .WithReadOnly(entityTranslations)
    17.             .ForEach((Entity entity, int entityInQueryIndex, in Target target, in MoveData moveData) => {
    18.                 targetTranslations[entityInQueryIndex] = entityTranslations[target.Entity];
    19.             }).ScheduleParallel();
    20.  
    21.         Entities
    22.             .WithReadOnly(targetTranslations)
    23.             .WithDeallocateOnJobCompletion(targetTranslations)
    24.             .ForEach((Entity entity, int entityInQueryIndex, ref Translation translation, in Target target, in MoveData moveData) => {
    25.                 var targetTranslation = targetTranslations[entityInQueryIndex];
    26.                 var distance = math.distance(targetTranslation.Value, translation.Value);
    27.                 if (distance <= 1f) {
    28.                     return;
    29.                 }
    30.  
    31.                 var direction = math.normalize(targetTranslation.Value - translation.Value);
    32.                 translation.Value += direction * moveData.MoveSpeed * deltaTime;
    33.             }).ScheduleParallel();
    34.     }
    35. }
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Nothing wrong with code, other than some potential optimizations (which may or may not matter to Burst).
    The first approach you don't need int entityInQueryIndex in the arguments, and the second you could use WithStoreEntityQueryInField instead of manually recreating the EntityQuery. Also in the second approach you should remove the in MoveData moveData and replace it with WithAll<MoveData>() in the first ForEach. Same goes for Target in the second ForEach.

    The other thing I would check is to make sure Safety Checks and Jobs Debugger are disabled when profiling as those can heavily impact results.
     
    smarrog likes this.
  9. smarrog

    smarrog

    Joined:
    Jan 18, 2016
    Posts:
    9
    Then how I can get index of my native array ?
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    You don't have an array in that approach.