diff --git a/modules/ecs/private/registry.test.cpp b/modules/ecs/private/registry.test.cpp index e6d2a82..c88b185 100644 --- a/modules/ecs/private/registry.test.cpp +++ b/modules/ecs/private/registry.test.cpp @@ -12,7 +12,7 @@ using lt::test::expect_eq; using lt::test::expect_false; using lt::test::expect_true; -using lt::ecs::Entity; +using lt::ecs::EntityId; using lt::ecs::Registry; struct Component @@ -86,7 +86,7 @@ Suite raii = [] { Suite entity_raii = [] { Case { "create_entity returns unique values" } = [] { auto registry = Registry {}; - auto set = std::unordered_set {}; + auto set = std::unordered_set {}; for (auto idx : std::views::iota(0, 10'000)) { @@ -101,7 +101,7 @@ Suite entity_raii = [] { Case { "post create/destroy_entity has correct state" } = [] { auto registry = Registry {}; - auto entities = std::vector {}; + auto entities = std::vector {}; for (auto idx : std::views::iota(0, 10'000)) { entities.emplace_back(registry.create_entity()); @@ -154,14 +154,18 @@ Suite component_raii = [] { Suite callbacks = [] { Case { "connecting on_construct/on_destruct won't throw" } = [] { auto registry = Registry {}; - registry.connect_on_construct([&](Registry &, Entity) {}); - registry.connect_on_destruct([&](Registry &, Entity) {}); + registry.connect_on_construct([&](Registry &, EntityId) {}); + registry.connect_on_destruct([&](Registry &, EntityId) {}); }; Case { "on_construct/on_destruct won't get called on unrelated component" } = [] { auto registry = Registry {}; - registry.connect_on_construct([&](Registry &, Entity) { expect_unreachable(); }); - registry.connect_on_destruct([&](Registry &, Entity) { expect_unreachable(); }); + registry.connect_on_construct([&](Registry &, EntityId) { + expect_unreachable(); + }); + registry.connect_on_destruct([&](Registry &, EntityId) { + expect_unreachable(); + }); for (auto idx : std::views::iota(0, 100'000)) { @@ -171,14 +175,14 @@ Suite callbacks = [] { Case { "on_construct/on_destruct gets called" } = [] { auto registry = Registry {}; - auto all_entities = std::vector {}; - auto on_construct_called = std::vector {}; - auto on_destruct_called = std::vector {}; + auto all_entities = std::vector {}; + auto on_construct_called = std::vector {}; + auto on_destruct_called = std::vector {}; - registry.connect_on_construct([&](Registry &, Entity entity) { + registry.connect_on_construct([&](Registry &, EntityId entity) { on_construct_called.emplace_back(entity); }); - registry.connect_on_destruct([&](Registry &, Entity entity) { + registry.connect_on_destruct([&](Registry &, EntityId entity) { on_destruct_called.emplace_back(entity); }); @@ -206,8 +210,8 @@ Suite each = [] { auto shared_entity_counter = 0u; - auto component_map_a = std::unordered_map {}; - auto entities_a = std::vector {}; + auto component_map_a = std::unordered_map {}; + auto entities_a = std::vector {}; for (auto idx : std::views::iota(0, 10'000)) { @@ -220,10 +224,10 @@ Suite each = [] { component_map_a[entity] = component; } - auto component_map_b = std::unordered_map {}; + auto component_map_b = std::unordered_map {}; for (auto idx : std::views::iota(0, 10'000)) { - auto entity = Entity {}; + auto entity = EntityId {}; if (idx % 3 == 0) { entity = entities_a[idx]; @@ -243,7 +247,7 @@ Suite each = [] { Case { "each one element" } = [&] { auto counter = 0u; - registry.each([&](Entity entity, Component &component) { + registry.each([&](EntityId entity, Component &component) { ++counter; expect_eq(component_map_a[entity], component); }); @@ -251,7 +255,7 @@ Suite each = [] { expect_eq(component_map_a.size(), counter); counter = 0u; - registry.each([&](Entity entity, Component_B &component) { + registry.each([&](EntityId entity, Component_B &component) { ++counter; expect_eq(component_map_b[entity], component); }); @@ -261,7 +265,7 @@ Suite each = [] { Case { "each two element" } = [&] { auto counter = 0u; registry.each( - [&](Entity entity, Component &component_a, Component_B &component_b) { + [&](EntityId entity, Component &component_a, Component_B &component_b) { expect_eq(component_map_a[entity], component_a); expect_eq(component_map_b[entity], component_b); ++counter; @@ -277,8 +281,8 @@ Suite views = [] { auto shared_entity_counter = 0u; - auto component_map_a = std::unordered_map {}; - auto entities_a = std::vector {}; + auto component_map_a = std::unordered_map {}; + auto entities_a = std::vector {}; for (auto idx : std::views::iota(0, 10'000)) { @@ -291,10 +295,10 @@ Suite views = [] { component_map_a[entity] = component; } - auto component_map_b = std::unordered_map {}; + auto component_map_b = std::unordered_map {}; for (auto idx : std::views::iota(0, 10'000)) { - auto entity = Entity {}; + auto entity = EntityId {}; if (idx % 3 == 0) { entity = entities_a[idx]; diff --git a/modules/ecs/public/entity.hpp b/modules/ecs/public/entity.hpp index 6ab0f0b..4c0ea67 100644 --- a/modules/ecs/public/entity.hpp +++ b/modules/ecs/public/entity.hpp @@ -1,5 +1,42 @@ #pragma once -namespace lt { +#include -} // namespace lt +namespace lt::ecs { + +/** High-level entity convenience wrapper */ +class Entity +{ +public: + Entity(Ref registry, EntityId identifier) + : m_registry(std::move(registry)) + , m_identifier(identifier) + { + ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier); + } + + template + auto get() -> Component_T & + { + return m_registry->get(m_identifier); + } + + template + auto get() const -> const Component_T & + { + return m_registry->get(m_identifier); + } + + auto get_registry() -> Ref + { + return m_registry; + } + +private: + Ref m_registry; + + EntityId m_identifier; +}; + + +} // namespace lt::ecs diff --git a/modules/ecs/public/registry.hpp b/modules/ecs/public/registry.hpp index c6c10af..75b3265 100644 --- a/modules/ecs/public/registry.hpp +++ b/modules/ecs/public/registry.hpp @@ -4,9 +4,9 @@ namespace lt::ecs { -using Entity = uint32_t; +using EntityId = uint32_t; -constexpr auto null_entity = std::numeric_limits::max(); +constexpr auto null_entity = std::numeric_limits::max(); /** A registry of components, the heart of an ECS architecture. * @@ -24,9 +24,9 @@ constexpr auto null_entity = std::numeric_limits::max(); class Registry { public: - using UnderlyingSparseSet_T = TypeErasedSparseSet; + using UnderlyingSparseSet_T = TypeErasedSparseSet; - using Callback_T = std::function; + using Callback_T = std::function; template void connect_on_construct(Callback_T callback) @@ -52,13 +52,13 @@ public: m_on_destruct_hooks.erase(get_type_id()); } - auto create_entity() -> Entity + auto create_entity() -> EntityId { ++m_entity_count; return m_current++; } - void destroy_entity(Entity entity) + void destroy_entity(EntityId entity) { for (const auto &[key, set] : m_sparsed_sets) { @@ -69,14 +69,21 @@ public: } template - auto get(Entity entity) -> Component_T & + auto get(EntityId entity) const -> const Component_T & { auto &derived_set = get_derived_set(); return derived_set.at(entity).second; } template - auto add(Entity entity, Component_T component) -> Component_T & + auto get(EntityId entity) -> Component_T & + { + auto &derived_set = get_derived_set(); + return derived_set.at(entity).second; + } + + template + auto add(EntityId entity, Component_T component) -> Component_T & { auto &derived_set = get_derived_set(); auto &added_component = derived_set.insert(entity, std::move(component)).second; @@ -90,7 +97,7 @@ public: }; template - void remove(Entity entity) + void remove(EntityId entity) { if (m_on_destruct_hooks.contains(get_type_id())) { @@ -102,18 +109,18 @@ public: } template - auto view() -> SparseSet & + auto view() -> SparseSet & { return get_derived_set(); }; template requires(!std::is_same_v) - auto view() -> std::vector> + auto view() -> std::vector> { auto &set_a = get_derived_set(); auto &set_b = get_derived_set(); - auto view = std::vector> {}; + auto view = std::vector> {}; /* iterate over the "smaller" component-set, and check if its entities have the other * component */ @@ -142,7 +149,7 @@ public: }; template - void each(std::function functor) + void each(std::function functor) { for (auto &[entity, component] : get_derived_set()) { @@ -152,7 +159,7 @@ public: template requires(!std::is_same_v) - void each(std::function functor) + void each(std::function functor) { auto &set_a = get_derived_set(); auto &set_b = get_derived_set(); @@ -223,22 +230,22 @@ private: } template - auto get_derived_set() -> SparseSet & + auto get_derived_set() -> SparseSet & { constexpr auto type_id = get_type_id(); if (!m_sparsed_sets.contains(type_id)) { - m_sparsed_sets[type_id] = create_scope>(); + m_sparsed_sets[type_id] = create_scope>(); } auto *base_set = m_sparsed_sets[type_id].get(); - auto *derived_set = dynamic_cast *>(base_set); + auto *derived_set = dynamic_cast *>(base_set); ensure(derived_set, "Failed to downcast to derived set"); return *derived_set; } - Entity m_current; + EntityId m_current; TypeId m_entity_count; diff --git a/modules/ecs/public/sparse_set.hpp b/modules/ecs/public/sparse_set.hpp index 833b89b..3b93d1a 100644 --- a/modules/ecs/public/sparse_set.hpp +++ b/modules/ecs/public/sparse_set.hpp @@ -128,6 +128,11 @@ public: return m_dense.end(); } + [[nodiscard]] auto at(Identifier_T identifier) const -> const Dense_T & + { + return m_dense.at(m_sparse.at(identifier)); + } + [[nodiscard]] auto at(Identifier_T identifier) -> Dense_T & { return m_dense.at(m_sparse.at(identifier));