I need some help fixing an issue in my entity component system.
So I have a registry class which contains two containers:
1 2 3 4 5 6
|
using View = std::unordered_map<Entity, std::any &>;
// ...
// Vector index = entity id. Map contains a single instance of every component the entity needs.
std::vector<std::unordered_map<std::type_index, std::any>> pool;
// For grouping components of same type together.
std::unordered_map<std::type_index, View> views;
|
An
Entity
is just an alias for std::uint32_t. They are generated by this member function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
Entity Registry::create() noexcept
{
if (!freedIds.empty())
{
const Entity available = freedIds.back();
freedIds.pop_back();
return available;
}
#ifdef _DEBUG
// null is just an std::uint32_t value reserved for invalid entities.
if (nextId == null)
{
SDL_LogWarn(SDL_LOG_CATEGORY_CUSTOM, "Maximum number of entities reached!");
return null;
}
#endif
pool.emplace_back();
return nextId++;
}
|
And I add components to the registry like this:
1 2 3 4 5 6
|
template<typename Component, typename... Arg>
void emplace(Entity entity, Arg &&...args) noexcept
{
pool[entity].emplace(typeid(Component), Component{std::forward<Arg>(args)...});
views[typeid(Component)].emplace(entity, pool[entity].at(typeid(Component)));
}
|
This function gives me a component belonging to an entity:
1 2 3 4 5 6
|
template<typename Component>
[[nodiscard]] Component &get(Entity entity) noexcept
{
assert(pool[entity].find(typeid(Component)) != pool[entity].end() && "Component does not exist!");
return std::any_cast<Component &>(pool[entity].at(typeid(Component)));
}
|
And to get a collection of components of same type I do this:
1 2 3 4 5 6
|
template<typename Component>
[[nodiscard]] View &getView() noexcept
{
assert(views.find(typeid(Component)) != views.end() && "Component does not exist!");
return views.at(typeid(Component));
}
|
I store the registry class in the scene class, which also contains all the component systems.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
class Scene final
{
public:
ecs::Registry entityReg;
ecs::Entity camera = entityReg.create();
// ...
ecs::RenderSystem renderSys{entityReg};
ecs::KeyboardInputSystem keyInputSys{entityReg};
ecs::CollisionSystem collSys{entityReg};
ecs::VelocitySystem velocitySys{entityReg};
ecs::CameraSystem cameraSys{entityReg};
};
|
The scene class creates the camera entity. In the constructor, it assigns the ecs::Position and ecs::Camera components to the camera. Systems iterate over the components they need by getting the "Views" and looping through them. It all works fine until I create a new entity from outside the scene class.
1 2 3 4 5 6 7 8 9 10 11
|
GameplayState::GameplayState(Application &app) noexcept
: scene{app}
{
// ...
// scene is a class member.
ecs::Entity entity = scene.entityReg.create();
scene.entityReg.emplace<ecs::Position>(entity);
scene.entityReg.emplace<ecs::Size>(entity, SDL_Point{32, 64});
scene.entityReg.emplace<ecs::Collision>(entity, SDL_Rect{0, 0, 32, 64}, scene);
// Creates more components...
}
|
For some reason the camera system throws a read access violation exception when it tries to std::any_cast the std::any object to ecs::Camera.
Exception thrown at 0x00007FF98CC4E098 (ucrtbased.dll) in Application.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF. |
And sometimes it throws an std::bad_any_cast exception.
1 2 3 4 5 6 7 8 9 10 11
|
void CameraSystem::update() noexcept
{
for (auto [id, value] : entityReg.getView<Camera>())
{
// find is same as get but it returns nullptr if the component does not exist.
Position *const pos = entityReg.find<Position>(id);
if (!pos) continue;
// Error when trying to cast.
const Camera &camera = std::any_cast<const Camera &>(value);
// ...
|
If I comment out this update function and in the GameplayState write this:
1 2
|
std::cout << "Entity " << scene.camera << " contains:\n";
std::cout << scene.entityReg.getView<ecs::Camera>().at(scene.camera).type().name() << "\n";
|
It prints out a different component name each type I run the game, when none of those components actually belong to the camera entity.
First run:
Entity 0 contains:
struct frc::ecs::Size
Second run:
Entity 0 contains:
struct frc::ecs::Texture
Third run:
Entity 0 contains:
struct frc::ecs::Velocity |
...
Sometimes, it also throws a read access violation when it tries to call the .name() function:
Exception thrown at 0x00007FF99DCCAB9E (vcruntime140d.dll) in Application.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF. |
(Creating this entity in the Scene class also results in the same problems). The other systems seem to be working completely fine, and creating just a single entity works fine too. Also, I previously stored the entity registry in the gameplay state, and it worked completely fine then, but after I created the scene class and moved the registry there it just stopped working.
Any help would be appreciated.
Edit: I think I might have fixed it myself. If anyone's wondering what the issue was, I tracked the ecs::Camera component until it's reference starts referencing another type. I tracked it down to the create() function. It seems like when the I resize the entity pool, It messed up the components references. I figured this happens when the vector reallocates its memory and moves the data to the new memory block, invalidating all the references to that data. After switching it up so that the unordered_map stores the actual values, and the vector stores references, it fixed the issue. I'm not sure if my explanation is correct, but it is working now.