I am messing with a small ECS implementation found here. I slowly copied the code to understand it and it works fine. However, instead of one big namespace, I'm trying to separate the functionality out into different classes and abstract everything with a "coordinator" class before I try to add the "system" part of ECS.
So I have something like this (sorry if my code is bad in advance, still learning C++):
// ECSCoordinator.h
#ifndef I3D_ECS_COORDINATOR_H
#define I3D_ECS_COORDINATOR_H
#include <memory>
#include "Entity.h"
#include "EntityManager.h"
#include "ComponentContainer.h"
class ECSCoordinator {
public:
ECSCoordinator();
Entity create_entity();
void delete_entity();
template<typename Component>
void add_component_to(Entity e, Component c);
private:
template<class Component>
static ComponentContainer<Component> registry;
std::unique_ptr<EntityManager> entity_manager;
};
template<typename Component>
void ECSCoordinator::add_component_to(Entity e, Component c) {
registry<Component>.insert(e, c);
}
// ECSCoordinator.cpp
#include "ECSCoordinator.h"
#include "ECS/Entity.h"
#include "ECS/EntityManager.h"
ECSCoordinator::ECSCoordinator()
: entity_manager(std::make_unique<EntityManager>()) {}
Entity ECSCoordinator::create_entity() {
return entity_manager->create_entity();
}
#endif // I3D_ECS_COORDINATOR_H
One thing that I had to decide on was where to declare registry
. In the original implementation it was declared in the scope of the namespace, but once I moved stuff into classes I didn't want to have a global variable just sitting there. So instead I made the variable template a static class member of the coordinator. I believe these are equivalent? The forward declaration of ComponentContainer
then no longer worked since I'm defining add_component_to
in the header, so I had to #include "ComponentContainer.h"
instead, meaning I had to move the static definitions in ComponentContainer
into their own .cpp file (if that matters).
Other than that, and making Entity.h
a POD (holds just an unsigned int) and EntityManager.h
responsible for creating an Entity with an incremented ID, I haven't changed much.
So when I want to use the system, I want to do this instead:
std::unique_ptr<ECSCoordinator> coordinator = std::make_unique<ECSCoordinator>();
Entity fish = coordinator->create_entity();
coordinator->add_component_to(fish, Animal("Fish"));
However, these changes cause a read access violation when I try to add a (key, value) to the unordered map (line 100 of tiny_ecs.hpp):
map_entity_component_index[e.id] = component_index;
I can definitely confirm that e.id = 0
and component_index = 1
for the first addition. I can run a method on the unordered map like size()
right before this error just fine. But, attempting to add something to the map causes a crash.
I'm not sure how to interpret the error I'm seeing in my debugger:
Thank you to anyone that helps.
The answer ended up being due to static order initialisation fiasco. The solution was to access the static template variable in the Coordinator via a getter to ensure its initialisation:
class ECSCoordinator {
//....
template<class Component>
ComponentContainer<Component>& get_registry();
//...
};
template<typename Component>
void ECSCoordinator::add_component_to(Entity e, Component c) {
get_registry<Component>().insert(e, c);
}
template<class Component>
ComponentContainer<Component>& ECSCoordinator::get_registry() {
static ComponentContainer<Component> registry;
return registry;
}