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

How to create proper pure entity spawn system?

Discussion in 'Entity Component System' started by xVergilx, Jan 24, 2019.

  1. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    I'm facing some issues due to the lack of ECS / DOD experience:

    So, here's my questions:
    - How to organize shared data access from Unity Editor?
    E.g. references to the Mesh / Material, so that they can be set for the RenderMesh component.
    I'm thinking about some static lookup that stores RenderMesh components fetched from ScriptableObject, but I'm not sure if that's a proper solution.

    Edit: Or alternatively bootstrap the data objects to the entity archetypes?

    - How to construct an entity to communicate which entity archetype / prefab should be spawned by the SpawnSystem?
    - How to create a spawn system that is utilizing JobComponentSystem and can be bursted?


    Boid example uses ComponentSystem, and the previous examples were taken down as well, so no luck there.
     
    Last edited: Jan 24, 2019
    senynen likes this.
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    So re-thinking this whole thing, I've ended up using something like this to bind the components from the gameobject wrapper:
    Code (CSharp):
    1. /// <summary>
    2. /// Bootstraps entity from the entity template (gameObject) and attaches Prefab component to it
    3. ///
    4. /// Equal to creating an entity prefab
    5. /// </summary>
    6. public static Entity CreatePrefab(this EntityManager manager, GameObject template) {
    7.     Entity prefab = manager.Instantiate(template);
    8.     manager.AddComponentData(prefab, new Prefab());
    9.  
    10.     return prefab;
    11. }
    12.  
    As a bonus, it seems like (hybrid) render system is not displaying prefabs, which is good.

    Now I need to figure out how to bind the entity prefab to the spawn request data, and process that via job.
    And also simplify access to the entity prefabs via lookup or something similar.
     
    Last edited: Jan 24, 2019
    senynen likes this.
  3. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Ok, algorithm so far:
    1. All templates (gameObjects) are pushed from the single scriptable objects to the entities, via .CreatePrefab();
    This creates all required prefabs in the entity system.

    Pro: Every prefab gets an entity prefab at the bootstrap phase (So it's as far "pure" as it gets without creating archetypes in code / fetching data manually);
    Con: Every prefab in the ECS require to be added manually, but that's fixable with some editor magic.

    2. To spawn an entity, I've created a Spawn method, that queues an entity SpawnRequestData that contains ComponentType which resembles a Tag.

    3. SpawnSystem should now fetch the SpawnRequestData, and create entities via EntityCommandBuffer based on the SpawnRequestData->EntityTag.


    Can someone explain me how to fetch all Entities that contain multiple components in a system / job?
    E.g. I want first entity that contains Prefab and passed ComponentType. Is it possible?

    Code so far:

    SpawnRequestData:
    Code (CSharp):
    1.  public struct SpawnRequestData : IComponentData {
    2.         public float3 Position;
    3.         public quaternion Rotation;
    4.  
    5.         public ComponentType EntityTag;
    6.     }
    SpawnSystem and it's extension:
    Code (CSharp):
    1. public class SpawnSystem : JobComponentSystem {
    2.         struct SpawnJob : IJobProcessComponentData<SpawnRequestData> {
    3.             public EntityCommandBuffer.Concurrent Buffer;
    4.             public EntityManager EntityManager;
    5.    
    6.             public void Execute(ref SpawnRequestData data) {
    7.                   // How to fetch <Prefab, data.EntityTag>?
    8.                   // How to fetch jobIndex for EntityCommandBuffer?
    9.             }
    10.         }
    11.  
    12.         protected override JobHandle OnUpdate(JobHandle inputDeps) {
    13.             SpawnJob spawnJob = new SpawnJob();
    14.  
    15.             EntityManager manager = World.Active.GetExistingManager<EntityManager>();
    16.             spawnJob.EntityManager = manager;
    17.    
    18.             JobHandle spawnHandle = spawnJob.Schedule(this, inputDeps);
    19.             return spawnHandle;
    20.         }
    21.     }
    22.  
    23.     public static class SpawnSystemExt {
    24.         private static EntityArchetype SpawnRequest { get; set; }
    25.  
    26.         /// <summary>
    27.         /// Initializes required archetypes
    28.         /// </summary>
    29.         /// <param name="manager"></param>
    30.         public static void Init(EntityManager manager) {
    31.             SpawnRequest = manager.CreateArchetype(typeof(SpawnRequestData));
    32.         }
    33.  
    34.         /// <summary>
    35.         /// Creates a spawn request for the spawn system to pick up
    36.         /// </summary>
    37.         /// <param name="manager"></param>
    38.         /// <param name="position"></param>
    39.         /// <param name="rotation"></param>
    40.         public static void Spawn<T>(this EntityManager manager, float3 position, quaternion rotation) where T : IComponentData{
    41.             Entity request = manager.CreateEntity(SpawnRequest);
    42.             ComponentType tag = ComponentType.Create<T>();
    43.    
    44.             manager.SetComponentData(request, new SpawnRequestData {
    45.                                                                        Position = position,
    46.                                                                        Rotation = rotation,
    47.                                                                        EntityTag = tag
    48.                                                                    });
    49.         }
    50.  
    51.         // TODO add an extension variant for the EntityCommandBuffer
    52.     }
    A tag is PlayerData:
    Code (CSharp):
    1. [Serializable]
    2. public struct PlayerData : IComponentData {
    3. }
    And the actual spawn call:
    Code (CSharp):
    1. EntityManager.Spawn<PlayerData>(position, rotation);
    Please point me in the right direction on:
    - How to query components based on the types
    - How to use EntityCommandBuffer.Instantiate() (where can I get job index?)
    - Is it possible to search through received query of entities? E.g. if I'd want to add the prefab variants, will it be possible to fetch <Prefab, EntityTag> and then search through them to find specific variant by id?

    Also, architecture suggestions would be really appreciated.
     
    Last edited: Jan 24, 2019
  4. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Right direction wrong solution!

    First of all try to not generalize your stuff by generic calls...
    You should create one system for each type.
    In the later process you can perhaps try to generalize your stuff. (Keep in mind that generic systems needs to be manually added!)

    Next don't instantiate immediately. Use a NativeList or NativeQueue to temporaly store your spawn infos there. Process the entity spawns all together in the update loop. This has the advantage that you can take use of the batch api and you don't cancle or interrupt running jobs each time you add an entity by calling Spawn(...)!

    Code (CSharp):
    1. var myEntities = new NativeArray<Entity>(list.Length, Allocate.Temp);
    2. EntityManager.Create(myArchetype, myEntities);
    3. //do init stuff
     
    xVergilx likes this.
  5. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    It's not generic system, it's a pretty hack to avoid another parameter (ComponentType) :)
    This is used to pass main entity component, and should be treated as a tag.
    Though, it may look as a generic one (I might constrain it to something else, like an ITag : IComponentData instead).

    The idea is to make a single system that handles only spawning.
    E.g. handling creation of entity from the template and settings it's position / rotation.


    Moreover, I'm not going to use the .Spawn() directly from anywhere.
    System will consume an entity "request".
    So that should be ECS-ish?

    About interruptions, shouldn't EntityCommandBuffer used for this case?
    E.g. for queueing creation of entities for example at the end of frame barrier?


    The actual problems I'm encountering right now are coming from lack of manual.
    Some of the basic features are just hard to grasp without it.

    Main issue right now is that I'm not quite sure how to properly fetch entities that have specific components from the EntityManager within JobComponentSystem.
    This is in addition to injecting an actual SpawnRequestData that should be processed.

    ComponentQuery / ComponentGroup comes to mind, but I haven't figured out how to find an actual, whole entities.

    Fresh morning look might help me solve this.
    I really appreciate the help though, thanks!
     
    Last edited: Jan 24, 2019
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I am a little lost to as what you want to achieve. I have not looked into this, but I almost have the feeling that you are interested in something that is already part of p23?
    • GameObjectConversionSystem & GameObjectConversionUtility can be used to perform conversion of existing GameObject scenes into entity representation.
     
    xVergilx likes this.
  7. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Not quite, those are used to convert gameobjects to components. But I've already got this covered by EntityManager.Instantiate. As for optimization purposes, it might be useful.

    What I'm trying to do is quite simple:

    SomeSystem -- Sends SpawnRequestData --> SpawnSystem -- Processes Request --> Spawns Entity.

    Although that might take two frames to spawn an entity, instead of one when using EntityCommandBuffer.

    Also, seems like making a NativeArray<Entity> for the prefab entities might be a good idea.
    This will prevent running through all entities to find out which entity is a prefab.
     
  8. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    have you looked at the entity event system from @tertle ?
     
  9. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Nope, do you have a link?
     
  10. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    xVergilx likes this.
  11. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Looks cool, but solves only half of the problem. I need to instantiate entities somehow from prefabs that are persistent, and live more than 1 frame.
     
  12. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    I can't figure out how to get a jobIndex for the EntityCommandBuffer.Concurrent. And I can't find a proper example on where to fetch it.

    This is what I'm trying to do (brute force solution):
    Code (CSharp):
    1. public class SpawnSystem : JobComponentSystem {
    2.         private EntityManager _entityManager;
    3.         private EntityCommandBuffer.Concurrent _buffer;
    4.  
    5.         private EndFrameBarrier _endFrameBarrier;
    6.  
    7.         [BurstCompile]
    8.         struct SpawnJob : IJobProcessComponentData<SpawnRequestData> {
    9.             public EntityManager EntityManager;
    10.             public EntityCommandBuffer.Concurrent Buffer;
    11.             public NativeArray<Entity> Prefabs;
    12.          
    13.             public void Execute(ref SpawnRequestData data) {
    14.                 foreach (Entity entity in Prefabs) {
    15.                     NativeArray<ComponentType> types = EntityManager.GetComponentTypes(entity);
    16.  
    17.                     foreach (ComponentType type in types) {
    18.                         if (type == data.EntityTag) {
    19.                             Buffer.Instantiate(???, entity);
    20.                             return;
    21.                         }
    22.                     }
    23.                 }
    24.             }
    25.         }
    26.  
    27.         protected override JobHandle OnUpdate(JobHandle inputDeps) {
    28.             SpawnJob spawnJob = new SpawnJob {
    29.                                                  EntityManager = _entityManager,
    30.                                                  Prefabs = SpawnSystemExt.Prefabs, // Static NativeArray<Entity>
    31.                                                  Buffer = _endFrameBarrier.CreateCommandBuffer()
    32.                                                                           .ToConcurrent()
    33.                                              };
    34.  
    35.             JobHandle spawnHandle = spawnJob.Schedule(this, inputDeps);
    36.             return spawnHandle;
    37.         }
    38.  
    39.         protected override void OnCreateManager() {
    40.             World world = World.Active;
    41.          
    42.             _entityManager = world.GetOrCreateManager<EntityManager>();
    43.             _endFrameBarrier = world.GetOrCreateManager<EndFrameBarrier>();
    44.         }
    45.     }
     
  13. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Use IJobProcessComponentDataWithEntity
    Will give you an index to use

    Or use the NativeSetThreadIndex attribute
    Code (CSharp):
    1. [NativeSetThreadIndex]
    2. public int ThreadIndex;
     
    xVergilx likes this.
  14. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Ok, you can also store ComponentType in the sturct that is queued. (No need to create an entity directly)

    Basically yes, but they have a bad performance if you spawn thousends of entities each time. Thats why it's better to use the batch api!

    You "can't" access all components of an specific entity in an oop way...
    You can but it's not what you should do...

    EDIT:
    For jobs you can also use GetComponentDataFromEntity<>()


    If you need to init some special components than you could create a reactive system for that.
    E.g. add a SystemComponent to that entitity which tells a system that it needs to be init.

    This can also be done in the other direction -> ComponentType.Substractive<>()
    Or by creating event entities...
    (Many roads lead to Rome)


    Maybe you can explane more what your specific usecase/problem is. Maybe we can find a good solution for you!
     
    xVergilx likes this.
  15. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Almost there.

    I've rewritten the lookup to the prefab to the NativeMultiHashMap<int, int> where key is prefab index in NativeArray<Entity> prefab lookup. And values are it's components pre-fetched in bootstrap phase converted to TypeIndex. (I hope they aren't changed through runtime tho!)

    Entity is found correctly and even spawned. One issue is that EntityCommandBuffer is not disposed correctly:
    Here's a current version
    Code (CSharp):
    1. public class SpawnSystem : JobComponentSystem {
    2.         private EndFrameBarrier _endFrameBarrier;
    3.  
    4.         [BurstCompile]
    5.         struct SpawnJob : IJobProcessComponentData<SpawnRequestData> {
    6.             [WriteOnly]
    7.             public EntityCommandBuffer.Concurrent Buffer;
    8.      
    9.             [ReadOnly]
    10.             public NativeArray<Entity> Prefabs;
    11.      
    12.             [ReadOnly]
    13.             public NativeMultiHashMap<int, int> PrefabComponents;
    14.  
    15.             [NativeSetThreadIndex]
    16.             private int _threadIndex;
    17.      
    18.             public void Execute(ref SpawnRequestData data) {
    19.                 int tagIndex = data.EntityTagTypeIndex;
    20.          
    21.                 for (int i = 0; i < Prefabs.Length; i++) {
    22.                     // Check if type of the component is the same as the type of the request component type
    23.                     // Equals to if the entity has component of type
    24.  
    25.                     if (!PrefabComponents.TryGetFirstValue(i, out int firstIndex, out var iterator)) continue;
    26.  
    27.                     // Complete condition
    28.                     if (CompareIndex(firstIndex, tagIndex, i)) return;
    29.  
    30.                     while (PrefabComponents.TryGetNextValue(out int typeIndex, ref iterator)) {
    31.                         // Complete condition
    32.                         if (CompareIndex(typeIndex, tagIndex, i)) return;
    33.                     }
    34.                 }
    35.             }
    36.  
    37.             private bool CompareIndex(int typeIndex, int tagIndex, int prefabIndex) {
    38.                 if (typeIndex != tagIndex) return false;
    39.          
    40.                 Buffer.Instantiate(_threadIndex, Prefabs[prefabIndex]);
    41.                 return true;
    42.             }
    43.         }
    44.  
    45.         protected override JobHandle OnUpdate(JobHandle inputDeps) {
    46.             SpawnJob spawnJob = new SpawnJob {
    47.                                                  Prefabs = SpawnSystemExt.Prefabs,
    48.                                                  PrefabComponents = SpawnSystemExt.PrefabComponents,
    49.                                                  Buffer = _endFrameBarrier.CreateCommandBuffer()
    50.                                                                           .ToConcurrent()
    51.                                              };
    52.  
    53.             JobHandle spawnHandle = spawnJob.Schedule(this, inputDeps);
    54.             return spawnHandle;
    55.         }
    How do I "complete" the handle? Do I need to store the <jobHandle, spawnJob> and then iterate over their state in OnUpdate?

    As I understood, I could use NativeQueue<SpawnRequestData> and iterate over it? In theory it should not make a difference. Although I was thinking about using an EntityEventSystem to push SpawnRequestData instead of spawning requests from inside systems.

    It's not implemented yet, as I'm trying to solve one problem at a time.


    Usecase - Spawn a specific Entity Prefab based on the IComponentData tag / additional data (e.g. variation), without handling with an actual entity prefab (event/reactive manner) from inside of that System / Data.

    A reason behind this: to avoid writing a ton of spawn systems for each projectile, player, enemy, or any other entity template / archetype that may be in-game and automatically fill them with proper data from the prefab data. (Possibly setting Position / Rotation if possible)
     
    Last edited: Jan 25, 2019
  16. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Ok. I've figured out what was missing:
    I haven't added the job handle to the barrier system.
    Code (CSharp):
    1. protected override JobHandle OnUpdate(JobHandle inputDeps) {
    2.             SpawnJob spawnJob = new SpawnJob {
    3.                                                  Prefabs = SpawnSystemExt.Prefabs,
    4.                                                  PrefabComponents = SpawnSystemExt.PrefabComponents,
    5.                                                  Buffer = _endFrameBarrier.CreateCommandBuffer().ToConcurrent()
    6.                                              };
    7.             JobHandle handle = spawnJob.Schedule(this, inputDeps);
    8.             _endFrameBarrier.AddJobHandleForProducer(handle);
    9.  
    10.             return handle;
    11.         }
     
    firatcetiner likes this.