From 44308fb418d0cedabc4df8c0e69b5355890b1c4e Mon Sep 17 00:00:00 2001 From: learncold Date: Mon, 23 Mar 2026 17:37:54 +0900 Subject: [PATCH 1/2] [Build] reorganize source layout and sync docs --- AGENTS.md | 4 +- CMakeLists.txt | 47 +++++---- docs/GitHub Project.md | 2 + ...5\355\212\270 \352\265\254\354\241\260.md" | 10 +- .../{ => include/application}/MainWindow.h | 0 src/application/{ => src}/MainWindow.cpp | 0 src/application/{ => src}/main.cpp | 0 .../{ => include/domain}/SafeCrowdDomain.h | 0 src/domain/{ => src}/SafeCrowdDomain.cpp | 0 .../{ => include/engine}/EngineConfig.h | 0 .../{ => include/engine}/EngineRuntime.h | 0 src/engine/{ => include/engine}/EngineState.h | 0 src/engine/{ => include/engine}/EngineStats.h | 0 .../{ => include/engine}/EngineStepContext.h | 0 .../{ => include/engine}/EngineSystem.h | 0 src/engine/include/engine/Entity.h | 35 +++++++ src/engine/include/engine/EntityRegistry.h | 39 ++++++++ src/engine/{ => include/engine}/FrameClock.h | 0 src/engine/{ => src}/EngineRuntime.cpp | 0 src/engine/src/EntityRegistry.cpp | 95 +++++++++++++++++++ src/engine/{ => src}/FrameClock.cpp | 0 tests/EntityRegistryTests.cpp | 59 ++++++++++++ 22 files changed, 268 insertions(+), 23 deletions(-) rename src/application/{ => include/application}/MainWindow.h (100%) rename src/application/{ => src}/MainWindow.cpp (100%) rename src/application/{ => src}/main.cpp (100%) rename src/domain/{ => include/domain}/SafeCrowdDomain.h (100%) rename src/domain/{ => src}/SafeCrowdDomain.cpp (100%) rename src/engine/{ => include/engine}/EngineConfig.h (100%) rename src/engine/{ => include/engine}/EngineRuntime.h (100%) rename src/engine/{ => include/engine}/EngineState.h (100%) rename src/engine/{ => include/engine}/EngineStats.h (100%) rename src/engine/{ => include/engine}/EngineStepContext.h (100%) rename src/engine/{ => include/engine}/EngineSystem.h (100%) create mode 100644 src/engine/include/engine/Entity.h create mode 100644 src/engine/include/engine/EntityRegistry.h rename src/engine/{ => include/engine}/FrameClock.h (100%) rename src/engine/{ => src}/EngineRuntime.cpp (100%) create mode 100644 src/engine/src/EntityRegistry.cpp rename src/engine/{ => src}/FrameClock.cpp (100%) create mode 100644 tests/EntityRegistryTests.cpp diff --git a/AGENTS.md b/AGENTS.md index 806c5d7..3a9efd8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,7 @@ - `src/application` - `src/domain` - `src/engine` +- Each layer may further split into `include/` and `src/` under its own root. - When touching build files, confirm that referenced source files are actually tracked in Git. ## Build @@ -28,7 +29,7 @@ ## Source Layout - All C++ source files live under `src/`. -- Use `src/` as the include root. +- Split each layer so headers live under `src//include//` and source files live under `src//src/`. - Preferred includes: - `#include "application/..."` - `#include "domain/..."` @@ -81,6 +82,7 @@ - When changing contribution workflow files, keep `CONTRIBUTING.md` and `.github/` files aligned. ## Docs +- After any changes to the project, ensure the documents remain consistent. - Architecture notes: `docs/프로젝트 구조.md` - Project workflow notes: `docs/GitHub Project.md` - Requirements and overview docs are under `docs/`. diff --git a/CMakeLists.txt b/CMakeLists.txt index 6098b27..e786ff4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,8 +22,6 @@ if (SAFECROWD_BUILD_APP) qt_standard_project_setup() endif() -set(SAFECROWD_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") - function(configure_project_target target_name) if (MSVC) target_compile_options(${target_name} PRIVATE /W4 /permissive-) @@ -35,32 +33,35 @@ function(configure_project_target target_name) endfunction() add_library(ecs_engine STATIC - src/engine/EngineConfig.h - src/engine/EngineRuntime.cpp - src/engine/EngineRuntime.h - src/engine/EngineState.h - src/engine/EngineStats.h - src/engine/EngineStepContext.h - src/engine/EngineSystem.h - src/engine/FrameClock.cpp - src/engine/FrameClock.h + src/engine/include/engine/Entity.h + src/engine/include/engine/EntityRegistry.h + src/engine/include/engine/EngineConfig.h + src/engine/include/engine/EngineRuntime.h + src/engine/include/engine/EngineState.h + src/engine/include/engine/EngineStats.h + src/engine/include/engine/EngineStepContext.h + src/engine/include/engine/EngineSystem.h + src/engine/include/engine/FrameClock.h + src/engine/src/EntityRegistry.cpp + src/engine/src/EngineRuntime.cpp + src/engine/src/FrameClock.cpp ) target_include_directories(ecs_engine PUBLIC - ${SAFECROWD_SRC_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/src/engine/include ) configure_project_target(ecs_engine) add_library(safecrowd_domain STATIC - src/domain/SafeCrowdDomain.cpp - src/domain/SafeCrowdDomain.h + src/domain/include/domain/SafeCrowdDomain.h + src/domain/src/SafeCrowdDomain.cpp ) target_include_directories(safecrowd_domain PUBLIC - ${SAFECROWD_SRC_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/src/domain/include ) target_link_libraries(safecrowd_domain @@ -74,6 +75,7 @@ if (BUILD_TESTING) add_executable(safecrowd_tests tests/TestMain.cpp tests/TestSupport.h + tests/EntityRegistryTests.cpp tests/FrameClockTests.cpp tests/EngineRuntimeTests.cpp tests/SafeCrowdDomainTests.cpp @@ -81,7 +83,6 @@ if (BUILD_TESTING) target_include_directories(safecrowd_tests PRIVATE - ${SAFECROWD_SRC_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/tests ) @@ -97,14 +98,20 @@ endif() if (SAFECROWD_BUILD_APP) add_executable(safecrowd_app - src/application/main.cpp - src/application/MainWindow.cpp - src/application/MainWindow.h + src/application/include/application/MainWindow.h + src/application/src/main.cpp + src/application/src/MainWindow.cpp ) target_include_directories(safecrowd_app PRIVATE - ${SAFECROWD_SRC_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/src/application/include + ) + + set_target_properties(safecrowd_app PROPERTIES + AUTOMOC OFF + AUTOUIC OFF + AUTORCC OFF ) target_link_libraries(safecrowd_app diff --git a/docs/GitHub Project.md b/docs/GitHub Project.md index 60928bd..79a6b69 100644 --- a/docs/GitHub Project.md +++ b/docs/GitHub Project.md @@ -60,5 +60,7 @@ - `Docs`, `Chore`, `Analysis`는 `Lightweight Task` form으로 가볍게 등록한다. - `Engine`, `Domain`, `Application`, `Build`는 `Implementation Task` form으로 범위와 검증 계획까지 남긴다. - 세부 작업명, 부모-자식 관계, dependency는 GitHub Project와 issue 자체를 기준으로 관리한다. +- `blocked by`는 실제로 선행 해결이 필요한 hard dependency에만 건다. 단순한 권장 순서나 같은 Epic 안의 묶음 관계 때문에 불필요하게 직렬화하지 않는다. +- 하나의 Task가 서로 다른 관심사를 함께 묶어 병렬 진행을 막으면, 별도 Task로 분리해서 dependency를 다시 연결한다. - 문서 또는 기여 정책만 다루는 PR은 별도 issue 없이 진행할 수 있다. - Task의 순서는 제목 접두사 뒤 숫자로 관리하지 않는다. 중간 작업이 생기면 새 issue를 추가하고 `Sprint`, `Parent issue`, `blocked by`로 위치를 표현한다. diff --git "a/docs/\355\224\204\353\241\234\354\240\235\355\212\270 \352\265\254\354\241\260.md" "b/docs/\355\224\204\353\241\234\354\240\235\355\212\270 \352\265\254\354\241\260.md" index 8d26d07..5071cd2 100644 --- "a/docs/\355\224\204\353\241\234\354\240\235\355\212\270 \352\265\254\354\241\260.md" +++ "b/docs/\355\224\204\353\241\234\354\240\235\355\212\270 \352\265\254\354\241\260.md" @@ -44,8 +44,14 @@ Project/ vcpkg.json src/ engine/ + include/engine/ + src/ domain/ + include/domain/ + src/ application/ + include/application/ + src/ external/ docs/ ``` @@ -174,12 +180,12 @@ Qt viewport가 엔진 렌더러와 직접 연결되어야 하면 `application -> ### 7.1. 소스 루트 원칙 - 모든 C++ 소스와 헤더는 `src/` 아래에 둔다. -- include 기준 루트도 `src/`로 통일한다. +- 각 계층은 `src//include//`에 헤더를, `src//src/`에 구현 파일을 둔다. - 따라서 include는 `#include "engine/..."`, `#include "domain/..."`, `#include "application/..."` 형태를 사용한다. ### 7.2. CMake 원칙 - `CMakeLists.txt`의 타깃 소스 경로는 항상 `src/...`를 기준으로 적는다. -- 각 타깃의 include directory 역시 `src/`를 공개 루트로 잡는다. +- 각 타깃의 include directory는 해당 계층의 `src//include/`를 공개 루트로 잡는다. - 계층이 늘어나도 당분간 루트 `CMakeLists.txt` 하나에서 관리하고, 타깃 수와 파일 수가 커질 때만 하위 `CMakeLists.txt`를 추가한다. ### 7.3. 외부 의존성 원칙 diff --git a/src/application/MainWindow.h b/src/application/include/application/MainWindow.h similarity index 100% rename from src/application/MainWindow.h rename to src/application/include/application/MainWindow.h diff --git a/src/application/MainWindow.cpp b/src/application/src/MainWindow.cpp similarity index 100% rename from src/application/MainWindow.cpp rename to src/application/src/MainWindow.cpp diff --git a/src/application/main.cpp b/src/application/src/main.cpp similarity index 100% rename from src/application/main.cpp rename to src/application/src/main.cpp diff --git a/src/domain/SafeCrowdDomain.h b/src/domain/include/domain/SafeCrowdDomain.h similarity index 100% rename from src/domain/SafeCrowdDomain.h rename to src/domain/include/domain/SafeCrowdDomain.h diff --git a/src/domain/SafeCrowdDomain.cpp b/src/domain/src/SafeCrowdDomain.cpp similarity index 100% rename from src/domain/SafeCrowdDomain.cpp rename to src/domain/src/SafeCrowdDomain.cpp diff --git a/src/engine/EngineConfig.h b/src/engine/include/engine/EngineConfig.h similarity index 100% rename from src/engine/EngineConfig.h rename to src/engine/include/engine/EngineConfig.h diff --git a/src/engine/EngineRuntime.h b/src/engine/include/engine/EngineRuntime.h similarity index 100% rename from src/engine/EngineRuntime.h rename to src/engine/include/engine/EngineRuntime.h diff --git a/src/engine/EngineState.h b/src/engine/include/engine/EngineState.h similarity index 100% rename from src/engine/EngineState.h rename to src/engine/include/engine/EngineState.h diff --git a/src/engine/EngineStats.h b/src/engine/include/engine/EngineStats.h similarity index 100% rename from src/engine/EngineStats.h rename to src/engine/include/engine/EngineStats.h diff --git a/src/engine/EngineStepContext.h b/src/engine/include/engine/EngineStepContext.h similarity index 100% rename from src/engine/EngineStepContext.h rename to src/engine/include/engine/EngineStepContext.h diff --git a/src/engine/EngineSystem.h b/src/engine/include/engine/EngineSystem.h similarity index 100% rename from src/engine/EngineSystem.h rename to src/engine/include/engine/EngineSystem.h diff --git a/src/engine/include/engine/Entity.h b/src/engine/include/engine/Entity.h new file mode 100644 index 0000000..63e31b5 --- /dev/null +++ b/src/engine/include/engine/Entity.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include + +namespace safecrowd::engine { + +using EntityIndex = std::uint32_t; +using EntityGeneration = std::uint32_t; + +struct Entity { + static constexpr EntityIndex invalidIndex = std::numeric_limits::max(); + + EntityIndex index{invalidIndex}; + EntityGeneration generation{0}; + + [[nodiscard]] constexpr bool isValid() const noexcept { + return index != invalidIndex; + } + + [[nodiscard]] static constexpr Entity invalid() noexcept { + return {}; + } + + auto operator<=>(const Entity&) const = default; +}; + +inline std::ostream& operator<<(std::ostream& stream, const Entity& entity) { + stream << "Entity{index=" << entity.index << ", generation=" << entity.generation << "}"; + return stream; +} + +} // namespace safecrowd::engine diff --git a/src/engine/include/engine/EntityRegistry.h b/src/engine/include/engine/EntityRegistry.h new file mode 100644 index 0000000..4d40289 --- /dev/null +++ b/src/engine/include/engine/EntityRegistry.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include + +#include "engine/Entity.h" + +namespace safecrowd::engine { + +inline constexpr std::size_t kMaxComponentTypes = 64; +using Signature = std::bitset; + +class EntityRegistry { +public: + explicit EntityRegistry(std::size_t maxEntityCount = 4096); + + [[nodiscard]] Entity allocate(); + void release(Entity entity); + [[nodiscard]] bool isAlive(Entity entity) const noexcept; + void setSignature(Entity entity, Signature signature); + [[nodiscard]] Signature signatureOf(Entity entity) const; + +private: + struct Entry { + EntityGeneration generation{0}; + bool alive{false}; + Signature signature{}; + }; + + [[nodiscard]] const Entry& entryFor(Entity entity) const; + [[nodiscard]] Entry& entryFor(Entity entity); + + std::vector entries_; + std::deque freeIndices_; +}; + +} // namespace safecrowd::engine diff --git a/src/engine/FrameClock.h b/src/engine/include/engine/FrameClock.h similarity index 100% rename from src/engine/FrameClock.h rename to src/engine/include/engine/FrameClock.h diff --git a/src/engine/EngineRuntime.cpp b/src/engine/src/EngineRuntime.cpp similarity index 100% rename from src/engine/EngineRuntime.cpp rename to src/engine/src/EngineRuntime.cpp diff --git a/src/engine/src/EntityRegistry.cpp b/src/engine/src/EntityRegistry.cpp new file mode 100644 index 0000000..756c44e --- /dev/null +++ b/src/engine/src/EntityRegistry.cpp @@ -0,0 +1,95 @@ +#include "engine/EntityRegistry.h" + +#include +#include +#include + +namespace safecrowd::engine { +namespace { + +std::string describeEntity(Entity entity) { + std::ostringstream stream; + stream << entity; + return stream.str(); +} + +} // namespace + +EntityRegistry::EntityRegistry(std::size_t maxEntityCount) + : entries_(maxEntityCount) { + freeIndices_.resize(maxEntityCount); + + for (std::size_t index = 0; index < maxEntityCount; ++index) { + freeIndices_[index] = static_cast(index); + } +} + +Entity EntityRegistry::allocate() { + if (freeIndices_.empty()) { + throw std::overflow_error("EntityRegistry capacity exhausted."); + } + + const EntityIndex index = freeIndices_.front(); + freeIndices_.pop_front(); + + Entry& entry = entries_[index]; + entry.alive = true; + entry.signature.reset(); + + return Entity{index, entry.generation}; +} + +void EntityRegistry::release(Entity entity) { + Entry& entry = entryFor(entity); + entry.alive = false; + entry.signature.reset(); + ++entry.generation; + freeIndices_.push_back(entity.index); +} + +bool EntityRegistry::isAlive(Entity entity) const noexcept { + if (!entity.isValid()) { + return false; + } + + const auto index = static_cast(entity.index); + if (index >= entries_.size()) { + return false; + } + + const Entry& entry = entries_[index]; + return entry.alive && entry.generation == entity.generation; +} + +void EntityRegistry::setSignature(Entity entity, Signature signature) { + Entry& entry = entryFor(entity); + entry.signature = signature; +} + +Signature EntityRegistry::signatureOf(Entity entity) const { + return entryFor(entity).signature; +} + +const EntityRegistry::Entry& EntityRegistry::entryFor(Entity entity) const { + if (!entity.isValid()) { + throw std::invalid_argument("Invalid entity handle."); + } + + const auto index = static_cast(entity.index); + if (index >= entries_.size()) { + throw std::out_of_range("Entity index is out of range."); + } + + const Entry& entry = entries_[index]; + if (!entry.alive || entry.generation != entity.generation) { + throw std::invalid_argument("Stale or dead entity handle: " + describeEntity(entity)); + } + + return entry; +} + +EntityRegistry::Entry& EntityRegistry::entryFor(Entity entity) { + return const_cast(std::as_const(*this).entryFor(entity)); +} + +} // namespace safecrowd::engine diff --git a/src/engine/FrameClock.cpp b/src/engine/src/FrameClock.cpp similarity index 100% rename from src/engine/FrameClock.cpp rename to src/engine/src/FrameClock.cpp diff --git a/tests/EntityRegistryTests.cpp b/tests/EntityRegistryTests.cpp new file mode 100644 index 0000000..f1a0307 --- /dev/null +++ b/tests/EntityRegistryTests.cpp @@ -0,0 +1,59 @@ +#include + +#include "TestSupport.h" + +#include "engine/EntityRegistry.h" + +SC_TEST(EntityRegistryReusesIndexWithNewGeneration) { + safecrowd::engine::EntityRegistry registry(1); + + const auto first = registry.allocate(); + SC_EXPECT_TRUE(registry.isAlive(first)); + + registry.release(first); + SC_EXPECT_TRUE(!registry.isAlive(first)); + + const auto second = registry.allocate(); + SC_EXPECT_EQ(second.index, first.index); + SC_EXPECT_TRUE(second.generation > first.generation); + SC_EXPECT_TRUE(registry.isAlive(second)); +} + +SC_TEST(EntityRegistryRejectsStaleEntityHandles) { + safecrowd::engine::EntityRegistry registry(1); + + const auto entity = registry.allocate(); + registry.release(entity); + + bool threwOnRelease = false; + try { + registry.release(entity); + } catch (const std::invalid_argument&) { + threwOnRelease = true; + } + + SC_EXPECT_TRUE(threwOnRelease); + + bool threwOnSignatureRead = false; + try { + static_cast(registry.signatureOf(entity)); + } catch (const std::invalid_argument&) { + threwOnSignatureRead = true; + } + + SC_EXPECT_TRUE(threwOnSignatureRead); +} + +SC_TEST(EntityRegistryStoresSignaturesPerLiveEntity) { + safecrowd::engine::EntityRegistry registry(1); + + const auto entity = registry.allocate(); + safecrowd::engine::Signature signature; + signature.set(1); + signature.set(5); + + registry.setSignature(entity, signature); + + const auto stored = registry.signatureOf(entity); + SC_EXPECT_EQ(stored, signature); +} From 209508bbc31a95f29179c6ab8070073dd4d9c656 Mon Sep 17 00:00:00 2001 From: learncold Date: Mon, 23 Mar 2026 17:44:15 +0900 Subject: [PATCH 2/2] [Build] remove ECS implementation from structure branch --- CMakeLists.txt | 4 - src/engine/include/engine/Entity.h | 35 -------- src/engine/include/engine/EntityRegistry.h | 39 --------- src/engine/src/EntityRegistry.cpp | 95 ---------------------- tests/EntityRegistryTests.cpp | 59 -------------- 5 files changed, 232 deletions(-) delete mode 100644 src/engine/include/engine/Entity.h delete mode 100644 src/engine/include/engine/EntityRegistry.h delete mode 100644 src/engine/src/EntityRegistry.cpp delete mode 100644 tests/EntityRegistryTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e786ff4..9d56db7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,8 +33,6 @@ function(configure_project_target target_name) endfunction() add_library(ecs_engine STATIC - src/engine/include/engine/Entity.h - src/engine/include/engine/EntityRegistry.h src/engine/include/engine/EngineConfig.h src/engine/include/engine/EngineRuntime.h src/engine/include/engine/EngineState.h @@ -42,7 +40,6 @@ add_library(ecs_engine STATIC src/engine/include/engine/EngineStepContext.h src/engine/include/engine/EngineSystem.h src/engine/include/engine/FrameClock.h - src/engine/src/EntityRegistry.cpp src/engine/src/EngineRuntime.cpp src/engine/src/FrameClock.cpp ) @@ -75,7 +72,6 @@ if (BUILD_TESTING) add_executable(safecrowd_tests tests/TestMain.cpp tests/TestSupport.h - tests/EntityRegistryTests.cpp tests/FrameClockTests.cpp tests/EngineRuntimeTests.cpp tests/SafeCrowdDomainTests.cpp diff --git a/src/engine/include/engine/Entity.h b/src/engine/include/engine/Entity.h deleted file mode 100644 index 63e31b5..0000000 --- a/src/engine/include/engine/Entity.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace safecrowd::engine { - -using EntityIndex = std::uint32_t; -using EntityGeneration = std::uint32_t; - -struct Entity { - static constexpr EntityIndex invalidIndex = std::numeric_limits::max(); - - EntityIndex index{invalidIndex}; - EntityGeneration generation{0}; - - [[nodiscard]] constexpr bool isValid() const noexcept { - return index != invalidIndex; - } - - [[nodiscard]] static constexpr Entity invalid() noexcept { - return {}; - } - - auto operator<=>(const Entity&) const = default; -}; - -inline std::ostream& operator<<(std::ostream& stream, const Entity& entity) { - stream << "Entity{index=" << entity.index << ", generation=" << entity.generation << "}"; - return stream; -} - -} // namespace safecrowd::engine diff --git a/src/engine/include/engine/EntityRegistry.h b/src/engine/include/engine/EntityRegistry.h deleted file mode 100644 index 4d40289..0000000 --- a/src/engine/include/engine/EntityRegistry.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "engine/Entity.h" - -namespace safecrowd::engine { - -inline constexpr std::size_t kMaxComponentTypes = 64; -using Signature = std::bitset; - -class EntityRegistry { -public: - explicit EntityRegistry(std::size_t maxEntityCount = 4096); - - [[nodiscard]] Entity allocate(); - void release(Entity entity); - [[nodiscard]] bool isAlive(Entity entity) const noexcept; - void setSignature(Entity entity, Signature signature); - [[nodiscard]] Signature signatureOf(Entity entity) const; - -private: - struct Entry { - EntityGeneration generation{0}; - bool alive{false}; - Signature signature{}; - }; - - [[nodiscard]] const Entry& entryFor(Entity entity) const; - [[nodiscard]] Entry& entryFor(Entity entity); - - std::vector entries_; - std::deque freeIndices_; -}; - -} // namespace safecrowd::engine diff --git a/src/engine/src/EntityRegistry.cpp b/src/engine/src/EntityRegistry.cpp deleted file mode 100644 index 756c44e..0000000 --- a/src/engine/src/EntityRegistry.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "engine/EntityRegistry.h" - -#include -#include -#include - -namespace safecrowd::engine { -namespace { - -std::string describeEntity(Entity entity) { - std::ostringstream stream; - stream << entity; - return stream.str(); -} - -} // namespace - -EntityRegistry::EntityRegistry(std::size_t maxEntityCount) - : entries_(maxEntityCount) { - freeIndices_.resize(maxEntityCount); - - for (std::size_t index = 0; index < maxEntityCount; ++index) { - freeIndices_[index] = static_cast(index); - } -} - -Entity EntityRegistry::allocate() { - if (freeIndices_.empty()) { - throw std::overflow_error("EntityRegistry capacity exhausted."); - } - - const EntityIndex index = freeIndices_.front(); - freeIndices_.pop_front(); - - Entry& entry = entries_[index]; - entry.alive = true; - entry.signature.reset(); - - return Entity{index, entry.generation}; -} - -void EntityRegistry::release(Entity entity) { - Entry& entry = entryFor(entity); - entry.alive = false; - entry.signature.reset(); - ++entry.generation; - freeIndices_.push_back(entity.index); -} - -bool EntityRegistry::isAlive(Entity entity) const noexcept { - if (!entity.isValid()) { - return false; - } - - const auto index = static_cast(entity.index); - if (index >= entries_.size()) { - return false; - } - - const Entry& entry = entries_[index]; - return entry.alive && entry.generation == entity.generation; -} - -void EntityRegistry::setSignature(Entity entity, Signature signature) { - Entry& entry = entryFor(entity); - entry.signature = signature; -} - -Signature EntityRegistry::signatureOf(Entity entity) const { - return entryFor(entity).signature; -} - -const EntityRegistry::Entry& EntityRegistry::entryFor(Entity entity) const { - if (!entity.isValid()) { - throw std::invalid_argument("Invalid entity handle."); - } - - const auto index = static_cast(entity.index); - if (index >= entries_.size()) { - throw std::out_of_range("Entity index is out of range."); - } - - const Entry& entry = entries_[index]; - if (!entry.alive || entry.generation != entity.generation) { - throw std::invalid_argument("Stale or dead entity handle: " + describeEntity(entity)); - } - - return entry; -} - -EntityRegistry::Entry& EntityRegistry::entryFor(Entity entity) { - return const_cast(std::as_const(*this).entryFor(entity)); -} - -} // namespace safecrowd::engine diff --git a/tests/EntityRegistryTests.cpp b/tests/EntityRegistryTests.cpp deleted file mode 100644 index f1a0307..0000000 --- a/tests/EntityRegistryTests.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include - -#include "TestSupport.h" - -#include "engine/EntityRegistry.h" - -SC_TEST(EntityRegistryReusesIndexWithNewGeneration) { - safecrowd::engine::EntityRegistry registry(1); - - const auto first = registry.allocate(); - SC_EXPECT_TRUE(registry.isAlive(first)); - - registry.release(first); - SC_EXPECT_TRUE(!registry.isAlive(first)); - - const auto second = registry.allocate(); - SC_EXPECT_EQ(second.index, first.index); - SC_EXPECT_TRUE(second.generation > first.generation); - SC_EXPECT_TRUE(registry.isAlive(second)); -} - -SC_TEST(EntityRegistryRejectsStaleEntityHandles) { - safecrowd::engine::EntityRegistry registry(1); - - const auto entity = registry.allocate(); - registry.release(entity); - - bool threwOnRelease = false; - try { - registry.release(entity); - } catch (const std::invalid_argument&) { - threwOnRelease = true; - } - - SC_EXPECT_TRUE(threwOnRelease); - - bool threwOnSignatureRead = false; - try { - static_cast(registry.signatureOf(entity)); - } catch (const std::invalid_argument&) { - threwOnSignatureRead = true; - } - - SC_EXPECT_TRUE(threwOnSignatureRead); -} - -SC_TEST(EntityRegistryStoresSignaturesPerLiveEntity) { - safecrowd::engine::EntityRegistry registry(1); - - const auto entity = registry.allocate(); - safecrowd::engine::Signature signature; - signature.set(1); - signature.set(5); - - registry.setSignature(entity, signature); - - const auto stored = registry.signatureOf(entity); - SC_EXPECT_EQ(stored, signature); -}