Unity: Object Pooling in ECS
The DOTS system, including the Unity Entity Component System (ECS) and the Burst compiler, provides a more performant way to handle and manipulate large amounts of data. However, good ole’ object pooling can still provide additional performance benefits by reducing the overhead of creating and destroying objects during runtime.
In a DOTS-based project, object pooling can be implemented using the same basic principles as traditional object pooling, but with some differences in the implementation. For example, you may use the new ECS architecture to manage the pool of objects, or make use of the Burst compiler for improved performance.
Therefore, object pooling is still a relevant technique for optimizing performance in Unity, including in DOTS-based projects.
Let’s take a look at how we might create a script that can help us pool our objects in ECS.
using Unity.Entities;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
public class ObjectPool : MonoBehaviour
{
// Prefab to use for the objects in the pool
public GameObject prefab;
// Size of the pool
public int poolSize = 10;
// Array to store the entities in the pool
private Entity[] pool;
// EntityManager to manage the entities
private EntityManager entityManager;
private void Start()
{
// Get the EntityManager from the default world
entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
// Initialize the pool array
pool = new Entity[poolSize];
// Create the specified number of entities and add them to the pool
for (int i = 0; i < poolSize; i++)
{
// Create a new entity
Entity entity = entityManager.CreateEntity(typeof(PoolableComponent));
// Add a PoolableComponent to the entity and set it to inactive
entityManager.SetComponentData(entity, new PoolableComponent
{
isActive = false
});
// Add the prefab as a GameObjectComponent to the entity
entityManager.AddComponentObject(entity, prefab);
// Add the entity to the pool
pool[i] = entity;
}
}
// Method to get an inactive entity from the pool
public Entity GetObject()
{
// Loop through the pool to find an inactive entity
for (int i = 0; i < poolSize; i++)
{
Entity entity = pool[i];
PoolableComponent poolable = entityManager.GetComponentData<PoolableComponent>(entity);
if (!poolable.isActive)
{
// If an inactive entity is found, set it to active and return it
entityManager.SetComponentData(entity, new PoolableComponent
{
isActive = true
});
return entity;
}
}
// Return null if there are no inactive entities
return Entity.Null;
}
// Method to return an entity to the pool
public void ReturnObject(Entity entity)
{
// Set the entity's PoolableComponent to inactive
entityManager.SetComponentData(entity, new PoolableComponent
{
isActive = false
});
}
}
// ComponentData to track the active state of an entity
public struct PoolableComponent : IComponentData
{
public bool isActive;
}
This might be a lot to look at, but let’s break it down.
The Start method initializes our pool, and loads it with new Entities mapped with our given prefab.
You’ll notice the use of the EntityManager
, this is a class in Unity's Entity Component System (ECS) that provides a centralized way of creating, destroying, and manipulating entities and their components. It acts as the main interface between the code and the data stored in entities and components, allowing us to create, modify, and query entities and their components.
Additionally, we have the GetObject method, the method loops through the pool array, which contains all the entities in the pool. For each entity in the pool, it retrieves its PoolableComponent
using the entityManager
and checks if its isActive
field is set to false
.
If it finds an inactive entity, it sets its PoolableComponent
's isActive
field to true using the entityManager
, and returns the entity.
If it doesn't find any inactive entities, it returns Entity.Null
to indicate that there are no available entities in the pool.
We call this whenever an entity is needed, and it returns an inactive entity from the pool that can be used for that purpose. The method ensures that the pool is used efficiently by only returning entities that are not currently in use, and it helps to avoid the overhead of creating and destroying entities frequently, which can impact performance considerably if you are in fact, spawning a lot of assets.
Finally, our ReturnObject method, which takes an Entity parameter that will be returned to the pool, so it retrieves the PoolableComponent
of the entity using the entityManager
and sets its isActive
field to false
.
The method is called when an entity is no longer needed and is being returned to the pool for reuse. This helps to ensure that the pool is used efficiently, as inactive entities can be easily reused, and new entities don't need to be created frequently, which can impact performance as mentioned throughout this post.
Reflections
In general, the script is well-structured and easy to understand, making it a good starting point for those who are new to object pooling and ECS in Unity. However, it is a basic implementation and could be improved in various ways. For example, it could be made more flexible by allowing the pool to contain entities of different types, or by adding options to configure the size of the pool and the initial set of entities that are created.
The ObjectPool
script could be a useful tool for you managing a pool of entities in Unity's ECS, and it serves as a good starting point for more advanced implementations of object pooling in Unity, in my opinion.