An approach to convert a data collection class to ECS

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.

public class IntCollection
{
    private List<int> ints = new List<int>();
    public IntCollection() { }

    public void Add(int i)
    {
        ints.Add(i);
    }

    public int Sum()
    {
        int sum = 0;
        for(int i = 0; i < ints.Count; i++)
        {
            sum += ints[i];
        }
        return sum;
    }
}

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 :

public struct IntCollectionData : IComponentData
{
    public int[] ints;
}

public class IntCollectionECS
{
    private Entity entityToData;
    private static EntityArchetype archetype;
    public IntCollectionECS()
    {
        EntityManager em = World.Active.GetOrCreateManager<EntityManager>();
        if(archetype.Valid == false)
        {
            em.CreateArchetype(typeof(IntCollectionData));
        }
        entityToData = em.CreateEntity(archetype);
    }

    public void Add(int toAdd)
    {
        EntityManager em = World.Active.GetOrCreateManager<EntityManager>();
        IntCollectionData icd = em.GetComponentData<IntCollectionData>(entityToData);
        int[] largerArray = new int[icd.ints.Length+1];
        for(int i = 0; i < icd.ints.Length; i++)
        {
            largerArray[i] = icd.ints[i];
        }
        largerArray[largerArray.Length-1] = toAdd;
        icd.ints = largerArray;
        //Set data back after enlarging it with a new data.
        em.SetComponentData<IntCollectionData>(entityToData, icd);
    }

    public int Sum()
    {
        EntityManager em = World.Active.GetOrCreateManager<EntityManager>();
        IntCollectionData icd = em.GetComponentData<IntCollectionData>(entityToData);
        int sum = 0;
        for(int i = 0; i < icd.ints.Length; i++)
        {
            sum += icd.ints[i];
        }
        return sum;
    }
}

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

ArgumentException: IntCollectionData is an IComponentData, and thus must be blittable (No managed object is allowed on the struct).
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)

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).

Ok, so what is some typical approaches to express that a single Entity is owning a varying number of data?

reply up.

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.

You can add a FixedArray to an entity using the below.

EntityManager.AddComponent(entity, ComponentType.FixedArray(typeof(int), 10)
1 Like

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.

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.

Why not make every int as a component?