In this blog, I will discuss how I am creating the ECS system that we talked about in my previous blog. I will discuss the design approach, progress & next steps.
Design Approach
There are many ways an ECS system can be designed I will discuss two designs that I am familiar with and explain why I choose one over another.
Design Choice 1:
As now we are familiar with ECS, we know that ECS follows composition over inheritance principle. So, we would need some kind of representation for a Component that can be used to attach to entities to add some behavior or data. So we create an abstract Component class with some methods that every component would need to implement
class Component
{
public:
virtual void init() = 0;
virtual void update()=0;
virtual ~Component() {}
private:
};
This abstract class will be used to create other components by inheriting from this class.
Similarly, we will create an Entity class and Manager class with other methods like Get, Add, Remove apart from init & update. The Entity class is like a container for all components that an object can have and the Manager class is a container for all entities in a Level/Scene.
That's all the classes you would need. You create entities, add components to them, and add entities to a Manager. A Manager initializes and Updates all entities similarly an Entity initializes and updates all components.
Design Choice 2:
This is the design that we discussed in the previous blog. In this design, the user has to create a struct for a component representation as per his design. Once the components are created using the interface provided add, get & remove operations can be done. So how do we store various components and entities? We use storage pools to store all the components. Each component type gets its own storage pool and we can access the pools based on the component type. For example, all components of type TagComoponent are stored in one pool. In this method Entity is just a unique identifier in the future this will be abstracted but essentially it is just a number. We store the index of the component in a pool along with the Entity id so that we can use it to retrieve the component information for a corresponding Entity.
I am using design 2 for my ECS system. The reason for choosing design two is it gives more freedom for the user. Let's see an example If I want to update only the Transform Data for all the entities in a game it is not possible with the first design. You have to update the manager to update all the entities which will update all the components. You can have some complex conditions to get it done but it is very difficult. In the second method, it is very simple the interface provides access to the pool of Transform Component and it can be updated as per the user's wish. One other performance improvement that design 2 has is that when we use a pool and update all the data by iterating through it there will be fewer cache misses as we are iterating over a continuous block of an array of components. In design 1 this is the opposite, every time you update there will be more indirections causing more cache misses.
Progress
In the past week, I spend time on the design of the ECS system. How to create storage pools and how to store entity information to retrieve component data for the corresponding entity.
I created the static library project for my ECS system and set up all the properties using property sheets. Currently, it is designed to be a header-only library which means all you would need is a header file to use the ECS in a project.
I created classes for Registry which is a container for all the Storage Pools. You add components through Registry. I also created the Storage class which is an abstract class and base class for all ComponentStorage pools. I created a templated ComponentStorage class which inherits from the Storage class and is the actual Storage pool class for each component. Currently following are the methods are that are available in the interface for Registry.
Add - Adds a component
Get - returns Component pointer if found or else returns nullptr
Remove - Removes a component
GetAll - Gets all instances of a component.
Next Steps:
I will be expanding the interface by adding more useful methods and start abstracting an entity class which will basically contain all the methods of registry so that a user can use just an entity to update the data.
Ex: Entity->AddComponent()
Once I am done with the system, I will try to use it in my current game and test it properly.
I will spend time finding and fixing any bugs. I will also be working on the human-readable file format for the ECS.
In the later week, I will be working on creating builder projects to build the binary files using the human-readable files. But I will be testing thoroughly with existing data first so that there aren't any bugs when using the binary files.
Comments