Search Unity

Shared Component Entity Query in Jobs

Discussion in 'Entity Component System' started by charleshendry, Aug 16, 2019.

  1. charleshendry

    charleshendry

    Joined:
    Jan 7, 2018
    Posts:
    97
    Hi,

    I'm new to ECS and struggling to write my first system. I've seen posts about similar setups, but can't work it out.

    I want to have a system that responds to UI toggles being activated. The toggles should cause the entities color to change, depending on the data in their ObjectOverlayComponent.

    The UI code is a MonoBehaviour and a toggle click creates a single entity with an ObjectOverlayClickedComponent. The system should only run when this component exists and there will only ever be a single entity with this component (the system destroys the entity after processing).

    There's a list of enums in the component, representing different filters and also a value which will be used to further filter the entities in some cases, which can be done with EntityQuery.SetFilter(). From the documentation I understand that the query should be passed into the Schedule() method. Ideally I then access all matching entities and edit the RenderMesh component material in each of them. I'm planning on having the ObjectOverlayComponents as children to the entity with the RenderMesh component because this should give much better chunk utilization (as there'll be a range of different meshes that should get the same color).

    Here's what I have for starters, but it's incomplete and I know some of it'll be incorrect. Any advice much appreciated!

    Code (CSharp):
    1. public struct ObjectOverlayClickedComponent : IComponentData
    2. {
    3.     public OverlayType OverlayType;
    4.     public byte Value;
    5. }
    6.  
    7. public struct ObjectOverlayComponent : ISharedComponentData
    8. {
    9.     public OverlayType OverlayType;
    10.     public byte Value;
    11. }
    12.  
    13. public enum OverlayType
    14. {
    15.     SelectablePatrol,
    16.     SelectableExplore,
    17.     SelectableBomb,
    18.     WithinTerritory,
    19.     Tasks,
    20.     LandValue
    21. }
    22.  
    23. public class ObjectOverlaySystem : JobComponentSystem
    24. {
    25.     EntityQuery queryClick, entityQuery;
    26.  
    27.     [BurstCompile]
    28.     private struct ObjectOverlayJob : IJobParallelFor
    29.     {
    30.         public void Execute(int index)
    31.         {
    32.  
    33.  
    34.         }
    35.     }
    36.  
    37.     protected override void OnCreateManager()
    38.     {
    39.         queryClick = GetEntityQuery(ComponentType.ReadOnly<ObjectOverlayClickedComponent>());
    40.         entityQuery = GetEntityQuery(ComponentType.ReadOnly<ObjectOverlayComponent>());
    41.     }
    42.  
    43.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    44.     {
    45.         if (queryClick.CalculateEntityCount() == 0) { return inputDeps; }
    46.         var query = queryClick.ToComponentDataArray<ObjectOverlayClickedComponent>(Allocator.Temp)[0];
    47.  
    48.         entityQuery.SetFilter(new ObjectOverlayComponent { OverlayType = query.OverlayType, Value = query.Value });
    49.         //NativeArray<Entity> entities = entityQuery.ToEntityArray(Allocator.Temp);
    50.  
    51.         var job = new ObjectOverlayJob() { };
    52.         return job.Schedule(entityQuery, inputDeps);
    53.     }
    54. }
     
  2. Deleted User

    Deleted User

    Guest

    There are few problems here.
    First here:
    var query = queryClick.ToComponentDataArray<ObjectOverlayClickedComponent>(Allocator.Temp)[0];

    You cannot do that. That causes memory leak. You need to assign the array to a variable and call Dispose after you're done with it.

    Also, You can use GetSingleton method to get a singleton component data from a query.

    Another point, use RequireForUpdate method. With that your system doesn't run when query doesn't have a result. And you won't need this line anymore:
     if (queryClick.CalculateEntityCount() == 0) { return inputDeps; }


    Also, I don't think that you need a IJobParalleFor here. Create an IJobForeach job, add color field to that, then fetch all RenderMesh components in IJobForeach and change their colors.
     
  3. mukultictoc

    mukultictoc

    Joined:
    Sep 24, 2016
    Posts:
    15
    I don't think SharedComponentData can be accessed in jobs. You have to use ComponentSystem to do that.
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,270
    Not true with Allocator.Temp. Allocator.Temp is automatically disposed whenever control is passed back to Unity C++ context. That's what allows Allocator.Temp to allocate in jobs, and typically calling Dispose on something using Allocator.Temp causes errors. You have to be careful when using it on the main thread with other main thread API, but in this case the required lifetime of the array is extremely short and not an issue.
     
  5. charleshendry

    charleshendry

    Joined:
    Jan 7, 2018
    Posts:
    97
    Thanks for everyone's help! Am using ComponentSystem now and had to use Allocator.TempJob in the query and dispose of it to get rid of error messages. Here's the updated code.

    Code (CSharp):
    1. public class ObjectOverlaySystem : ComponentSystem
    2. {
    3.     EntityQuery queryClick, entityQuery;
    4.     readonly List<Material> materials = new List<Material>();
    5.  
    6.     protected override void OnCreate()
    7.     {
    8.         queryClick = GetEntityQuery(ComponentType.ReadOnly<ObjectOverlayClickedComponent>());
    9.         entityQuery = GetEntityQuery(ComponentType.ReadOnly<ObjectOverlayComponent>(), ComponentType.ReadOnly<Parent>());
    10.         RequireForUpdate(queryClick);
    11.  
    12.         materials.Add(Resources.Load("Materials/C1") as Material);
    13.         materials.Add(Resources.Load("Materials/C2") as Material);
    14.         materials.Add(Resources.Load("Materials/C3") as Material);
    15.         materials.Add(Resources.Load("Materials/C4") as Material);
    16.         materials.Add(Resources.Load("Materials/C5") as Material);
    17.     }
    18.  
    19.     protected override void OnUpdate()
    20.     {
    21.         ObjectOverlayClickedComponent query = queryClick.GetSingleton<ObjectOverlayClickedComponent>();
    22.  
    23.         entityQuery.SetFilter(new ObjectOverlayComponent { OverlayType = query.OverlayType, Value = query.Value });
    24.         NativeArray<Parent> parents = entityQuery.ToComponentDataArray<Parent>(Allocator.TempJob);
    25.  
    26.         for (int i = 0; i < parents.Length; i++)
    27.         {
    28.             RenderMesh renderMesh = EntityManager.GetSharedComponentData<RenderMesh>(parents[i].Value);
    29.             renderMesh.material = materials[query.Value];
    30.             EntityManager.SetSharedComponentData<RenderMesh>(parents[i].Value, renderMesh);
    31.         }
    32.         EntityManager.DestroyEntity(queryClick.GetSingletonEntity());
    33.         parents.Dispose();
    34.     }
    35. }