Search Unity

Synchronizing Entities from separate world

Discussion in 'Entity Component System' started by yatagarasu, Jun 19, 2018.

  1. yatagarasu

    yatagarasu

    Joined:
    Apr 24, 2013
    Posts:
    28
    Currently I am working on a setup where two worlds exist, one custom for game logic entities, where all my numerous creatures dwell. And another own is World.Active - default world where GameObjectEntities are created, these GameObjectEntites are "views" which represent currently viewable creatures from game logic world.

    Game Logic world have a system which process entites tagged with UpdateViewEntity data

    Code (CSharp):
    1. struct UpdateViewEntity : IComponentData
    2. {
    3.     Entity view;
    4. }
    view - is a reference to GameObjectEntity entitym so it is basically entity from different world.
    what happens next, is there is a system, which process all UpdateViewEntites in game logic world, process theirs components and update global world's view entites via EntityCommandBuffer in a parallel job.

    It is scheduled like this

    Code (CSharp):
    1. if (m_GatherGroup.Length > 0)
    2. {
    3.     inputDeps = new GatherJob
    4.     {
    5.         View = m_GatherGroup.View,
    6.         Position = m_GatherGroup.Position,
    7.         Rotation = m_GatherGroup.Rotation,
    8.         Creature = m_GatherGroup.Creature,
    9.  
    10.         ecb = World.Active.GetExistingManager<CreatureBarrierSystem>().CreateCommandBuffer()
    11.     }.Schedule(m_GatherGroup.Length, 64, inputDeps);
    12. }
    That gather job is executed in "game logic" world, and use command buffer from global world.

    Code (CSharp):
    1. struct GatherJob : IJobParallelFor
    2. {
    3.     [ReadOnly] public ComponentDataArray<CreaturePlantUpdateViewEntity> View;
    4.     [ReadOnly] public ComponentDataArray<Position> Position;
    5.     [ReadOnly] public ComponentDataArray<Rotation> Rotation;
    6.     [ReadOnly] public ComponentDataArray<CreatureEntity> Creature;
    7.  
    8.     public EntityCommandBuffer.Concurrent ecb;
    9.  
    10.  
    11.     public void Execute(int index)
    12.     {
    13.         Entity e = View[index].entity;
    14.  
    15.         ecb.SetComponent(e, Position[index]);
    16.         ecb.SetComponent(e, Rotation[index]);
    17.         ecb.SetComponent(e, new CreaturePlantViewEntity { Size = Creature[index].Mass });
    18.     }
    19. }
    20.  
    Everything works like a charm, but maybe not as fast as it could be. And look like parallel execution of such task is slower then single threaded execution and update of entities via EntityManager. (35 and 40 fps on 20k objects)

    So I am worried,
    Am I doing this right? Is it allowed to update entites data from another world, does it violate ECS ideas and will that be forbidden in the future?
     
  2. yatagarasu

    yatagarasu

    Joined:
    Apr 24, 2013
    Posts:
    28
    So I finally made a decent synchronization. One system keep a persistent NativeHashMap, which is used to transfer data. Jobs from that system write gathered data to that hash.

    The other system in different world reference that NativeHashMap and pass it to it's jobs, where data is retreived by Entity and used (to update gameobject state.)
     
  3. capyvara

    capyvara

    Joined:
    Mar 11, 2010
    Posts:
    80
    Care to share some code?

    Thanks!
     
    Last edited: Jun 21, 2018
  4. yatagarasu

    yatagarasu

    Joined:
    Apr 24, 2013
    Posts:
    28
    Yes, sure.

    Here is the setup. One world is with game logic, it process Creatures in some way (not important here), and there is one system which gather data from different Creature* components and store it in NativeHassMap

    Code (CSharp):
    1.     public struct CreaturePlantUpdateViewEntity : IComponentData
    2.     {
    3.         public Entity entity;
    4.     }
    5.  
    6.     public class CreaturePlantUpdateViewSystem : JobComponentSystem
    7.     {
    8.         struct GatherGroup
    9.         {
    10.             public int Length;
    11.             [ReadOnly] public ComponentDataArray<CreaturePlantUpdateViewEntity> View;
    12.             [ReadOnly] public ComponentDataArray<Position> Position;
    13.             [ReadOnly] public ComponentDataArray<Rotation> Rotation;
    14.             [ReadOnly] public ComponentDataArray<CreatureEntity> Creature;
    15.         }
    16.  
    17.         [Inject]
    18.         GatherGroup m_GatherGroup;
    19.  
    20.  
    21.         struct GatherJob : IJobParallelFor
    22.         {
    23.             [ReadOnly] public ComponentDataArray<CreaturePlantUpdateViewEntity> View;
    24.             [ReadOnly] public ComponentDataArray<Position> Position;
    25.             [ReadOnly] public ComponentDataArray<Rotation> Rotation;
    26.             [ReadOnly] public ComponentDataArray<CreatureEntity> Creature;
    27.  
    28.             public NativeHashMap<Entity, CreatureData>.Concurrent Output;
    29.  
    30.  
    31.             public void Execute(int index)
    32.             {
    33.                 Entity e = View[index].entity;
    34.  
    35.                // At first it was done via EntityComponentBuffer, but looks like filling them takes a lot of time.
    36.                 //ecb.SetComponent(e, Position[index]);
    37.                 //ecb.SetComponent(e, Rotation[index]);
    38.                 //ecb.SetComponent(e, new CreaturePlantViewEntity { Size = Creature[index].Mass });
    39.                 Output.TryAdd(e, new CreatureData
    40.                 {
    41.                     Position = Position[index],
    42.                     Rotation = Rotation[index],
    43.  
    44.                    // Idea here is that CreaturePlantViewEntity structure aggregates data from many CreatureSomething components, ex from CreatureEntity, CreatureFeedable, CreatureBreedable, CreatureSleepable etc.
    45.                   // integrate in one struct so view update code will be simpler and use less references.
    46.                     View = new CreaturePlantViewEntity { Size = Creature[index].Mass }
    47.                 });
    48.             }
    49.         }
    50.  
    51.         public struct CreatureData
    52.         {
    53.             public Position Position;
    54.             public Rotation Rotation;
    55.             public CreaturePlantViewEntity View;
    56.         }
    57.  
    58.         public NativeHashMap<Entity, CreatureData> m_ViewData;
    59.  
    60.         protected override void OnStartRunning()
    61.         {
    62.             base.OnStartRunning();
    63.         }
    64.  
    65.         protected override void OnStopRunning()
    66.         {
    67.             if (m_ViewData.IsCreated)
    68.             {
    69.                 m_ViewData.Dispose();
    70.             }
    71.  
    72.             base.OnStopRunning();
    73.         }
    74.  
    75.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    76.         {
    77.             if (m_GatherGroup.Length > 0)
    78.             {
    79.                 if (!m_ViewData.IsCreated)
    80.                 {
    81.                     m_ViewData = new NativeHashMap<Entity, CreatureData>(m_GatherGroup.Length, Allocator.Persistent);
    82.                 }
    83.                 else
    84.                 {
    85.                     m_ViewData.Capacity = m_ViewData.Length;
    86.                 }
    87.  
    88.                 new GatherJob
    89.                 {
    90.                     View = m_GatherGroup.View,
    91.                     Position = m_GatherGroup.Position,
    92.                     Rotation = m_GatherGroup.Rotation,
    93.                     Creature = m_GatherGroup.Creature,
    94.  
    95.                     Output = m_ViewData
    96.                 }.Schedule(m_GatherGroup.Length, 64, inputDeps).Complete();
    97. // need to complete that Job before doing anything else.
    98.             }
    99.  
    100.             return base.OnUpdate(inputDeps);
    101.         }
    102.     }
    103.  
    So that system gather all creatures and update their view data. Creature entities are bound to GameObjectEntities from different world via CreaturePlantUpdateViewEntity structure, holding Entity of GameObjectEntity.

    Next system in main world is working on updating GameObjectEntities using CreaturePlantViewEntity data.

    Code (CSharp):
    1.     public struct CreaturePlantViewEntity : IComponentData
    2.     {
    3.         public float Size;
    4.     }
    5.  
    6.     public class CreaturePlantViewEntityComponent : ComponentDataWrapper<CreaturePlantViewEntity>
    7.     {
    8.     }
    9.  
    10.     public class CreaturePlantViewEntitySystem : JobComponentSystem
    11.     {
    12.         struct Group
    13.         {
    14.             public int Length;
    15.             [ReadOnly] public EntityArray Entity;
    16.             [WriteOnly] public ComponentDataArray<Position> Position;
    17.             [WriteOnly] public ComponentDataArray<Rotation> Rotation;
    18.             [WriteOnly] public ComponentDataArray<CreaturePlantViewEntity> View;
    19.         }
    20.  
    21.         [Inject]
    22.         Group m_Group;
    23.  
    24.         public CreaturePlantUpdateViewSystem ViewSystem;
    25.  
    26.         struct UpdateJob : IJobParallelFor
    27.         {
    28.             [ReadOnly] public EntityArray Entity;
    29.             [WriteOnly] public ComponentDataArray<Position> Position;
    30.             [WriteOnly] public ComponentDataArray<Rotation> Rotation;
    31.             [WriteOnly] public ComponentDataArray<CreaturePlantViewEntity> View;
    32.  
    33.             [ReadOnly] public NativeHashMap<Entity, CreaturePlantUpdateViewSystem.CreatureData> ViewData;
    34.  
    35.             public void Execute(int index)
    36.             {
    37.                 Entity e = Entity[index];
    38.  
    39.                 CreaturePlantUpdateViewSystem.CreatureData data;
    40.                 if (ViewData.TryGetValue(e, out data))
    41.                 {
    42.                     Position[index] = data.Position;
    43.                     Rotation[index] = data.Rotation;
    44.                     View[index] = data.View;
    45.                 }
    46.             }
    47.         }
    48.  
    49.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    50.         {
    51.             if (m_Group.Length > 0 && ViewSystem.m_ViewData.IsCreated)
    52.             {
    53.                 new UpdateJob
    54.                 {
    55.                     Entity = m_Group.Entity,
    56.                     Position = m_Group.Position,
    57.                     Rotation = m_Group.Rotation,
    58.                     View = m_Group.View,
    59.  
    60.                     // here ViewData NaticeHashMap is injected into jobs from different system.
    61.                     // ViewData is symply copied into GameObjectsComponent,
    62.                     // later in GameObject's behaviour Update() that data is read and other GameObject components are used. (like set scale of an object depending on Size variable,
    63.                     ViewData = ViewSystem.m_ViewData,
    64.                 }.Schedule(m_Group.Length, 64, inputDeps).Complete();
    65.             }
    66.  
    67.             return base.OnUpdate(inputDeps);
    68.         }
    69.     }
    70.  
    Synchronizing operations are blocking, as Complete function is called on them, but they are parallelized, so execute quite fast.
    My PC have 70fps on 20k objects. twice as more than using EntityCommandBuffer aaproach.

    Update: Opened Profile and now not sure if these Jobs are running in parallel. And a lot of time is spent in BehaviourUpdate, wonder if it is possible to run these jobs in parallel with BehaviourUpdate.
     
    Last edited: Jun 21, 2018
  5. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    I was also looking for a way to have a system updating before a the BehaviourUpdate. This way it would make any jobs from those systems to run in parallel with the BehaviourUpdate.

    Thanks