
Unity ECS is not all about high performance subset of C# and struct works. Knowing about how it deals with reference types can help you assemble a complete system where you need it.
The forbidden land object[]

There is an object[]
deep in a class called ArchetypeManager
, which your EntityManager
owns it. Also this object[]
is not just one, but an array of object[]
( ManagedArrayStorage[]
)
That means, each World get one unique ManagedArrayStorage[]
array for use because each World
got its own EntityManager
. How many object[]
we get in a world? One per chunk.
(One other place, is a List<object>
for housing ISharedComponentData
. This is one linear, big, and long object
list where anything goes. On the upside, we could represent any item of any kind with a single int
as an index.)
GameObjectEntity’s magical “MonoBehaviour wrapping” ability
You might have observe this “magical” behaviour from GameObjectEntity
, where instantly it seems to be able to “wrap” even MonoBehaviour
together with ComponentDataProxy
and SharedComponentDataProxy
, to an Entity
. How can this be? Isn’t a chunk could contain only blittable type? The answer is those MonoBehaviour
all goes into this object[]
as a reference type.
EntityManager.SetComponentObject
You don't have to rely on that GameObjectEntity
however, it is thanks to this method that we can add ANY object to Entity
! (That could be qualified as a ComponentType
) But how it is stored is not in a chunk, again, they are kept together in one of object[]
in ManagedArrayStorage[]
, in that ArchetypeManager
.
Game object conversion
On using the conversion API, somehow you would also get the MonoBehaviour
currently active on the GameObject
together with the conversion product. It is again the work of storing those as reference type.
http://68.183.97.180/gameobject-conversion-ecosystem-code-tour/
Sneaky int
fields in a Chunk

The ECS docs might give an idea that chunk is a tightly packed, well defined memory idea thanks to how the layout from IComponentData
assembled into an archetype, is known beforehand.
But there are 2 gateways to the forbidden lands built-in, that is ManagedArrayIndex
which maps to one object[]
which is not really a data in this chunk. This index is just one int
. It indexes into ManagedArrayStorage[]
which holds many object[]
. That object[]
is for this one chunk.
And also SharedComponentValueArray
, which maps to List<object>
for each type of ISharedComponentData
that happen to be associated with this chunk. It is not really a data in this chunk. The index is used directly as an index to that List<object>
.
Defining “archetype” again?
From ECS document you might assume that an archetype is a combination of IComponentData
and ISharedComponentData
types, but in reality, it is a set of ComponentType
.
The ECS data type ComponentType
is not limited to IComponentData
and ISharedComponentData
derived type. It can be any type including your MonoBehaviour
type! Actually, it is looking for anything derived from Component
type. e.g. ComponentType.Create<Transform>()
can be added to an Entity
. And that's the start of "hybrid" ECS since ECS can now move normal Unity things via them.
How ManagedArrayStorage[] works
Being not a List<object>
like the storage for ISharedComponentData
, there must be some kind of strategy to getting the data out and expanding the storage that is not as simple as just adding and using fixed index.
Each chunk get its own object[]
, legth equal to no. of CLASS type in chunk’s archetype x possible amount of entity
The object[]
will be added to ManagedArrayStorage[]
with this strategy
- Search for
null
hole, if found we allocate newobject[]
- If not found, we x2 the length of
ManagedArrayStorage[]
then surely a newnull
hole will appear. ManagedArrayStorage[]
starts from length 1.
Allocate object[]
for how large? Enough for each entity to get one!
Supposed that, we got 3 class type : Transform
, RigidBody
, LineRenderer
and several other IComponentData
which take considerable space. Each entity is now sized 1.6kB. Because one chunk is currently fixed at 16kB, the chunk “capacity” is 10.
In that case, we allocate new object[30]
so that each Entity
can hold one of each class type. So.. the space implication is pointer size * capacity * amount of class types
.
Then it is not wise to blindly add so many class type component to an archetype without planning to use them.
Getting that data

Now it is simple, we just get the object[]
of that chunk (each chunk got its own) then multiply jump according to entity index in this chunk, then addition move to the correct space for this class type. Each class type simply get its own offset 0, 1, 2, …
null
as removal
When entities are destroyed, there is a chance that a chunk will become empty. When it does, its personal object[]
array will be null
. Then GC can collect the pointers, and also the allocation routine can find a new null
hole. It is just that the x2'ed ManagedArrayStorage[]
is never shrink down.
The way OUT
You could use
- EntityManager -
public T GetComponentObject<T>(Entity entity)
- EntityQuery -
public static T[] ToComponentArray<T>(this EntityQuery group) where T : Component
The EntityManager
one is for single Entity
. For the whole query (remember you can put Component
derived type into the EntityQuery
query) ToComponentArray
join forces with other To__
method, the noticable difference is that the returned array is really a normal C# array, not NativeArray
. So the clean up job is left to the garbage collector as usual.
VS ISharedComponentData
Imagine this scenario
GameObjectEntity
+ComponentDataProxy<A>
+SharedComponentDataProxy<B>
GameObjectEntity
+ComponentDataProxy<A>
+C : MonoBehaviour
The point is I want an entity to hold A and some non blittable reference fields that could not go into A . B and C is equivalent in content. I have 100 of this kind of game object, content of B and C all unique.
Using SharedComponentDataProxy<B>
which go into List<object>
One chunk can hold one index to each type of shared component data. I mentioned that each B is different, so no entity can be together in the same chunk.
I got 100 chunks, each chunk containing only 1 Entity! All off these 100 chunks has an exactly same archetype but because of different shared index they are separated. I lose 16kB * 100 = 1.6MB memory instantly. Chunk iteration is slow as it jumps around for long distance per Entity.
Note that it looks bad because I am breaking the definition of ISharedComponentData
, “shared”. Each of my SharedComponentDataProxy
is not really shared with anyone else, only for that one entity. And so this usage is not appropriate.
But I do need reference types to kind of go with my entity in some way. What could be the solution?
Using MonoBehaviour
which go into object[]
In this case I get only 1 chunk with 100 entities happily staying together (if it is small enough). A chunk get its own object[]
, which is made for each entity. Each entity then can be chunk iterated faster, then get its object as needed from EntityManager
.