Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Getting Component directly from Entity in a Job

Discussion in 'Entity Component System' started by Tazadar66, Sep 18, 2018.

  1. Tazadar66

    Tazadar66

    Joined:
    Aug 27, 2013
    Posts:
    57
    Hello,

    I am trying to get a component directly from an entity in a running job. I can't get it from the inject since I am using a DynamicBuffer to get the Entity.

    I have tried to get it from EntityManager but it does not work :(

    Maybe my approach is wrong but here is my Test code :

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4.  
    5. public class TestSystem : JobComponentSystem
    6. {
    7.     public struct Data
    8.     {
    9.         public readonly int Length;
    10.         public BufferArray<CellEntityComponent> Buffers;
    11.     }
    12.  
    13.     [Inject] Data m_Data;
    14.  
    15.     public struct MyJob : IJobParallelFor
    16.     {
    17.         [ReadOnly] public BufferArray<CellEntityComponent> Buffers;
    18.         [ReadOnly] public int bufferIndex;
    19.         [ReadOnly] public EntityManager em;
    20.  
    21.         public EntityCommandBuffer.Concurrent EntityCommandBuffer;
    22.  
    23.         public void Execute(int i)
    24.         {
    25.             Entity e = Buffers[bufferIndex][i].cell;
    26.             CellDataComponent cdc = em.GetComponentData<CellDataComponent>(e);
    27.             cdc.cost = cdc.cost + 1;
    28.             EntityCommandBuffer.SetComponent(e, cdc);
    29.         }
    30.     }
    31.  
    32.     // Barrier for updating components
    33.     [Inject] private TestBarrier m_barrier;
    34.  
    35.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    36.     {
    37.         if(m_Data.Length > 0)
    38.         {
    39.             return new MyJob
    40.             {
    41.                 Buffers = m_Data.Buffers,
    42.                 bufferIndex = 0,
    43.                 em = World.Active.GetExistingManager<EntityManager>(),
    44.                 EntityCommandBuffer = m_barrier.CreateCommandBuffer()
    45.             }.Schedule(m_Data.Buffers[0].Length, 32, inputDeps);
    46.         }
    47.  
    48.         return base.OnUpdate(inputDeps);
    49.     }
    50. }
    51.  
    52. public class TestBarrier : BarrierSystem
    53. { }
    54.  
    Thanks for your help.
     
  2. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    just use

    Code (CSharp):
    1.  
    2. //inside system
    3. ComponentDataFromEntity<MyType> myTypeFromEntity = GetComponentDataFromEntity<MyType>(true);
    4.  
    5. //inside job
    6. if(myTypeFromEntity.Exists(entity)) {
    7.    MyType myType = myTypeFromEntity[entity]
    8. }

    Appendix:

    You don't have to call World.Active.GetExistingManager<EntityManager>() inside a system!

    Just use EntityManager...
    or even better use the provided methods like
    GetComponentDataFromEntity
    ...
     
    Last edited: Sep 18, 2018
  3. Tazadar66

    Tazadar66

    Joined:
    Aug 27, 2013
    Posts:
    57
    Thanks for your answer :)
    It works but it is very slow for 1024 * 1024 entities.

    I will try something else. Here is the updated code for those who have the same question :

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4.  
    5. public class TestSystem : JobComponentSystem
    6. {
    7.     public struct Data
    8.     {
    9.         public readonly int Length;
    10.         public BufferArray<CellEntityComponent> Buffers;
    11.     }
    12.  
    13.     [Inject] Data m_Data;
    14.  
    15.     public struct MyJob : IJobParallelFor
    16.     {
    17.         [ReadOnly] public BufferArray<CellEntityComponent> Buffers;
    18.         [ReadOnly] public int bufferIndex;
    19.         [ReadOnly] public ComponentDataFromEntity<CellDataComponent> em;
    20.  
    21.         public EntityCommandBuffer.Concurrent EntityCommandBuffer;
    22.  
    23.         public void Execute(int i)
    24.         {
    25.             Entity e = Buffers[bufferIndex][i].cell;
    26.             CellDataComponent cdc = em[e];
    27.             cdc.cost = cdc.cost + 1;
    28.             EntityCommandBuffer.SetComponent(e, cdc);
    29.         }
    30.     }
    31.  
    32.     // Barrier for updating components
    33.     [Inject] private TestBarrier m_barrier;
    34.  
    35.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    36.     {
    37.         if(m_Data.Length > 0)
    38.         {
    39.             //inside system
    40.             ComponentDataFromEntity<CellDataComponent> myTypeFromEntity = GetComponentDataFromEntity<CellDataComponent>(true);
    41.  
    42.             return new MyJob
    43.             {
    44.                 Buffers = m_Data.Buffers,
    45.                 bufferIndex = 0,
    46.                 em = myTypeFromEntity,
    47.                 EntityCommandBuffer = m_barrier.CreateCommandBuffer()
    48.             }.Schedule(m_Data.Buffers[0].Length, 32, inputDeps);
    49.         }
    50.  
    51.         return base.OnUpdate(inputDeps);
    52.     }
    53. }
    54.  
    55. public class TestBarrier : BarrierSystem
    56. { }
    57.  
     
    Skyblade likes this.
  4. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Maybe you use an native array of CellData instead of entities for your purpose.
    So you have the control over the order...

    or you create a cache ... so that you can benefit from both
     
  5. Tazadar66

    Tazadar66

    Joined:
    Aug 27, 2013
    Posts:
    57
    I will give a try :)
    Will let you know thanks !
     
  6. Tazadar66

    Tazadar66

    Joined:
    Aug 27, 2013
    Posts:
    57
    What is a cache for you ?
    How do you implement it?
     
  7. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    No, you have to use ComponentSystem.GetComponentDataFromEntity. Using EntityManager.GetComponentDataFromEntity will not track dependencies.
     
    edalbeci likes this.
  8. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    I know...
    I just want to say that he don't need to use World.Active.GetExistingManager to get the EntityManager
     
  9. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Just a NativeArray of the required data. But with the order you need / you know...

    Maybe based on integer position in case of a grid

    E.g: each 2D array can be represented as 1D array
    The index of (X,Y) is then just Y * Width + X

    With ISystemStateComponentData you can check if an entity needs to be added to the cache or needs to be erased from the cache.

    Therefore if data needs to be added to cache the entity hasn't the specific ISystemStateComponentData component attached to it. So add the entity / data to the cache. Assign the ISystemStateComponentData component to the entity.

    If the entity was deleted or the component removed, the data or entity needs to be removed from the cache.

    This is more to illustrate the usecase
    Code (CSharp):
    1.  
    2. public class SomeSystem : ComponentSystem {
    3.  
    4.     struct InCache : ISystemStateComponentData { public int index; }
    5.  
    6.     struct DataToAdd {
    7.         public EntityArray Entity;
    8.         public ComponentDataArray<MyData> myData;
    9.         public ComponentDataArray<GridPosition> position;
    10.         public SubtractiveComponent<InCache> InCache;
    11.         public readonly Length;
    12.     }
    13.    
    14.     struct DataToRemove {
    15.         public EntityArray Entity;
    16.         public SubtractiveComponent<MyData> myData;
    17.         public ComponentDataArray<InCache> InCache;
    18.         public readonly Length;
    19.     }
    20.    
    21.     struct SomeOtherStuff {
    22.         public ComponentDataArray<OtherData> otherData;
    23.         public ComponentDataArray<GridPosition> position;
    24.         public readonly Length;
    25.     }
    26.    
    27.    
    28.     NativeArray<MyData> myDataCache;
    29.    
    30.     [Inject] DataToAdd dataToAdd;
    31.     [Inject] DataToRemove dataToRemove;
    32.     [Inject] SomeOtherStuff someOtherStuff;
    33.    
    34.     protected override void OnCreateManager() {
    35.         base.OnCreateManager();
    36.         myDataCache = new NativeArray<MyData>(1024*1024, Allocator.Persistent);
    37.     }
    38.    
    39.     protected override void OnUpdate() {
    40.         for(int i=0; i<dataToAdd.Length; i++) {
    41.             int2 pos = dataToAdd.position[i].value;
    42.             int index = pos.y * 1024 + pos.x;
    43.             myDataCache[index] = dataToAdd.myData;
    44.             PostUpdateCommand.AddComponent(dataToAdd.Entity[i], new InCache{ value = index; } )
    45.         }
    46.        
    47.         for(int i=0; i<dataToRemove.Length; i++) {
    48.             int index = dataToRemove.InCache[i].index;
    49.             myDataCache[index] = MyData.Empty;
    50.             PostUpdateCommand.RemoveComponent<InCache>(dataToRemove.Entity[i] )
    51.         }
    52.        
    53.         for(int i=0; i<dataToAdd.Length; i++) {
    54.             int2 pos = someOtherStuff.position[i].value;
    55.             int index = pos.y * 1024 + pos.x;
    56.             MyData myData = myDataCache[index];
    57.             //do what ever...
    58.         }
    59.     }
    60. }
     
  10. Skyblade

    Skyblade

    Joined:
    Nov 19, 2013
    Posts:
    77
    I don't understand which Entity's component `GetComponentDataFromEntity` method returns.
    When using `EntityManager.GetComponentData` method, one need to pass entity as a parameter.
     
  11. mike_acton

    mike_acton

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    110
    Skyblade likes this.
  12. Skyblade

    Skyblade

    Joined:
    Nov 19, 2013
    Posts:
    77
  13. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    As the name says, it's a command buffer.

    It just queues a bunch of commands which then will be executed by the Barrier or System it was created from.
    You don't have direct access to data.
    Thats why, you won't get back an Entitiy from EntityCommandBuffer.CreateEntity
     
    Skyblade likes this.
  14. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    714
    Lets say that I have a IComponentData that stores an Entity, how can I get access to this entity component in a job?

    For instance, I have a ClockSpanSystem that processes ClockSpan components, in which are stored references to the Clock it has to use to calculate the spans. So I have the reference to the Entity inside the IJobForEach<ClockSpan>, but what I need is access to the Clock component of that entity.

    Is that doable / possible inside a job? If not, what is the best way for me to have a ClockSpan that refers to its associated Clock for the calculation, knowing that many ClockSpan may or may not use the same Clock?

    For now I use GetSingleton outside the job as I only have one Clock, but I want to change this so my system can handle any amount of spans and clocks. I kind of struggle with managing dependencies like that in the ECS+Job environment.

    Code (CSharp):
    1.   protected override JobHandle OnUpdate(JobHandle inputDependencies)
    2.         {
    3.             var job = new UpdateSpansJob()
    4.             {
    5.                 Clock = GetSingleton<Clock>(), // TODO: Find a way to bind a specific Clock to a span
    6.                 UnityDeltaTime = Time.deltaTime
    7.             };
    8.             return job.Schedule(this, inputDependencies);
    9.         }
    For reference, the ClockSpanSystem simply evaluate if the time of day is inside a span and its progress inside the span if appropriate.

    Code (CSharp):
    1. [BurstCompile]
    2.         private struct UpdateSpansJob : IJobForEach<ClockSpan>
    3.         {
    4.             [ReadOnly] public Clock Clock;
    5.             [ReadOnly] public float UnityDeltaTime;
    6.            
    7.             public void Execute(ref ClockSpan span)
    8.             {
    9.                 if (span.DurationInHours == 0) return;
    10.                
    11.                 var isActive    = (span.IsActive == 1);
    12.                 var isOnDay     = Clock.Day >= span.StartingDay;
    13.                 var isOnTime    = Clock.Hour >= span.StartingTime;
    14.                 var day         = span.StartingDay;
    15.                
    16.                 if (isOnDay && isOnTime) isActive = true;
    17.                 if (!isActive) return;
    18.              
    19.                 var progress                = span.Progress + Clock.DeltaTime;
    20.                 var normalizedProgress      = progress / span.Duration;
    21.                 var realtimeProgress        = span.RealtimeProgress + UnityDeltaTime;
    22.                
    23.                 if (normalizedProgress >= 0.99f)
    24.                 {
    25.                     progress            = 0;
    26.                     normalizedProgress  = 0;
    27.                     realtimeProgress    = 0;
    28.                     day                 = Mathf.FloorToInt(Clock.Day) + 1 + span.DelayBetweenSpans;
    29.                     isActive = false;
    30.                 }
    31.  
    32.                 var newSpan                     = span;
    33.                     newSpan.Progress            = progress;
    34.                     newSpan.NormalizedProgress  = normalizedProgress;
    35.                     newSpan.RealtimeProgress    = realtimeProgress;
    36.                     newSpan.IsActive            = isActive ? 1 : 0;
    37.                     newSpan.StartingDay         = day;
    38.                     newSpan.RealtimeDuration    = span.Duration * (1 / Clock.SpeedOfTime);
    39.  
    40.                 span = newSpan;
    41.             }
    42.         }
    Cheers!
     
  15. dstilwagen

    dstilwagen

    Joined:
    Mar 14, 2018
    Posts:
    36
  16. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    714
    The structure I am seeking is this:
    • Planet A
      • Clock A
        • System A updates Component A according to Clock A values, since it is on Planet A
        • System B updates Component B according to Clock A values, since it is on Planet A
        • Etc.
    • Planet B
      • Clock B
        • System A updates Component A according to Clock B values, since it is on Planet B
        • System B updates Component B according to Clock B values, since it is on Planet B
        • Etc.
    So my System A and System B should "get" and pass the values from their associated Clock to the Components they operate on. Yet I don't know how to get these values.

    I thought of adding an Entity ClockReference field in their components, and initialize their values at start. So this way I'd have a shared Clock per Planet and I'd be suppert happy.

    The problem with that, and with ComponentDataFromEntity, is that it can't get the ComponentData of that field's Entity. The method only gives me the data of the Entity being processed by the system, which is not my Clock.

    The answer must be simple, but I searched for hours and couldn't find any viable example solving this. I use the Clock example as it is simple, but there are various depency trees like that in a game and I'd like to know how to achieve it in DOTS.
     
  17. dstilwagen

    dstilwagen

    Joined:
    Mar 14, 2018
    Posts:
    36
    The simplest thing would probably be to just iterate over all the clock and planet components. So using the code you posted above I would change the OnUpdate function to something like this.
    Code (CSharp):
    1.  
    2. protected override JobHandle OnUpdate(JobHandle inputDependencies)
    3.     {
    4.         //Store the previous inputDeps so we can chain the dependencies.
    5.         var prevInputDeps = inputDeps;
    6.         //Get any entities with a Planet and a Clock component and iterate through them and create your update jobs.
    7.         Entities.WithAny<Planet, Clock>().ForEach((ref Planet planet, ref Clock clock)) =>
    8.         {
    9.             var job = new UpdateSpansJob()
    10.                       {
    11.                           Clock          = clock,
    12.                           UnityDeltaTime = Time.deltaTime
    13.                       };
    14.             var nextInputDeps = job.Schedule(this, prevInputDeps);
    15.             //NOTE: This may not be the correct or best way to handle dependencies. You may need to use
    16.             //JobHandle.CombineDependencies(JobHandle, JobHandle) or possibly populate an array with each
    17.             //dependency and combine them after or set up and store each job then schedule them all at once after.
    18.             prevInputDeps = nextInputDeps;
    19.         };
    20.         return prevInputDeps;
    21.     }
    22.  
    You could also use an EntityQuery and ToComponentData<Clock>() to get a list of all the clock components and iterate over just that.
     
  18. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,217
    ComponentDataFromEntity is exactly for this purpose. ComponentDataFromEntity is a struct you assign to your IJobForEach when you schedule it (just like a NativeArray) and it lets you do something like this in a job:
    var clockComponent = clockComponentDataFromEntity[clockEntity];

    By making it [ReadOnly] in the job and setting the ReadOnly argument to true when calling this.GetComponentDataFromEntity in your OnUpdate method, you can use it in a parallel job worry-free.
     
    alexandre-fiset likes this.