Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Simple ECS instantiation

Discussion in 'Entity Component System' started by laurentlavigne, Dec 31, 2019.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,994
    I'd like to spawn N objects, N is a variable shown in the inspector and the objects are also variables in the inspectors
    In mono I'd do this very short code:
    Code (CSharp):
    1. public class Instantiator : MonoBehaviour
    2. {
    3.     public int howMany = 10000;
    4.     public GameObject conveyorMiddle;
    5.  
    6.     private void Start()
    7.     {
    8.         for (int i = 0; i < howMany; i++)
    9.         {
    10.             Instantiate(conveyorMiddle, UnityEngine.Random.insideUnitSphere * 100, UnityEngine.Random.rotation);
    11.         }
    12.     }
    13. }
    14.  
    What's the fastest way to do that in ECS?
     
  2. elcionap

    elcionap

    Joined:
    Jan 11, 2016
    Posts:
    138
    Not the fastest (for coding or performance) but I think it's a goot start point:

    Code (CSharp):
    1.  
    2. // The generator for authoring components doesn't do this conversion (GameObject to Entity) automagically
    3. public class InstantiatorAuthoring : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity {
    4.     public GameObject Prefab;
    5.     public int Count;
    6.  
    7.     public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs) {
    8.         // This will register the prefab to be converted too
    9.         referencedPrefabs.Add(Prefab);
    10.     }
    11.  
    12.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) {
    13.         dstManager.AddComponentData(entity, new Instantiator {
    14.             Prefab = conversionSystem.GetPrimaryEntity(Prefab),
    15.             Count = Count
    16.         });
    17.     }
    18. }
    19.  
    20. public struct Instantiator : IComponentData {
    21.     public Entity Prefab;
    22.     public int Count;
    23. }
    24.  
    25. [AlwaysSynchronizeSystem]
    26. public class InstantiatorSystem : JobComponentSystem {
    27.     EntityQuery m_Query;
    28.  
    29.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    30.         // Unity.Mathematics.Random
    31.         // Seed cannot be 0 (zero).
    32.         var random = new Random((uint)Environment.TickCount);
    33.         var entityList = new NativeList(Allocator.TempJob);
    34.      
    35.         Entities
    36.             .WithStoreEntityQueryInField(ref m_Query)
    37.             .ForEach((Instantiator instantiator) => {
    38.                 // This will set the Length and resize the internal array if needed
    39.                 entityList.ResizeUninitialized(instantiator.Count);
    40.              
    41.                 EntityManager.Instantiate(instantiator.Prefab, entityList);
    42.              
    43.                 for (var entityIndex = 0; entityIndex < entityList.Length; entityIndex++) {
    44.                     var entity = entityList[entityIndex];
    45.                  
    46.                     EntityManager.SetComponentData(entity, new Translation {
    47.                         Value = random.NextFloat3Direction() * random.NextFloat(100)
    48.                     });
    49.                  
    50.                     EntityManager.SetComponentData(entity, new Rotation {
    51.                         Value = random.NextQuaternionRotation()
    52.                     });
    53.                 }
    54.              
    55.             })
    56.             .WithStructuralChanges()
    57.             .WithoutBurst()
    58.             .Run();
    59.      
    60.         entityList.Dispose();
    61.      
    62.         // That's a batch operation for all entities captures in the query and scale really well.
    63.         EntityManager.DestroyEntity(m_Query);
    64.      
    65.         return inputDeps;
    66.     }
    67. }
    68.  
    With this you can create a GameObject in the scene, attach InstantiatorAuthoring and ConvertToEntity (if outside of a SubScene), set the count and reference the prefab and will work.

    Actually you could create all the entities and initialize the values in a job but this will require to pre create enough seeds to be used late the in the jobs.

    As you can see EntityManager.Instantiate (CreateEntity also) can receive a NativeArray (NativeList was cast as NativeArray in this case) and will create (batch) enough entities to fill the array.

    I could've used the count inside the Instantiate adding the allocator too which would return a NativeArray but that will means unnecessary allocations for every item in the ForEach.

    []'s
     
    NotaNaN likes this.
  3. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    288
    @elcionap

    Can you talk a little bit about the workings of "WithStructuralChanges()", ".WithStoreEntityQueryInField(ref m_Query)"
    and the use of the code below? Thank you!

    Code (CSharp):
    1. // That's a batch operation for all entities captures in the query and scale really well.
    2.         EntityManager.DestroyEntity(m_Query);
     
    Last edited: Jan 1, 2020
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,994
    Thanks but it's not creating instances, with or without ConverToEntity on the prefab.
    There is a new thing for conversion, try this:
    Code (CSharp):
    1. [GenerateAuthoringComponent]
    2. public struct Instantiator : IComponentData
    3. {
    4.     public Entity refab;
    5.     public int count;
    6. }
     
  5. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,994
    So this wasn't working because I forgot to add a ConvertToEntity component to the gameobject that the IComponentData is on. I think when [GenerateAuthoringComponent] is there it should require a convertion to entity, don't you think?
     
  6. elcionap

    elcionap

    Joined:
    Jan 11, 2016
    Posts:
    138
    If you need to use EntityManager you need to use WithStructuralChanges to inform that you need the code running in the main thread and without burst. This will force a sync point but some operations (batch) are faster than using EntityCommandBuffer.

    When using a ForEach, internally, an EntityQuery related to your system will be created and used to extract the data. WithStoreEntityQueryInField will point the query to your ref variable so you can use the same query for other things.

    EntityManager.DestroyEntity (IINM EntityCommandBuffer now also have a DestroyEntity with an EntityQuery parameter) will destroy all the chunks that match with the query. This will skip things like safe checks per entity and swap memory for entities in the middle of the chunks. This is really fast but have some problems (at least before the 0.2, haven't tested this recently) with ISystemState variants.
    For example there is a LinkedEntityGroup dynamic buffer (ISystemStateElementData). When an Entity with this dynamic buffer is destroyed all the entities in this buffer will be destroyed together. But this didn't work when using DestroyEntity with a EntityQuery.

    Batching create/destroy entities are useful for things like creating/destroying one frame events/messages. You can create chunks (CreateChunks) with a defined entity count (this can lead to chunk fragmentation if you don't know what you are doing). For a high number of events per frame with a better scalability @tertle has a better solution (IMHO) here https://forum.unity.com/threads/event-system.779711/.

    I'm not sure if I made me clear in all or any topic or you are asking more about use cases but fell free to ask anything else.

    []'s
     
    laurentlavigne likes this.