Skip to content

Commit 5314fe1

Browse files
SilverSupplierclaudelearncold
authored
[Engine] WorldQuery facade 및 EntityRegistry::eachAlive 구현 (#69)
- WorldQuery: view<T...>(), contains<T>(), get<T>() read-only facade 추가 - EntityRegistry: eachAlive() 템플릿 순회 메서드 추가 - WorldQueryTests: view 필터링, 미등록 타입, 파괴된 entity, contains, get 검증 5개 테스트 추가 - CMakeLists.txt: WorldQuery.h, WorldQueryTests.cpp 등록 Closes #9 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: learncold <learncold8154@gmail.com>
1 parent 3f9fd0b commit 5314fe1

4 files changed

Lines changed: 152 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ add_library(ecs_engine STATIC
4949
src/engine/EngineStepContext.h
5050
src/engine/EngineSystem.h
5151
src/engine/FrameClock.h
52+
src/engine/WorldQuery.h
5253
src/engine/EntityRegistry.cpp
5354
src/engine/EngineRuntime.cpp
5455
src/engine/FrameClock.cpp
@@ -103,6 +104,7 @@ if (BUILD_TESTING)
103104
tests/EcsCoreTests.cpp
104105
tests/ImportContractsTests.cpp
105106
tests/DxfImportServiceTests.cpp
107+
tests/WorldQueryTests.cpp
106108
)
107109

108110
target_include_directories(safecrowd_tests

src/engine/EntityRegistry.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ class EntityRegistry {
2222
void setSignature(Entity entity, Signature signature);
2323
[[nodiscard]] Signature signatureOf(Entity entity) const;
2424

25+
template <typename Fn>
26+
void eachAlive(Fn&& fn) const {
27+
for (std::size_t i = 0; i < entries_.size(); ++i) {
28+
const Entry& entry = entries_[i];
29+
if (entry.alive) {
30+
fn(Entity{static_cast<EntityIndex>(i), entry.generation}, entry.signature);
31+
}
32+
}
33+
}
34+
2535
private:
2636
struct Entry {
2737
EntityGeneration generation{0};

src/engine/WorldQuery.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
#include "engine/EcsCore.h"
6+
7+
namespace safecrowd::engine {
8+
9+
class WorldQuery {
10+
public:
11+
explicit WorldQuery(EcsCore& core) : core_(core) {}
12+
13+
template <typename... Ts>
14+
[[nodiscard]] std::vector<Entity> view() const {
15+
Signature required{};
16+
bool allRegistered = true;
17+
([&] {
18+
const auto id = core_.componentRegistry().tryTypeOf<Ts>();
19+
if (!id.has_value()) {
20+
allRegistered = false;
21+
} else {
22+
required.set(id.value());
23+
}
24+
}(), ...);
25+
26+
if (!allRegistered) return {};
27+
28+
std::vector<Entity> result;
29+
core_.entityRegistry().eachAlive([&](Entity entity, const Signature& sig) {
30+
if ((sig & required) == required) {
31+
result.push_back(entity);
32+
}
33+
});
34+
return result;
35+
}
36+
37+
template <typename T>
38+
[[nodiscard]] bool contains(Entity entity) const {
39+
return core_.hasComponent<T>(entity);
40+
}
41+
42+
template <typename T>
43+
[[nodiscard]] T& get(Entity entity) {
44+
return core_.getComponent<T>(entity);
45+
}
46+
47+
template <typename T>
48+
[[nodiscard]] const T& get(Entity entity) const {
49+
return core_.getComponent<T>(entity);
50+
}
51+
52+
private:
53+
EcsCore& core_;
54+
};
55+
56+
} // namespace safecrowd::engine

tests/WorldQueryTests.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include "TestSupport.h"
2+
3+
#include "engine/WorldQuery.h"
4+
5+
namespace {
6+
7+
struct Position {
8+
float x{0.0f};
9+
float y{0.0f};
10+
};
11+
12+
struct Velocity {
13+
float vx{0.0f};
14+
float vy{0.0f};
15+
};
16+
17+
} // namespace
18+
19+
SC_TEST(WorldQuery_ViewFiltersEntitiesBySignature) {
20+
safecrowd::engine::EcsCore core;
21+
safecrowd::engine::WorldQuery query{core};
22+
23+
const auto e1 = core.createEntity();
24+
const auto e2 = core.createEntity();
25+
const auto e3 = core.createEntity();
26+
27+
core.addComponent(e1, Position{});
28+
core.addComponent(e1, Velocity{});
29+
core.addComponent(e2, Position{});
30+
core.addComponent(e3, Velocity{});
31+
32+
const auto result = query.view<Position, Velocity>();
33+
SC_EXPECT_EQ(result.size(), std::size_t{1});
34+
SC_EXPECT_TRUE(result[0] == e1);
35+
}
36+
37+
SC_TEST(WorldQuery_ViewReturnsEmptyIfTypeNotRegistered) {
38+
safecrowd::engine::EcsCore core;
39+
safecrowd::engine::WorldQuery query{core};
40+
41+
static_cast<void>(core.createEntity());
42+
43+
const auto result = query.view<Position>();
44+
SC_EXPECT_TRUE(result.empty());
45+
}
46+
47+
SC_TEST(WorldQuery_ViewExcludesDestroyedEntities) {
48+
safecrowd::engine::EcsCore core;
49+
safecrowd::engine::WorldQuery query{core};
50+
51+
const auto e1 = core.createEntity();
52+
core.addComponent(e1, Position{});
53+
core.destroyEntity(e1);
54+
55+
const auto e2 = core.createEntity();
56+
core.addComponent(e2, Position{});
57+
58+
const auto result = query.view<Position>();
59+
SC_EXPECT_EQ(result.size(), std::size_t{1});
60+
SC_EXPECT_TRUE(result[0] == e2);
61+
}
62+
63+
SC_TEST(WorldQuery_ContainsReflectsComponentPresence) {
64+
safecrowd::engine::EcsCore core;
65+
safecrowd::engine::WorldQuery query{core};
66+
67+
const auto e = core.createEntity();
68+
core.addComponent(e, Position{});
69+
70+
SC_EXPECT_TRUE(query.contains<Position>(e));
71+
SC_EXPECT_TRUE(!query.contains<Velocity>(e));
72+
}
73+
74+
SC_TEST(WorldQuery_GetReturnsComponentRef) {
75+
safecrowd::engine::EcsCore core;
76+
safecrowd::engine::WorldQuery query{core};
77+
78+
const auto e = core.createEntity();
79+
core.addComponent(e, Position{3.0f, 4.0f});
80+
81+
const auto& pos = query.get<Position>(e);
82+
SC_EXPECT_NEAR(pos.x, 3.0f, 1e-6);
83+
SC_EXPECT_NEAR(pos.y, 4.0f, 1e-6);
84+
}

0 commit comments

Comments
 (0)