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

An approach to convert a data collection class to ECS

Discussion in 'Entity Component System' started by 5argon, May 13, 2018.

  1. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    This is a simple example, I have a class which I used to `new` and use it to collect data to do things in OOP style.

    Code (CSharp):
    1. public class IntCollection
    2. {
    3.     private List<int> ints = new List<int>();
    4.     public IntCollection() { }
    5.  
    6.     public void Add(int i)
    7.     {
    8.         ints.Add(i);
    9.     }
    10.  
    11.     public int Sum()
    12.     {
    13.         int sum = 0;
    14.         for(int i = 0; i < ints.Count; i++)
    15.         {
    16.             sum += ints[i];
    17.         }
    18.         return sum;
    19.     }
    20. }
    I want to try out C# jobs and ECS on this class and then I can speed up the `Sum()` method by doing it as a job, for example. But to keep the existing user of this class use the same outward interface as before (new the class, .Add and then .Sum it should just works like before) my idea is to do ECS things inside the class like this :

    Code (CSharp):
    1. public struct IntCollectionData : IComponentData
    2. {
    3.     public int[] ints;
    4. }
    5.  
    6. public class IntCollectionECS
    7. {
    8.     private Entity entityToData;
    9.     private static EntityArchetype archetype;
    10.     public IntCollectionECS()
    11.     {
    12.         EntityManager em = World.Active.GetOrCreateManager<EntityManager>();
    13.         if(archetype.Valid == false)
    14.         {
    15.             em.CreateArchetype(typeof(IntCollectionData));
    16.         }
    17.         entityToData = em.CreateEntity(archetype);
    18.     }
    19.  
    20.     public void Add(int toAdd)
    21.     {
    22.         EntityManager em = World.Active.GetOrCreateManager<EntityManager>();
    23.         IntCollectionData icd = em.GetComponentData<IntCollectionData>(entityToData);
    24.         int[] largerArray = new int[icd.ints.Length+1];
    25.         for(int i = 0; i < icd.ints.Length; i++)
    26.         {
    27.             largerArray[i] = icd.ints[i];
    28.         }
    29.         largerArray[largerArray.Length-1] = toAdd;
    30.         icd.ints = largerArray;
    31.         //Set data back after enlarging it with a new data.
    32.         em.SetComponentData<IntCollectionData>(entityToData, icd);
    33.     }
    34.  
    35.     public int Sum()
    36.     {
    37.         EntityManager em = World.Active.GetOrCreateManager<EntityManager>();
    38.         IntCollectionData icd = em.GetComponentData<IntCollectionData>(entityToData);
    39.         int sum = 0;
    40.         for(int i = 0; i < icd.ints.Length; i++)
    41.         {
    42.             sum += icd.ints[i];
    43.         }
    44.         return sum;
    45.     }
    46. }
    So the constructor will create an archetype if it is the first time. And when `new`-ing the class it would tell the entity manager to create a new entity. After this, the `Add` method would request the data from entity manager to modify and save back. The `Sum` method would do roughly the same.

    However I got an error

    Code (csharp):
    1. ArgumentException: IntCollectionData is an IComponentData, and thus must be blittable (No managed object is allowed on the struct).
    2. Unity.Entities.TypeManager.BuildComponentType (System.Type type)
    Suggesting that array is not allowed on the IComponentData? How can I approach building an entity with an array of data belong to it?

    (Of course in my game `IntCollectionData` is more complicated, and the equivalent of `Add` do much more thing that would benefit if I have all the data separated as a struct, arranged neatly by the ECS system, and allowing quick iteration + burst compiler. The old class has all the data together with its logic)
     
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    Array of structs is reference type - int[]. Therefore you can't use this in IComponentData. You can use native containers with persistent allocation (for manual deallocating at the need time).
     
    Last edited: May 13, 2018
  3. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Ok, so what is some typical approaches to express that a single Entity is owning a varying number of data?
     
  4. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    reply up.
     
  5. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Did you mean using `NativeArray<int>` instead of an array inside the `IComponentData`? The same error came up when I was trying it.

    From Unity's example I get that `NativeArray` was used mainly with `ComponentDataArray` which comes from [Inject]. The problem is the ECS way is to view each item in this `ComponentDataArray` at a particular index belonging to one entity (maybe coming up as `EntityArray`) in the same index. I get the idea of using `NativeArray` at the "need" time, but in "stored" time how could I instruct ECS to keep multiple values together?

    One Entity could have many different Component that we are looking for but inside those component still contains limited amount of data. (again, because array is not allowed in the IComponentData) This is the source of my confusion. If this class could handle up to 5 ints only, I would have no problem making a component with exactly 5 ints.
     
    Last edited: May 13, 2018
  6. xXPancakeXx

    xXPancakeXx

    Joined:
    Mar 14, 2015
    Posts:
    55
    You can add a FixedArray to an entity using the below.

    Code (CSharp):
    1. EntityManager.AddComponent(entity, ComponentType.FixedArray(typeof(int), 10)
     
    Djayp likes this.
  7. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Ah I see, so a different length of data will become a new type of component. From my understanding, it seems that this will likely defeat the speed advantage of ECS if many of my Entity all have different type of component. Data would not be continuous since they are nothing alike structs can't be grouped efficiently, hence the performance might be similar to class based (or worst, if struct copying cost is too much)

    I do think that it smells like it would not go well with ECS. I will just jobify the class instead without using ECS now that at least the data has been separated into a struct (inside a class). Thank you.
     
  8. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    448
    Because of how components are stored components need to have constant size in memory.

    You can have component that stores a link to other entity and another that stores int value.
     
  9. vanxining

    vanxining

    Joined:
    Mar 29, 2018
    Posts:
    20
    Why not make every int as a component?