diff --git a/CMakeLists.txt b/CMakeLists.txt index d52bce9..276fb41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ add_library(ecs_engine STATIC src/engine/EngineStepContext.h src/engine/EngineSystem.h src/engine/FrameClock.h + src/engine/WorldQuery.h src/engine/EntityRegistry.cpp src/engine/EngineRuntime.cpp src/engine/FrameClock.cpp @@ -103,6 +104,7 @@ if (BUILD_TESTING) tests/EcsCoreTests.cpp tests/ImportContractsTests.cpp tests/DxfImportServiceTests.cpp + tests/WorldQueryTests.cpp ) target_include_directories(safecrowd_tests diff --git a/src/engine/EntityRegistry.h b/src/engine/EntityRegistry.h index 392d487..9a3a952 100644 --- a/src/engine/EntityRegistry.h +++ b/src/engine/EntityRegistry.h @@ -22,6 +22,16 @@ class EntityRegistry { void setSignature(Entity entity, Signature signature); [[nodiscard]] Signature signatureOf(Entity entity) const; + template + void eachAlive(Fn&& fn) const { + for (std::size_t i = 0; i < entries_.size(); ++i) { + const Entry& entry = entries_[i]; + if (entry.alive) { + fn(Entity{static_cast(i), entry.generation}, entry.signature); + } + } + } + private: struct Entry { EntityGeneration generation{0}; diff --git a/src/engine/WorldQuery.h b/src/engine/WorldQuery.h new file mode 100644 index 0000000..cc3885a --- /dev/null +++ b/src/engine/WorldQuery.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include "engine/EcsCore.h" + +namespace safecrowd::engine { + +class WorldQuery { +public: + explicit WorldQuery(EcsCore& core) : core_(core) {} + + template + [[nodiscard]] std::vector view() const { + Signature required{}; + bool allRegistered = true; + ([&] { + const auto id = core_.componentRegistry().tryTypeOf(); + if (!id.has_value()) { + allRegistered = false; + } else { + required.set(id.value()); + } + }(), ...); + + if (!allRegistered) return {}; + + std::vector result; + core_.entityRegistry().eachAlive([&](Entity entity, const Signature& sig) { + if ((sig & required) == required) { + result.push_back(entity); + } + }); + return result; + } + + template + [[nodiscard]] bool contains(Entity entity) const { + return core_.hasComponent(entity); + } + + template + [[nodiscard]] T& get(Entity entity) { + return core_.getComponent(entity); + } + + template + [[nodiscard]] const T& get(Entity entity) const { + return core_.getComponent(entity); + } + +private: + EcsCore& core_; +}; + +} // namespace safecrowd::engine diff --git a/tests/WorldQueryTests.cpp b/tests/WorldQueryTests.cpp new file mode 100644 index 0000000..2bf469a --- /dev/null +++ b/tests/WorldQueryTests.cpp @@ -0,0 +1,84 @@ +#include "TestSupport.h" + +#include "engine/WorldQuery.h" + +namespace { + +struct Position { + float x{0.0f}; + float y{0.0f}; +}; + +struct Velocity { + float vx{0.0f}; + float vy{0.0f}; +}; + +} // namespace + +SC_TEST(WorldQuery_ViewFiltersEntitiesBySignature) { + safecrowd::engine::EcsCore core; + safecrowd::engine::WorldQuery query{core}; + + const auto e1 = core.createEntity(); + const auto e2 = core.createEntity(); + const auto e3 = core.createEntity(); + + core.addComponent(e1, Position{}); + core.addComponent(e1, Velocity{}); + core.addComponent(e2, Position{}); + core.addComponent(e3, Velocity{}); + + const auto result = query.view(); + SC_EXPECT_EQ(result.size(), std::size_t{1}); + SC_EXPECT_TRUE(result[0] == e1); +} + +SC_TEST(WorldQuery_ViewReturnsEmptyIfTypeNotRegistered) { + safecrowd::engine::EcsCore core; + safecrowd::engine::WorldQuery query{core}; + + static_cast(core.createEntity()); + + const auto result = query.view(); + SC_EXPECT_TRUE(result.empty()); +} + +SC_TEST(WorldQuery_ViewExcludesDestroyedEntities) { + safecrowd::engine::EcsCore core; + safecrowd::engine::WorldQuery query{core}; + + const auto e1 = core.createEntity(); + core.addComponent(e1, Position{}); + core.destroyEntity(e1); + + const auto e2 = core.createEntity(); + core.addComponent(e2, Position{}); + + const auto result = query.view(); + SC_EXPECT_EQ(result.size(), std::size_t{1}); + SC_EXPECT_TRUE(result[0] == e2); +} + +SC_TEST(WorldQuery_ContainsReflectsComponentPresence) { + safecrowd::engine::EcsCore core; + safecrowd::engine::WorldQuery query{core}; + + const auto e = core.createEntity(); + core.addComponent(e, Position{}); + + SC_EXPECT_TRUE(query.contains(e)); + SC_EXPECT_TRUE(!query.contains(e)); +} + +SC_TEST(WorldQuery_GetReturnsComponentRef) { + safecrowd::engine::EcsCore core; + safecrowd::engine::WorldQuery query{core}; + + const auto e = core.createEntity(); + core.addComponent(e, Position{3.0f, 4.0f}); + + const auto& pos = query.get(e); + SC_EXPECT_NEAR(pos.x, 3.0f, 1e-6); + SC_EXPECT_NEAR(pos.y, 4.0f, 1e-6); +}