Search Unity

I did something wrong with my SharedComponentData

Discussion in 'Entity Component System' started by Guedez, Apr 1, 2020.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I am trying to render 4 meshes, each gets it's own SharedComponentData, when I want to draw, I filter the query by said SharedComponentData, yet the queries return 0 entities, even though the debugger clearly shows me they are there.

    Edit: It seems all m_Group thinks they are querying for the index 1, which seems to be the CornerOut

    All 4 queries have that value being 1


    Relevant sources:
    System that adds/changes the SharedData (whole code since 90% is relevant)
    Code (CSharp):
    1.  
    2. (...)
    3. public class UpdateTileModel : JobComponentSystem {
    4.     private static TileMesh Center;
    5.     private static TileMesh CornerOut;
    6.     private static TileMesh Side;
    7.     private static TileMesh CornerIn;
    8.  
    9.     EntityQuery m_Group;
    10.     EndSimulationEntityCommandBufferSystem entityCommandBufferSystem;
    11.     protected override void OnCreate() {
    12.         base.OnCreate();
    13.         entityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    14.         Mesh center = Resources.Load<Mesh>("Center");
    15.         Mesh cornerIn = Resources.Load<Mesh>("Inner_Corner");
    16.         Mesh cornerOut = Resources.Load<Mesh>("Outer_Corner");
    17.         Mesh side = Resources.Load<Mesh>("Side");
    18.  
    19.         Center = new TileMesh() { Value = center, InstanceID = center.GetInstanceID() };
    20.         CornerOut = new TileMesh() { Value = cornerOut, InstanceID = cornerOut.GetInstanceID() };
    21.         Side = new TileMesh() { Value = side, InstanceID = side.GetInstanceID() };
    22.         CornerIn = new TileMesh() { Value = cornerIn, InstanceID = cornerIn.GetInstanceID() };
    23.  
    24.         m_Group = GetEntityQuery(ComponentType.ReadOnly<SubTileAdjacency>(), typeof(ToUpdateTileModel));
    25.     }
    26.  
    27.     //[BurstCompile] cannot use burst because TileMesh has a Mesh component
    28.     struct UpdateTileModelJob : IJobChunk {
    29.         public ArchetypeChunkSharedComponentType<TileMesh> TileMesh;
    30.         [ReadOnly] public ArchetypeChunkComponentType<SubTileAdjacency> TileAdjacency;
    31.         [ReadOnly] public ArchetypeChunkEntityType Entities;
    32.         public EntityCommandBuffer.Concurrent EntityCommandBuffer;
    33.  
    34.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) {
    35.             var TileAdjacency = chunk.GetNativeArray(this.TileAdjacency);
    36.             var Entities = chunk.GetNativeArray(this.Entities);
    37.             if (chunk.Has(TileMesh)) {
    38.                 for (var i = 0; i < chunk.Count; i++) {
    39.                     EntityCommandBuffer.SetSharedComponent(chunkIndex, Entities[i], MakeSharedData(TileAdjacency[i]));
    40.                     EntityCommandBuffer.RemoveComponent<ToUpdateTileModel>(chunkIndex, Entities[i]);
    41.                 }
    42.             } else {
    43.                 for (var i = 0; i < chunk.Count; i++) {
    44.                     EntityCommandBuffer.AddSharedComponent(chunkIndex, Entities[i], MakeSharedData(TileAdjacency[i]));
    45.                     EntityCommandBuffer.RemoveComponent<ToUpdateTileModel>(chunkIndex, Entities[i]);
    46.                 }
    47.             }
    48.         }
    49.  
    50.         private TileMesh MakeSharedData(SubTileAdjacency adj) {
    51.             if (adj.up != default && adj.right != default) {
    52.                 if (adj.diagonal != default) {
    53.                     return Center;
    54.                 } else {
    55.                     return CornerIn;
    56.                 }
    57.             } else {
    58.                 if (adj.up != default) {
    59.                     return Side;
    60.                 } else if (adj.right != default) {
    61.                     return Side;
    62.                 } else {
    63.                     return CornerOut;
    64.                 }
    65.             }
    66.         }
    67.     }
    68.     (...)
    69. }
    70.  
    System that reads the entities to queue drawing it (only relevant bits):
    Code (CSharp):
    1. (...)
    2. public class DrawCropTile : JobComponentSystem {
    3.     (...)
    4.     EntityQuery[] m_Group;
    5.     (...)
    6.     protected override void OnCreate() {
    7.         base.OnCreate();
    8.         (..)
    9.         Mesh center = Resources.Load<Mesh>("Center");
    10.         Mesh cornerIn = Resources.Load<Mesh>("Inner_Corner");
    11.         Mesh cornerOut = Resources.Load<Mesh>("Outer_Corner");
    12.         Mesh side = Resources.Load<Mesh>("Side");
    13.  
    14.         m_Group = new EntityQuery[4];
    15.         m_Group[0] = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>(),
    16.             ComponentType.ReadOnly<TileRendertData>(),
    17.             ComponentType.ReadOnly<TileMesh>(),
    18.             ComponentType.ChunkComponentReadOnly<InFrustrum>());
    19.         m_Group[1] = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>(),
    20.             ComponentType.ReadOnly<TileRendertData>(),
    21.             ComponentType.ReadOnly<TileMesh>(),
    22.             ComponentType.ChunkComponentReadOnly<InFrustrum>());
    23.         m_Group[2] = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>(),
    24.             ComponentType.ReadOnly<TileRendertData>(),
    25.             ComponentType.ReadOnly<TileMesh>(),
    26.             ComponentType.ChunkComponentReadOnly<InFrustrum>());
    27.         m_Group[3] = GetEntityQuery(ComponentType.ReadOnly<LocalToWorld>(),
    28.             ComponentType.ReadOnly<TileRendertData>(),
    29.             ComponentType.ReadOnly<TileMesh>(),
    30.             ComponentType.ChunkComponentReadOnly<InFrustrum>());
    31.  
    32.         int v = center.GetInstanceID(); << to debug and make sure both are really equal, they are
    33.         m_Group[0].SetSharedComponentFilter(new TileMesh() { Value = center, InstanceID = v });
    34.         m_Group[1].SetSharedComponentFilter(new TileMesh() { Value = cornerIn, InstanceID = cornerIn.GetInstanceID() });
    35.         m_Group[2].SetSharedComponentFilter(new TileMesh() { Value = cornerOut, InstanceID = cornerOut.GetInstanceID() });
    36.         m_Group[3].SetSharedComponentFilter(new TileMesh() { Value = side, InstanceID = side.GetInstanceID() });
    37.         (...)
    38.     }
    39.  
    40.     protected override JobHandle OnUpdate(JobHandle inputDependencies) {
    41.         (...)
    42.         for (int i = 0; i < 4; i++) {
    43.             int count = Matrix4x4List[i].Length; <<always zero because the job didn't found any to iterate through
    44.            if (count > 0) {
    45.                (...) << code that actually draws never runs
    46.            }
    47.            int worstcasecount = m_Group[i].CalculateEntityCount(); << always returns zero if I set SetSharedComponentFilter
    48.            if (worstcasecount >= Matrix4x4List[i].Capacity) {
    49.                Matrix4x4List[i].ResizeUninitialized(worstcasecount * 2);
    50.            }
    51.            (...) << job scheduling
    52.        }
    53.        return combined;
    54.    }
    55. }
    The SharedComponentData in question:
    Code (CSharp):
    1. [Serializable]
    2. public struct TileMesh : ISharedComponentData, IEquatable<TileMesh> {
    3.     public Mesh Value;
    4.     public int InstanceID;
    5.  
    6.     public bool Equals(TileMesh other) {
    7.         return InstanceID == other.InstanceID; <<should work, right?
    8.     }
    9.     public override int GetHashCode() {
    10.         return InstanceID;<<should work, right? all valid values are distinct
    11.     }
    12. }
     
    Last edited: Apr 1, 2020
  2. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    It seems the system is cacheing and giving me the same EntityQuery 4 times rather than giving me 4 equal entity queries that I can set filters to, so I actually am setting the filter to the same EntityQuery 4 times.
    I've discovered this when I wrote this piece of code before the entity counting and job scheduling, and now all meshes draw

    Code (CSharp):
    1. if(i == 0) {
    2.                 m_Group[i].SetSharedComponentFilter(new TileMesh() { Value = center, InstanceID = 1 });
    3.             } else if (i == 1) {
    4.                 m_Group[i].SetSharedComponentFilter(new TileMesh() { Value = cornerIn, InstanceID = 2 });
    5.             } else if (i == 2) {
    6.                 m_Group[i].SetSharedComponentFilter(new TileMesh() { Value = cornerOut, InstanceID = 3 });
    7.             } else if (i == 3) {
    8.                 m_Group[i].SetSharedComponentFilter(new TileMesh() { Value = side, InstanceID = 4 });
    9.             }
     
  3. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Replacing 3
    GetEntityQuery
    with
    EntityManager.CreateEntityQuery
    fixed the issue.
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    GetEntityQuery returns a cache if the query already exist so all 4 of your queries were exactly the same.

    You should just use 1 query and set your desired filter between access.
     
  5. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Doesn't setting/changing a filter have a overhead cost?
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Never performance tested and not super familiar with the internals in this area, but you probably have more overhead having 4 queries. As far as I'm aware you'd be doing the exact same filtering either way.
     
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Yeah, but making 4 queries makes so that I don't have to set the filter before doing the query, I just take it from the array and use as is. There could be some cacheing going on when making multiple queries using different filters on the same frame, or there could be not. Without knowing that, I will go with the naive thinking that it's the same except I don't have to call SetFilter, which would be, theoretically, faster.
    At the very least, as I understood from SetFilter, it will first check if it's the default struct, then check if the hash already exists on a hashmap, if so, return the result, otherwise cache the value in the hashmap and return the new index
    So unless there is some actual reason to use the same EntityQuery 4 times rather than create 4 of them, everything indicates 4 distinct pre filtered queries are faster than a single query changing filter each iteration, if only because I don't have to change the filter