diff --git a/core/src/cubos/core/ecs/types.cpp b/core/src/cubos/core/ecs/types.cpp index 63e3bf5d5..95851c583 100644 --- a/core/src/cubos/core/ecs/types.cpp +++ b/core/src/cubos/core/ecs/types.cpp @@ -39,11 +39,13 @@ void Types::add(const reflection::Type& type, Kind kind) DataTypeId Types::id(const reflection::Type& type) const { + CUBOS_ASSERT(mTypes.contains(type), "Type {} not registered", type.name()); return mTypes.at(type); } DataTypeId Types::id(const std::string& name) const { + CUBOS_ASSERT(mNames.contains(name), "Type {} not registered", name); return mNames.at(name); } diff --git a/core/src/cubos/core/ecs/world.cpp b/core/src/cubos/core/ecs/world.cpp index ede9df5d1..6100ebea5 100644 --- a/core/src/cubos/core/ecs/world.cpp +++ b/core/src/cubos/core/ecs/world.cpp @@ -144,7 +144,7 @@ void World::relate(Entity from, Entity to, const reflection::Type& type, void* v CUBOS_ASSERT(this->isAlive(to)); auto dataType = mTypes.id(type); - CUBOS_ASSERT(mTypes.isRelation(dataType)); + CUBOS_ASSERT(mTypes.isRelation(dataType), "Type {} is not registered as a relation", type.name()); // Create a sparse relation table identifier from the archetypes of both entities. auto fromArchetype = mEntityPool.archetype(from.index); @@ -230,7 +230,7 @@ auto World::Components::end() -> Iterator auto World::Components::add(const reflection::Type& type, void* value) -> Components& { auto typeId = mWorld.mTypes.id(type); - CUBOS_ASSERT(mWorld.mTypes.isComponent(typeId), "Type '{}' is not registered as a component", type.name()); + CUBOS_ASSERT(mWorld.mTypes.isComponent(typeId), "Type {} is not registered as a component", type.name()); auto columnId = ColumnId::make(typeId); auto oldArchetype = mWorld.mEntityPool.archetype(mEntity.index); @@ -265,7 +265,7 @@ auto World::Components::add(const reflection::Type& type, void* value) -> Compon auto World::Components::remove(const reflection::Type& type) -> Components& { auto typeId = mWorld.mTypes.id(type); - CUBOS_ASSERT(mWorld.mTypes.isComponent(typeId), "Type '{}' is not registered as a component", type.name()); + CUBOS_ASSERT(mWorld.mTypes.isComponent(typeId), "Type {} is not registered as a component", type.name()); auto columnId = ColumnId::make(typeId); // If the old archetype doesn't contain this component type, then we don't do anything. diff --git a/core/tests/ecs/utils.hpp b/core/tests/ecs/utils.hpp index 8c954a4c2..76a58d5b8 100644 --- a/core/tests/ecs/utils.hpp +++ b/core/tests/ecs/utils.hpp @@ -76,4 +76,7 @@ inline void setupWorld(cubos::core::ecs::World& world) world.registerComponent(); world.registerComponent(); world.registerComponent(); + world.registerRelation(); + world.registerRelation(); + world.registerRelation(); } diff --git a/core/tests/ecs/world.cpp b/core/tests/ecs/world.cpp index eee00043b..60684f48f 100644 --- a/core/tests/ecs/world.cpp +++ b/core/tests/ecs/world.cpp @@ -133,4 +133,80 @@ TEST_CASE("ecs::World") CHECK(destroyed); } + + SUBCASE("add and remove relations") + { + // Create three entities. + auto foo = world.create(); + auto bar = world.create(); + auto baz = world.create(); + + bool fooBarFlag = false; + bool barBazFlag = false; + bool fooBazFlag = false; + bool fooFooFlag = false; + + // Relate foo to bar. + REQUIRE_FALSE(world.related(foo, bar)); + world.relate(foo, bar, DetectDestructorRelation{{&fooBarFlag}}); + REQUIRE(world.related(foo, bar)); + + // Relate bar to baz. + REQUIRE_FALSE(world.related(bar, baz)); + world.relate(bar, baz, DetectDestructorRelation{{&barBazFlag}}); + REQUIRE(world.related(bar, baz)); + + // Relate foo to baz. + REQUIRE_FALSE(world.related(foo, baz)); + world.relate(foo, baz, DetectDestructorRelation{{&fooBazFlag}}); + REQUIRE(world.related(foo, baz)); + + // Relate foo to foo. + REQUIRE_FALSE(world.related(foo, foo)); + world.relate(foo, foo, DetectDestructorRelation{{&fooFooFlag}}); + REQUIRE(world.related(foo, foo)); + + // Nothing should have been destroyed yet. + CHECK_FALSE(fooBarFlag); + CHECK_FALSE(barBazFlag); + CHECK_FALSE(fooBazFlag); + CHECK_FALSE(fooFooFlag); + + // Remove the relation between foo and bar and add it again. + world.unrelate(foo, bar); + CHECK(fooBarFlag); + CHECK_FALSE(barBazFlag); + CHECK_FALSE(fooBazFlag); + CHECK_FALSE(fooFooFlag); + fooBarFlag = false; + world.relate(foo, bar, DetectDestructorRelation{{&fooBarFlag}}); + + // Destroy bar. + world.destroy(bar); + CHECK(fooBarFlag); + CHECK(barBazFlag); + CHECK_FALSE(fooBazFlag); + CHECK_FALSE(fooFooFlag); + + // Destroy foo. + world.destroy(foo); + CHECK(fooBazFlag); + CHECK(fooFooFlag); + } + + SUBCASE("relations are correctly destructed when the world is destroyed") + { + bool destroyed = false; + + // Create a world with an entity and a relation and immediately destroy it. + { + World world{}; + setupWorld(world); + auto foo = world.create(); + world.relate(foo, foo, DetectDestructorRelation{{&destroyed}}); + CHECK_FALSE(destroyed); + } + + CHECK(destroyed); + } }