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.

Next
Next

Unity2D: Creating a Radar Graph