This library provides a simple Entity Component System (ECS) for C++ projects. The library is built using Premake5 for C++23.
This library depends on log-lib, which is included as a git submodule.
- Clone the repository with submodules and open a terminal in the project root.
git clone --recursive https://github.com/rasmushugosson/ecs-lib.git
The next steps depend on your preferred build system below.
- Run
premake5 vs20XXto generate a Visual Studio solution file (.sln). - Open the solution file in Visual Studio and build using MSVC.
- Run the pre-defined action
premake5 gmake-gccto generate makefiles specifically for GCC. - Navigate into
/build/[os]-gccwhere theMakefileis created. - Run
make config=[build type]where the possible options aredebug,releaseordist. - Navigate into
/bin/Sandbox/[build type]and run theSandboxexecutable.
- Run the pre-defined action
premake5 gmake-clangto generate makefiles specifically for Clang. - Navigate into
/build/[os]-clangwhere theMakefileis created. - Run
make config=[build type]where the possible options aredebug,releaseordist. - Navigate into
/bin/Sandbox/[build type]and run theSandboxexecutable.
There are additional actions for formatting with clang-format and linting through clang-tidy. These are run through:
# Run clang-format
premake5 format
# Run clang-tidy
premake5 lint
# Run clang-tidy and apply fixes
premake5 lint-fixThese commands assume clang-format and clang-tidy are installed on your system.
- Premake5: This library uses Premake5 as its build configuration tool.
Ensure that
premake5is installed on your system or copied into the root folder. You can download it here.
This library can be used as a git submodule in other Premake5 projects. Add it as a submodule (with recursive init to get log-lib) and include the ECS project definition in your premake5.lua:
include("path/to/ecs-lib/ecs-project.lua")
project("YourProject")
-- ...
includedirs({
"path/to/ecs-lib/dep/log-lib/log-lib/include",
"path/to/ecs-lib/ecs-lib/include"
})
links({ "Log", "ECS" })The ecs-project.lua file defines the ECS project and automatically includes the Log project dependency. The premake5.lua is used for standalone builds including the Sandbox example.
To use the library, include the Entity.h header file in your project. The ECS consists of five main pillars: entities, components, collections, pools and systems. The general design of the library is SoA, where collections contain a pool for each component type as a contiguous array. However, the pools themselves are stored as AoS. These components can then be processed through systems. Below is a brief explanation of each of the mentioned pillars.
Entities are simply IDs, or 32 bit unsigned ints. They are merely used to organize components and have no separate functionality. An entity is acquired through:
ae::Entity entity = ae::NewEntity(); // Returns a unique entity IDComponent types are defined as data structs. This means they should not contain methods, and this is because any functionality would be implemented through Systems instead. Below is an example of a component struct that we will use in later examples.
struct ExampleComponent // Data struct for an example component type
{
float a;
int b;
};
ExampleComponent component = { 1.0f, 123 }; // Instantiates a component of the defined typeCollections are where components are stored, and the link between the entities, the components and the systems processing them. The collection class contains functionality for adding components, querying components, checking which entities have specific components and so on. It is also possible to add the same component to multiple collections. A collection can be created and populated with components like this:
std::shared_ptr<ae::ComponentCollection> pCollection = std::make_shared<ae::ComponentCollection>();
pCollection->Add(entity, component); // Adds the component to the entityPools are never created by the user manually, but are rather an internal piece of collections. Each collection contains one component pool for each component type, and these pools can be accessed through the collection. Here is how to access component pools:
// GetPool returns a reference (creates the pool if it doesn't exist)
ae::ComponentPool<ExampleComponent>& pool = pCollection->GetPool<ExampleComponent>();
// TryGetPool returns a pointer (nullptr if pool doesn't exist)
const ae::ComponentPool<ExampleComponent>* pool = pCollection->TryGetPool<ExampleComponent>();As mentioned, systems provide a way to access and process components. Systems are implemented as derived classes from the ae::ComponentSystem base class. It contains two pure virtual functions to be implemented, ValidImpl and RunImpl. The ValidImpl function is automatically called for all entities that have a component in the system's specified collection, and can be used to define constraints for how components are combined. A return value of true means the entity provided as an argument is valid and false means it is not valid. The second function, RunImpl, is where components are processed. ae::ComponentSystem::Run cannot be called unless all entities satisfy the constraints specified in ValidImpl. Below is an example of a system that has no constraints for component combinations, and that simply prints all component values when run:
class ExampleSystem : public ae::ComponentSystem
{
public:
ExampleSystem(std::shared_ptr<ae::ComponentCollection> pCollection)
: ae::ComponentSystem("ExampleSystem", pCollection)
{
Validate(); // Validate all entities in the collection
}
bool ValidImpl(ae::Entity entity) const override
{
return true; // All entities are always valid
}
void RunImpl() override
{
const auto& collection = GetCollection();
const auto* pool = collection.TryGetPool<ExampleComponent>();
if (!pool) return; // No components of this type
for (const ExampleComponent& component : pool->GetAll())
{
std::cout << component.a << ", " << component.b << std::endl;
}
}
};
auto pSystem = std::make_unique<ExampleSystem>(pCollection);
pSystem->Run(); // Validates (if not already) and runs the systemThe build configuration determines which logging macros from log-lib are active:
| Configuration | Define | Logging |
|---|---|---|
debug |
AE_DEBUG |
AE_LOG() and AE_LOG_BOTH() are active |
release |
AE_RELEASE |
AE_LOG_RELEASE() and AE_LOG_BOTH() are active |
dist |
AE_DIST |
All logging disabled |
A complete example project demonstrating the mentioned features and more can be found in sandbox/src/Sandbox.cpp.
This library is licensed under the Apache License 2.0. See the LICENSE file in this repository for details.