Search Unity

Synchronizing parent data with child entity

Discussion in 'Entity Component System' started by Guedez, Nov 3, 2020.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    A tile can be watered, and each 4 corners of said tile are each a different mesh (because of tiling logic, so adjacent tiles join seamlessly), since the tile gets watered, but the child entity that needs to update it's material to show the 'water level' (gets darker), there needs to be a sync on the parent tile and it's child entities.
    This is the current code, but it's exceedingly slow for some reason:

    Code (CSharp):
    1. [
    2. [UpdateInGroup(typeof(PlantGroupEndFrame))]
    3. public class WateredCropTileSystem : ComponentSystem {
    4.     private EntityQuery m_MainGroup;
    5.  
    6.     protected override void OnCreate() {
    7.         m_MainGroup = GetEntityQuery(ComponentType.ReadOnly<LinkedEntityGroup>(), ComponentType.ReadOnly<SoilNutriendDataWater>());
    8.     }
    9.     protected override void OnUpdate() {
    10.         Entities.With(m_MainGroup).ForEach((Entity e, DynamicBuffer<LinkedEntityGroup> crop, ref SoilNutriendDataWater t3) => {
    11.  
    12.             PostUpdateCommands.AddComponent<SoilNutriendDataWater>(e);
    13.             for (int i = 0; i < crop.Length; i++) {
    14.                 if (EntityManager.HasComponent<SoilNutriendDataWater>(crop[i].Value)) {
    15.                     PostUpdateCommands.SetComponent(crop[i].Value, t3);
    16.                 } else {
    17.                     PostUpdateCommands.AddComponent(crop[i].Value, t3);
    18.                 }
    19.             }
    20.         });
    21.     }
    22. }
    23.  
    Is there a faster, better way to code this? This gets slow with just 500 (2000 corners) entities
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    It looks like there is no burst?
    Try avoid adding and removing components with data, unless these are tags. You entities will need be moved between chunks.

    Other than that, not sure why is so slow. Show us profiler with job workers. P
     
  3. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    857
    a componentsystem is mainthread only, and by the looks of the phrase EndFrame your project is on an older version of entities?

    ComponentSystem and JobComponentSystem should be updated to using SystemBase as a start.
    Not quite sure whats going on as you appear to be adding SoilNutriendDataWater to a query that already contains the component(ref SoilNutriendDataWater t3)).
     
  4. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Deep Profiler: upload_2020-11-3_15-40-58.png
    I was going to post the profiler save, but that's 6gb file
    The WateredCropTileSystem runs entirely on the main thread it seems, the jobs is all UpdateTileModel (which is the one that figures out which mesh each 4 corners uses)
    upload_2020-11-3_15-48-35.png
    Code (CSharp):
    1. using System;
    2. using Unity.Burst;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Rendering;
    7. using UnityEngine;
    8. [UpdateBefore(typeof(RenderMeshSystemV2))]
    9. [UpdateInGroup(typeof(PlantGroupEndFrame))]
    10. public class UpdateTileModel : JobComponentSystem {
    11.     private static TileMesh Side1;
    12.     private static TileMesh Side2;
    13.     private static TileMesh Center;
    14.     private static TileMesh CornerIn;
    15.     private static TileMesh CornerOut;
    16.  
    17.     private static NonRenderRenderMesh NonRenderRenderMeshSide1;
    18.     private static NonRenderRenderMesh NonRenderRenderMeshSide2;
    19.     private static NonRenderRenderMesh NonRenderRenderMeshCenter;
    20.     private static NonRenderRenderMesh NonRenderRenderMeshCornerIn;
    21.     private static NonRenderRenderMesh NonRenderRenderMeshCornerOut;
    22.  
    23.     EntityQuery m_Group;
    24.     EntityQuery m_Group2;
    25.     EndSimulationEntityCommandBufferSystem entityCommandBufferSystem;
    26.     protected override void OnCreate() {
    27.         base.OnCreate();
    28.         entityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    29.         Mesh side1 = Resources.Load<Mesh>("Side1");
    30.         Mesh side2 = Resources.Load<Mesh>("Side2");
    31.         Mesh center = Resources.Load<Mesh>("Center");
    32.         Mesh cornerIn = Resources.Load<Mesh>("Inner_Corner");
    33.         Mesh cornerOut = Resources.Load<Mesh>("Outer_Corner");
    34.  
    35.         Side1 = new TileMesh() { Value = side1, InstanceID = 1 };
    36.         Side2 = new TileMesh() { Value = side2, InstanceID = 2 };
    37.         Center = new TileMesh() { Value = center, InstanceID = 3 };
    38.         CornerIn = new TileMesh() { Value = cornerIn, InstanceID = 4 };
    39.         CornerOut = new TileMesh() { Value = cornerOut, InstanceID = 5 };
    40.  
    41.         NonRenderRenderMeshSide1 = new NonRenderRenderMesh() { mesh = side1 };
    42.         NonRenderRenderMeshSide2 = new NonRenderRenderMesh() { mesh = side2 };
    43.         NonRenderRenderMeshCenter = new NonRenderRenderMesh() { mesh = center };
    44.         NonRenderRenderMeshCornerIn = new NonRenderRenderMesh() { mesh = cornerIn };
    45.         NonRenderRenderMeshCornerOut = new NonRenderRenderMesh() { mesh = cornerOut };
    46.  
    47.         m_Group = GetEntityQuery(ComponentType.ReadOnly<SubTileAdjacency>(), typeof(ToUpdateTileModel));
    48.         m_Group2 = GetEntityQuery(ComponentType.ReadOnly<TileAdjacency>(), ComponentType.ReadOnly<LinkedEntityGroup>(), typeof(ToUpdateTileModel));
    49.     }
    50.     [BurstCompile]
    51.     struct UpdateTileAdjacencyJob : IJobChunk {
    52.         [ReadOnly] public ComponentTypeHandle<TileAdjacency> TileAdjacency;
    53.         [ReadOnly] public BufferTypeHandle<LinkedEntityGroup> LinkedEntityGroup;
    54.         [ReadOnly] public ComponentDataFromEntity<SubTileAdjacency> SubTileAdjacency;
    55.         [ReadOnly] public EntityTypeHandle Entities;
    56.         public EntityCommandBuffer.ParallelWriter EntityCommandBuffer;
    57.  
    58.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    59.             var TileAdjacency = chunk.GetNativeArray(this.TileAdjacency);
    60.             var Entities = chunk.GetNativeArray(this.Entities);
    61.             var LinkedEntityGroup = chunk.GetBufferAccessor(this.LinkedEntityGroup);
    62.             for (var i = 0; i < chunk.Count; i++) {
    63.                 TileAdjacency adj = TileAdjacency[i];
    64.                 var Buffer = LinkedEntityGroup[i];
    65.                 Entity en1 = Buffer[0].Value;
    66.                 Entity en2 = Buffer[1].Value;
    67.                 Entity en3 = Buffer[2].Value;
    68.                 Entity en4 = Buffer[3].Value;
    69.                 SubTileAdjacency adj1 = SubTileAdjacency[en1];
    70.                 SubTileAdjacency adj2 = SubTileAdjacency[en2];
    71.                 SubTileAdjacency adj3 = SubTileAdjacency[en3];
    72.                 SubTileAdjacency adj4 = SubTileAdjacency[en4];
    73.  
    74.                 adj1.up = adj.down;
    75.                 adj1.diagonal = adj.DL;
    76.                 adj1.right = adj.left;
    77.  
    78.                 adj2.up = adj.right;
    79.                 adj2.diagonal = adj.DR;
    80.                 adj2.right = adj.down;
    81.  
    82.                 adj3.up = adj.up;
    83.                 adj3.diagonal = adj.UR;
    84.                 adj3.right = adj.right;
    85.  
    86.                 adj4.up = adj.left;
    87.                 adj4.diagonal = adj.UL;
    88.                 adj4.right = adj.up;
    89.  
    90.                 EntityCommandBuffer.SetComponent(chunkIndex, en1, adj1);
    91.                 EntityCommandBuffer.SetComponent(chunkIndex, en2, adj2);
    92.                 EntityCommandBuffer.SetComponent(chunkIndex, en3, adj3);
    93.                 EntityCommandBuffer.SetComponent(chunkIndex, en4, adj4);
    94.  
    95.                 EntityCommandBuffer.AddComponent<ToUpdateTileModel>(chunkIndex, en1);
    96.                 EntityCommandBuffer.AddComponent<ToUpdateTileModel>(chunkIndex, en2);
    97.                 EntityCommandBuffer.AddComponent<ToUpdateTileModel>(chunkIndex, en3);
    98.                 EntityCommandBuffer.AddComponent<ToUpdateTileModel>(chunkIndex, en4);
    99.  
    100.                 EntityCommandBuffer.RemoveComponent<ToUpdateTileModel>(chunkIndex, Entities[i]);
    101.             }
    102.         }
    103.     }
    104.  
    105.     //[BurstCompile] cannot use burst because TileMesh has a Mesh component
    106.     struct UpdateTileModelJob : IJobChunk {
    107.         [ReadOnly] public SharedComponentTypeHandle<TileMesh> TileMesh;
    108.         [ReadOnly] public ComponentTypeHandle<SubTileAdjacency> SubTileAdjacency;
    109.         [ReadOnly] public EntityTypeHandle Entities;
    110.         public EntityCommandBuffer.ParallelWriter EntityCommandBuffer;
    111.  
    112.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    113.             var SubTileAdjacency = chunk.GetNativeArray(this.SubTileAdjacency);
    114.             var Entities = chunk.GetNativeArray(this.Entities);
    115.             if (chunk.Has(TileMesh)) {
    116.                 for (var i = 0; i < chunk.Count; i++) {
    117.                     EntityCommandBuffer.SetSharedComponent(chunkIndex, Entities[i], MakeSharedData(SubTileAdjacency[i], i));
    118.                     EntityCommandBuffer.SetSharedComponent(chunkIndex, Entities[i], MakeSharedDataNonRender(SubTileAdjacency[i], i));
    119.                     EntityCommandBuffer.RemoveComponent<ToUpdateTileModel>(chunkIndex, Entities[i]);
    120.                 }
    121.             } else {
    122.                 for (var i = 0; i < chunk.Count; i++) {
    123.                     EntityCommandBuffer.AddSharedComponent(chunkIndex, Entities[i], MakeSharedData(SubTileAdjacency[i], i));
    124.                     EntityCommandBuffer.AddSharedComponent(chunkIndex, Entities[i], MakeSharedDataNonRender(SubTileAdjacency[i], i));
    125.                     EntityCommandBuffer.RemoveComponent<ToUpdateTileModel>(chunkIndex, Entities[i]);
    126.                 }
    127.             }
    128.         }
    129.  
    130.         private NonRenderRenderMesh MakeSharedData(SubTileAdjacency adj, int i) {
    131.             if (adj.up != default && adj.right != default) {
    132.                 if (adj.diagonal != default) {
    133.                     return NonRenderRenderMeshCenter;
    134.                 } else {
    135.                     return NonRenderRenderMeshCornerIn;
    136.                 }
    137.             } else {
    138.                 if (adj.up != default) {
    139.                     return NonRenderRenderMeshSide1;
    140.                 } else if (adj.right != default) {
    141.                     return NonRenderRenderMeshSide2;
    142.                 } else {
    143.                     return NonRenderRenderMeshCornerOut;
    144.                 }
    145.             }
    146.         }
    147.  
    148.         private TileMesh MakeSharedDataNonRender(SubTileAdjacency adj, int i) {
    149.             if (adj.up != default && adj.right != default) {
    150.                 if (adj.diagonal != default) {
    151.                     return Center;
    152.                 } else {
    153.                     return CornerIn;
    154.                 }
    155.             } else {
    156.                 if (adj.up != default) {
    157.                     return Side1;
    158.                 } else if (adj.right != default) {
    159.                     return Side2;
    160.                 } else {
    161.                     return CornerOut;
    162.                 }
    163.             }
    164.         }
    165.     }
    166.  
    167.     protected override JobHandle OnUpdate(JobHandle inputDependencies) {
    168.         var job = new UpdateTileModelJob();
    169.         job.TileMesh = GetSharedComponentTypeHandle<TileMesh>();
    170.         job.SubTileAdjacency = GetComponentTypeHandle<SubTileAdjacency>(true);
    171.         job.Entities = GetEntityTypeHandle();
    172.         job.EntityCommandBuffer = entityCommandBufferSystem.CreateCommandBuffer().AsParallelWriter();
    173.         JobHandle handle = job.Schedule(m_Group, inputDependencies);
    174.         entityCommandBufferSystem.AddJobHandleForProducer(handle);
    175.  
    176.         var job2 = new UpdateTileAdjacencyJob();
    177.         job2.TileAdjacency = GetComponentTypeHandle<TileAdjacency>(true);
    178.         job2.LinkedEntityGroup = GetBufferTypeHandle<LinkedEntityGroup>(true);
    179.         job2.SubTileAdjacency = GetComponentDataFromEntity<SubTileAdjacency>(true);
    180.         job2.Entities = GetEntityTypeHandle();
    181.         job2.EntityCommandBuffer = entityCommandBufferSystem.CreateCommandBuffer().AsParallelWriter();
    182.         JobHandle handle2 = job2.Schedule(m_Group2, JobHandle.CombineDependencies(handle, inputDependencies));
    183.         entityCommandBufferSystem.AddJobHandleForProducer(handle2);
    184.  
    185.         return JobHandle.CombineDependencies(handle, handle2);
    186.     }
    187. }
     
  5. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    The second image shows profiler only main thread. What about other thread?
     
  6. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Updated to use JobComponentSystem

    Code (CSharp):
    1. [UpdateInGroup(typeof(PlantGroupEndFrame))]//EntityManager forces WithoutBurst()
    2. public class WateredCropTileSystem : JobCommandBufferComponentSystem {
    3.     public override JobHandle OnUpdate(JobHandle inputDeps, EntityCommandBuffer EntityCommandBuffer) {
    4.         Entities.WithoutBurst().WithAll<LinkedEntityGroup, SoilNutriendDataWater>().ForEach((Entity e, in DynamicBuffer<LinkedEntityGroup> crop, in SoilNutriendDataWater t3) => {
    5.             EntityCommandBuffer.AddComponent<SoilNutriendDataWater>(e);
    6.             for (int i = 0; i < crop.Length; i++) {
    7.                 if (EntityManager.HasComponent<SoilNutriendDataWater>(crop[i].Value)) {
    8.                     EntityCommandBuffer.SetComponent(crop[i].Value, t3);
    9.                 } else {
    10.                     EntityCommandBuffer.AddComponent(crop[i].Value, t3);
    11.                 }
    12.             }
    13.         }).Run();
    14.         return inputDeps;
    15.     }
    16. }
    Code (CSharp):
    1. public abstract class JobCommandBufferComponentSystem : JobComponentSystem {
    2.     EndSimulationEntityCommandBufferSystem entityCommandBufferSystem;
    3.     protected override void OnCreate() {
    4.         base.OnCreate();
    5.         entityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    6.     }
    7.  
    8.     protected sealed override JobHandle OnUpdate(JobHandle inputDeps) {
    9.         EntityCommandBuffer EntityCommandBuffer = entityCommandBufferSystem.CreateCommandBuffer();
    10.         JobHandle handle = OnUpdate(inputDeps, EntityCommandBuffer);
    11.         entityCommandBufferSystem.AddJobHandleForProducer(handle);
    12.         return handle;
    13.     }
    14.  
    15.     public abstract JobHandle OnUpdate(JobHandle inputDeps, EntityCommandBuffer EntityCommandBuffer);
    16. }
    The advantages were minimal, about 10ms from 170~ to 160~
    Will try to remove the need of checking HasComponent and thus enabling burst
     
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    The second picture is of one of the job worker threads, not the main thread, all job workers are busy with the same thing, which is not the specific code I am figuring out how to optimize right now
     
  8. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    Any reason why you run without burst?
     
  9. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I am actually trying to, now I am getting all kinds of strange issues, like:
    InvalidOperationException: The writeable ComponentDataFromEntity<SoilNutriendDataWater> <>c__DisplayClass_OnUpdate_LambdaJob0.JobData.EntitySoilNutriendDataWater is the same ComponentTypeHandle<SoilNutriendDataWater> as <>c__DisplayClass_OnUpdate_LambdaJob0.JobData._lambdaParameterValueProviders.forParameter_t3._typeHandle, two containers may not be the same (aliasing).

    Code (CSharp):
    1. [UpdateInGroup(typeof(PlantGroupEndFrame))]
    2. public class WateredCropTileSystem : JobCommandBufferComponentSystem {
    3.     public override JobHandle OnUpdate(JobHandle inputDeps, EntityCommandBuffer EntityCommandBuffer) {
    4.         ComponentDataFromEntity<SoilNutriendDataWater> EntitySoilNutriendDataWater = GetComponentDataFromEntity<SoilNutriendDataWater>(false);
    5.         EntityCommandBuffer.ParallelWriter ParallelWriter = EntityCommandBuffer.AsParallelWriter();
    6.         Entities.WithNativeDisableParallelForRestriction(EntitySoilNutriendDataWater).WithAll<LinkedEntityGroup, SoilNutriendDataWater>().ForEach((Entity e, in DynamicBuffer<LinkedEntityGroup> crop, in SoilNutriendDataWater t3) => {
    7.             for (int i = 0; i < crop.Length; i++) {
    8.                 if (EntitySoilNutriendDataWater.HasComponent(crop[i].Value)) {
    9.                     EntitySoilNutriendDataWater[crop[i].Value] = t3;
    10.                 } else {
    11.                     ParallelWriter.AddComponent(0, crop[i].Value, t3);
    12.                 }
    13.             }
    14.         }).Schedule(inputDeps);
    15.         return inputDeps;
    16.     }
    17. }
     
  10. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    I don't think you can have both
    Code (CSharp):
    1. ComponentDataFromEntity<SoilNutriendDataWater> EntitySoilNutriendDataWater = GetComponentDataFromEntity<SoilNutriendDataWater>(false);
    and
    Code (CSharp):
    1. WithAll<LinkedEntityGroup, SoilNutriendDataWater>
    in the same job
    Job will try accessing same component SoilNutriendDataWater, by different entities.
    I think this restriction is mainly for a safety.
    You could eliminate
    Code (CSharp):
    1. WithAll<..., SoilNutriendDataWater>
    grab entity and use ComponentDataFromEntity instad.
     
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,270
    It is not the WithAll<>, that works with CDFE. It is the "in" argument that conflicts for safety reasons.

    There's a lot of issues with this code though.
    1) You are using JobComponentSystem, which has been outdated for nearly a year. Use SystemBase as it is more stable, even in 2019.4 using Entities 0.11.
    2) ParallelWriter.AddComponent should use an entityInQueryIndex rather than 0 for the SortKey (formerly known as JobIndex).
    3) LinkedEntityGroup is more than just the immediate children. It is also self-referential along with including children's children.
    4) Does the crop really need an identical component to the parent? It may make more sense to nest the type in another type. For example, I can nest a Translation inside a custom component. It will respond to the custom component queries rather than Translation queries, but holds identical data to a Translation.
     
    Guedez and Antypodish like this.
  12. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    Good point?