C++ single-header entity component system library

Overview

ECS

This is a simple C++ header-only type-safe entity component system library. It makes heavy use of C++11 constructs, so make sure you have an up to date compiler. It isn't meant to do absolutely everything, so please feel free to modify it when using. There's a VS2015 solution provided, but it should compile on any standard compiler with C++11 support (C++14 is more ideal, however, as it lets you use auto parameters in lambdas).

Again, this is meant for quick prototyping or as a starting point for a more advanced ECS toolkit. It works and it works well, but it isn't optimized for speed or anything.

This has been tested on the following compilers:

  • Visual Studio 2015 on Windows 10 (x64)
  • G++ 5.4.1 (using -std=c++11 and -std=c++14) on Ubuntu 14.04 (x64)

Contributions are welcome! Submit a pull request, or if you find a problem (or have a feature request) make a new issue!

Tutorial

This ECS library is based on the Evolve Your Hierarchy article. If you haven't read it, please do or else things won't make much sense. This is a data-driven entity component system library, and to know how to work with it you need to know what that entails (this is not the same as Unity's components so don't expect it to be).

Your first components

Components in ECS can be any data type, but generally they'll be a struct containing some plain old data. For now, let's define two components:

struct Position
{
    Position(float x, float y) : x(x), y(y) {}
    Position() : x(0.f), y(0.f) {}

    float x;
    float y;
}

struct Rotation
{
    Rotation(float angle) : angle(angle) {}
    Rotation() : angle(0) {}
    float angle;
}

This isn't the most realistic example - normally you'd just have a single transform component for a game but this should help illustrate some functionality later. Also note that we don't have to do anything special for these structs to act as components, though there is the requirement for at least a default constructor.

Create a system

Now we need some logic to act on that data. Let's make a simple gravity system:

class GravitySystem : public EntitySystem
{
public:
    GravitySystem(float amount)
    {
        gravityAmount = amount;
    }
    
    virtual ~GravitySystem() {}
    
    virtual void tick(World* world, float deltaTime) override
    {
        world->each<Position>([&](Entity* ent, ComponentHandle<Position> position) {
            position->y += gravityAmount * deltaTime;
        });
    }
    
private:
    float gravityAmount;
}

This is a pretty standard class definition. We subclass EntitySystem and implement the tick() method. The world provides the each method, which takes a list of component types and runs a given function (in this case a lambda) on every entity that has those components. Note that the lambda is passed a ComponentHandle, and not the component itself.

Alternate iteration methods

In addition to the lambda-based each, there's also an iterator-based each, made to be used with the range based for loop. Lambda-based each isn't a true loop, and as such you can't break from it. Instead, you can use a range based for loop. The downside is that it will not directly expose components as arguments, but you can combine it with Entity::with for a similar result:

for (Entity* ent : world->each<Position>())
{
    ent->with<Position>([&](ComponentHandle<Position> position) {
	    position->y += gravityAmount * deltaTime;
	});
}

Alternatively, you may retrieve a single component at a time with Entity::get, though this will return an invalid component handle (see ComponentHandle<T>::isValid and ComponentHandle<T>::operator bool()) if there isn't a component of that type attached:

ComponentHandle<Position> position = ent->get<Position>();
position->y += gravityAmount * deltaTime; // this will crash if there is no position component on the entity

with<T>() only runs the given function if the entity has the listed components. It also returns true if all components were found, or false if not all components were on the entity.

Finally, if you want to run a function on all entities, regardless of components, then use the all function in the same way as each:

world->all([](Entity* ent) {
	// do something with ent
});

You may also use all in a range based for loop in a similar fashion to each.

Create the world

Next, inside a main() function somewhere, you can add the following code to create the world, setup the system, and create an entity:

World* world = World::createWorld();
world->registerSystem(new GravitySystem(-9.8f));

Entity* ent = world->create();
ent->assign<Position>(0.f, 0.f); // assign() takes arguments and passes them to the constructor
ent->assign<Rotation>(35.f);

Now you can call the tick function on the world in order to tick all systems that have been registered with the world:

world->tick(deltaTime);

Once you are done with the world, make sure to destroy it (this will also deallocate the world).

world->destroyWorld();

Custom Allocators

You may use any standards-compliant custom allocator. The world handles all allocations and deallocations for entities and components.

In order to use a custom allocator, define ECS_ALLOCATOR_TYPE before including ECS.h:

#define ECS_ALLOCATOR_TYPE MyAllocator<Entity>
#include "ECS.h"

Allocators must have a default constructor. When creating the world with World::createWorld, you may pass in your custom allocator if you need to initialize it first. Additionally, custom allocators must be rebindable via std::allocator_traits.

The default implementation uses std::allocator<Entity>. Note that the world will rebind allocators for different types.

Working with components

You may retrieve a component handle (for example, to print out the position of your entity) with get:

ComponentHandle<Position> pos = ent->get<Position>();
std::cout << "My position is " << pos->x << ", " << pos->y << std::endl;

If an entity doesn't have a component and you try to retrieve that type from it, get will return an invalid component handle:

ComponentHandle<Position> pos = otherEnt->get<Position>(); // assume otherEnt doesn't have a Position component
pos.isValid(); // returns false, note the . instead of the ->

Alternatively, you may use a handle's bool conversion operator instead of isValid:

if (pos)
{
    // pos is valid
}
else
{
    // pos is not valid
}

Events

For communication between systems (and with other objects outside of ECS) there is an event system. Events can be any type of object, and you can subscribe to specific types of events by subclassing EventSubscriber and calling subscribe on the world:

struct MyEvent
{
    int foo;
    float bar;
}

class MyEventSubscriber : public EventSubscriber<MyEvent>
{
public:
    virtual ~MyEventSubscriber() {}
    
    virtual void receive(World* world, const MyEvent& event) override
    {
        std::cout << "MyEvent was emitted!" << std::endl;
    }
}

// ...

MyEventSubscriber* mySubscriber = new MyEventSubscriber();
world->subscribe<MyEvent>(mySubscriber);

Then, to emit an event:

world->emit<MyEvent>({ 123, 45.67f }); // you can use initializer syntax if you want, this sets foo = 123 and bar = 45.67f

Make sure you call unsubscribe or unsubscribeAll on your subscriber before deleting it, or else emitting the event may cause a crash or other undesired behavior.

Systems and events

Often, your event subscribers will also be systems. Systems have configure and unconfigure functions that are called when they are added to/removed from the world and which you may use to subscribe and unsubscribe from events:

class MySystem : public EntitySystem, public EventSubscriber<MyEvent>
{
    // ...
    
    virtual void configure(World* world) override
    {
        world->subscribe<MyEvent>(this);
    }
    
    virtual void unconfigure(World* world) override
    {
        world->unsubscribeAll(this);
        // You may also unsubscribe from specific events with world->unsubscribe<MyEvent>(this), but
        // when unconfigure is called you usually want to unsubscribe from all events.
    }
    
    // ...
}

Built-in events

There are a handful of built-in events. Here is the list:

  • OnEntityCreated - called when an entity has been created.
  • OnEntityDestroyed - called when an entity is being destroyed (including when a world is beind deleted).
  • OnComponentAssigned - called when a component is assigned to an entity. This might mean the component is new to the entity, or there's just a new assignment of the component to that entity overwriting an old one.
  • OnComponentRemoved - called when a component is removed from an entity. This happens upon manual removal (via Entity::remove() and Entity::removeAll()) or upon entity destruction (which can also happen as a result of the world being destroyed).

Avoiding RTTI

If you wish to avoid using RTTI for any reason, you may define the ECS_NO_RTTI macro before including ECS.h. When doing so, you must also add a couple of macros to your component and event types:

// in a header somewhere
struct MyComponent
{
	ECS_DECLARE_TYPE; // add this at the top of the structure, make sure to include the semicolon!

	// ...
};

// in a cpp somewhere
ECS_DEFINE_TYPE(MyComponent);

Again, make sure you do this with events as well.

Additionally, you will have to put the following in a cpp file:

#include "ECS.h"
ECS_TYPE_IMPLEMENTATION;

If you have any templated events, you may do the following:

template<typename T>
struct MyEvent
{
	ECS_DECLARE_TYPE;

	T someField;

	// ...
}

template<typename T>
ECS_DEFINE_TYPE(MyEvent<T>);
Comments
  • World::each causes Internal compiler error if class has an implementation file

    World::each causes Internal compiler error if class has an implementation file

    This header file compiles and works perfectly:

    #pragma once
    
    #include "ECS.h"
    
    struct TestComp {
    	int x;
    	int y;
    };
    
    class TestClass {
    public:
    	TestClass() {}
    	virtual ~TestClass() = default;
    
    	void update(ECS::World* world, float dt) {
    		using namespace ECS;
    		world->each<TestComp>([&](Entity* ent, ComponentHandle<TestComp> testComp) {
    			testComp->x += 1;
    		});
    
    	}
    };
    

    however, as soon as an implementation file is present, for example TestClass.cpp, even if it just contains

    #include "TestClass.h"
    
    

    Visual Studio shows an internal compiler error (C1001) at the last line of the implementation file. This error remains whatever i do, using inline, moving the implementation to the .cpp file and so on.

    Im running the latest Visual Studio enterprise 2017, v. 15.5.7

    bug 
    opened by jkunstwald 16
  • What about supporting singleton components

    What about supporting singleton components

    I'm using your library to write a simple game. Your library is wonderful and it helps me a lot. But I hope that you could add APIs to support to create singleton components (came from GDC2017 'Overwatch Gameplay Architecture and Netcode', I'm sorry that I didn't find the video), which is like global variables that directly belong to the world instead of entities. I think they are helpful when you need components with camera position, window width and height, time, etc, that need to share between systems but does not logically belong to any entity.

    I add some codes to complement a simple version, using a singleton entity to store the singleton components, but it's not gentle at all:

    class World
    {
    public:
    	Entity* singletons;
    
    	template<typename T, typename... Args>
    	void createSingletonComponent(Args&&... args) {
    		this->singletons->assign<T>(args...);
    	}
    
    	template<typename T>
    	ComponentHandle<T> getSingletonComponent() {
    		return this->singletons->get<T>();
    	}
    };
    

    I hope you could write these APIs in your way.

    opened by rubyyhj 10
  • Possible solution to issue #10

    Possible solution to issue #10

    Here is a possible way to do enabling and disabling of systems prompted by fabslabs request. This way would seperate the disabled systems into their own std::vector. That way the World wouldn't have to check each system for any kind of disabled flag. Systems would just be removed from the main systems vector and moved into a disabledSystems vector. Then they would be removed from disabledSystems and back into the main systems vector upon being enabled again. Also, not sure about how you'd feel about having registerSystem return the EntitySystem*. Maybe there's a better way to handle that.

    opened by MachineMitch21 5
  • Use allocator directly instead of allocator_traits

    Use allocator directly instead of allocator_traits

    Hi! First of all, thanks for this amazing resource. I've made a Super Mario Bros clone using this library.

    I didn't use your library as-is but made my own dumbed down version based on yours (mainly because I wanted to understand 100% of the code in my game, I'm new to C++).

    Now that the game is "complete" I want to fully understand your code in order to learn more advanced c++. I noticed you are making heavy use of Allocators (which I'm having a really hard time wrapping my head around). I was wondering why you use this "allocator_traits" function to access the allocator instead of just using the allocator you already defined.

    If you have any reference on where I can learn more about allocators I would greatly appreciate it. Feel free to close this PR as I'm sure doesn't improve the code at all.

    opened by feresr 4
  • Can't build with cmake and g++ 9.2.0

    Can't build with cmake and g++ 9.2.0

    I try to build ECS with my other Project (didn't work), so I fork ESC and add my typical cmake-file and compiler-options (try out C++14 and C++17): https://github.com/abeimler/ECS

    OS: Arch Linux x86_64 (4.16.81-1-MANJARO) Compiler: c++ (GCC) 9.2.0 CMake: 3.15.5

    Build:

    mkdir build
    cmake . -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DECS_SAMPLE_BUILD:BOOL="1"
    cmake --build build
    

    Error:

    [ 50%] Building CXX object CMakeFiles/sample.dir/sample.cpp.o
    /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/9.2.0/../../../../include/c++/9.2.0/ext/new_allocator.h:146:8: error: allocation of incomplete type 'ECS::Entity' [clang-diagnostic-error]
                                _Up(std::forward<_Args>(__args)...)))
                                ^
    /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/9.2.0/../../../../include/c++/9.2.0/bits/alloc_traits.h:483:24: note: in instantiation of exception specification for
     'construct<ECS::Entity, ECS::World *, unsigned long &>' requested here
            noexcept(noexcept(__a.construct(__p, std::forward<_Args>(__args)...)))
                                  ^
    ECS.h:454:4: note: in instantiation of exception specification for 'construct<ECS::Entity, ECS::World *, unsigned long &>
    ' requested here
                            std::allocator_traits<EntityAllocator>::construct(entAlloc, ent, this, lastEntityId);
                            ^
    ECS.h:131:8: note: forward declaration of 'ECS::Entity'
            class Entity;
                  ^
    1 error generated.
    Error while processing sample.cpp.
    Found compiler errors, but -fix-errors was not specified.
    Fixes have NOT been applied.
    
    Found compiler error(s).
    make[3]: *** [CMakeFiles/sample.dir/build.make:63: CMakeFiles/sample.dir/sample.cpp.o] Error 1
    make[2]: *** [CMakeFiles/Makefile2:105: CMakeFiles/sample.dir/all] Error 2
    make[1]: *** [CMakeFiles/Makefile2:117: CMakeFiles/sample.dir/rule] Error 2
    make: *** [Makefile:131: sample] Error 2
    
    opened by abeimler 3
  • Option to disable a system

    Option to disable a system

    I'd like to disable certain systems from running based on certain game events. To support would require being able to get a system from the world and call a method or set a field to disable it. Systems that are disabled would not have their tick methods called until reenabled.

    opened by fabslab 3
  • Fix #16: Can't build with cmake and g++ 9.2.0

    Fix #16: Can't build with cmake and g++ 9.2.0

    see #16 for the Error ... basically "error: allocation of incomplete type" you try to allocate (in World) an per-define class Entity. Solution: Move Enity class definition before World.

    I also add a CMakeLists.txt file.

    opened by abeimler 1
  • Not an issue, just a question

    Not an issue, just a question

    Awesome library. Works perfectly. I just have a question about general use. Should there be 1 global world or one in every scene? I have written a scene manager and i cant find an answer anywhere regarding this. I am leaning towards one for every scene..... but id love to hear how you use it. Thanks.

    Sorry i know this is the wrong place to ask questions but i dont understand github.

    question 
    opened by petergilmour1987 1
  • Allow systems to access entities and components during unconfigure

    Allow systems to access entities and components during unconfigure

    If a system creates an entity in configure(), the system cannot then access that entity from unconfigure(). If the system stores a reference to the entity or one of its components in configure(), accessing that reference from unconfigure() will cause a crash.

    By unconfigure()ing systems before entities are destroyed, systems can perform last minute data operations without the risk of crashing.

    bug 
    opened by MarcBritton 0
Owner
Sam Bloomberg
Software Engineer at 343 Industries / MS Studios Quality
Sam Bloomberg
Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more

EnTT is a header-only, tiny and easy to use library for game programming and much more written in modern C++. Among others, it's used in Minecraft by

Michele Caini 7.6k Dec 30, 2022
apecs: A Petite Entity Component System

apecs: A Petite Entity Component System A header-only, very small entity component system with no external dependencies.

Matt Cummins 17 Jun 1, 2022
EntityX - A fast, type-safe C++ Entity-Component system

EntityX - A fast, type-safe C++ Entity Component System NOTE: The current stable release 1.0.0 breaks backward compatibility with < 1.0.0. See the cha

Alec Thomas 2k Dec 29, 2022
C++ entity-component system

CORGI Version 1.0.2 {#corgi_readme} CORGI is a C++ entity-component system library developed primarily for games that focus on simplicity and flexibil

Google 250 Nov 6, 2022
Entity-Component-System (ECS) with a focus on ease-of-use, runtime extensibility and compile-time type safety and clarity.

Kengine The Koala engine is a type-safe and self-documenting implementation of an Entity-Component-System (ECS), with a focus on runtime extensibility

Nicolas Phister 466 Dec 26, 2022
Thoughts about entity-component-system

About Warning: This is not a complete production-ready library for entity-component-system. This is only my thoughts about how the modern entity-compo

Sergey Makeev 179 Dec 7, 2022
An open source C++ entity system.

anax ARCHIVED: as I haven't maintained this library for at least a couple of years. I don't have the time or interest to work on this. Please use anot

Miguel Martin 456 Jan 4, 2023
bsdiff changed to remove bz2, the header and to allow streaming interfaces, to be used on the esp32 with idf as a component

bspatch for esp32 This project adds support for bspatch to the esp32 with some changes: no compression (bz2), no header and changed the interfaces to

Blockstream 11 Oct 24, 2022
A single file, single function, header to make notifications on the PS4 easier

Notifi Synopsis Adds a single function notifi(). It functions like printf however the first arg is the image to use (NULL and any invalid input should

Al Azif 9 Oct 4, 2022
A drop-in entity editor for EnTT with Dear ImGui

imgui_entt_entity_editor A drop-in, single-file entity editor for EnTT, with ImGui as graphical backend. demo-code (live) Editor Editor with Entiy-Lis

Erik Scholz 151 Jan 2, 2023
Simple ESPHome Wiegand custom component

esphome-wiegand Simple ESPHome Wiegand custom component Based on this code: https://github.com/Luisiado/wiegand_esphome_module To use: Drop wiegand_de

Av 24 Dec 26, 2022
A component based project manager.

Component Based Project Manager CBPM provides an interface to manage a component-based project. Build To build CBPM, you must install xmake: a build-s

null 1 Nov 2, 2021
Spin-off component from existing IBM/mcas open source project

PyMM PyMM is a python library that allows the storing and manipulation of existing heavily used types such as Numpy ndarray and PyTorch on Persistent

International Business Machines 16 Nov 20, 2022
Custom ESPHome Component for generic Sit-Stand-Desks

ESPHomeGenericSitStandDesk I have one of those generic relatively cheap Sit Stand Desks. In an effort to monitor my desk usage I developed this overki

Till 18 Dec 27, 2022
ESPHome component to send and receive HDMI-CEC messages.

HDMI-CEC ESPHome Component An ESPHome component that supports receiving and transmitting HDMI-CEC messages to connected HDMI devices. The ultimate goa

John Boiles 23 Dec 16, 2022
A single-header C/C++ library for parsing and evaluation of arithmetic expressions

ceval A C/C++ header for parsing and evaluation of arithmetic expressions. [README file is almost identical to that of the ceval library] Functions ac

e_t 9 Oct 10, 2022
A single-header C/C++ library for parsing and evaluation of arithmetic expressions

ceval A C/C++ header for parsing and evaluation of arithmetic expressions. [README file is almost identical to that of the ceval library] Functions ac

e_t 9 Oct 10, 2022
Tuibox - A single-header terminal UI (TUI) library, capable of creating mouse-driven, interactive applications on the command line.

tuibox tuibox ("toybox") is a single-header terminal UI library, capable of creating mouse-driven, interactive applications on the command line. It is

Andrew 37 Dec 24, 2022