feat(ecs): add convenient Entity class
This commit is contained in:
parent
a58b0c030f
commit
b6834310a7
4 changed files with 96 additions and 43 deletions
|
@ -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<Entity> {};
|
||||
auto set = std::unordered_set<EntityId> {};
|
||||
|
||||
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<Entity> {};
|
||||
auto entities = std::vector<EntityId> {};
|
||||
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<Component>([&](Registry &, Entity) {});
|
||||
registry.connect_on_destruct<Component>([&](Registry &, Entity) {});
|
||||
registry.connect_on_construct<Component>([&](Registry &, EntityId) {});
|
||||
registry.connect_on_destruct<Component>([&](Registry &, EntityId) {});
|
||||
};
|
||||
|
||||
Case { "on_construct/on_destruct won't get called on unrelated component" } = [] {
|
||||
auto registry = Registry {};
|
||||
registry.connect_on_construct<Component>([&](Registry &, Entity) { expect_unreachable(); });
|
||||
registry.connect_on_destruct<Component>([&](Registry &, Entity) { expect_unreachable(); });
|
||||
registry.connect_on_construct<Component>([&](Registry &, EntityId) {
|
||||
expect_unreachable();
|
||||
});
|
||||
registry.connect_on_destruct<Component>([&](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<Entity> {};
|
||||
auto on_construct_called = std::vector<Entity> {};
|
||||
auto on_destruct_called = std::vector<Entity> {};
|
||||
auto all_entities = std::vector<EntityId> {};
|
||||
auto on_construct_called = std::vector<EntityId> {};
|
||||
auto on_destruct_called = std::vector<EntityId> {};
|
||||
|
||||
registry.connect_on_construct<Component>([&](Registry &, Entity entity) {
|
||||
registry.connect_on_construct<Component>([&](Registry &, EntityId entity) {
|
||||
on_construct_called.emplace_back(entity);
|
||||
});
|
||||
registry.connect_on_destruct<Component>([&](Registry &, Entity entity) {
|
||||
registry.connect_on_destruct<Component>([&](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<Entity, Component> {};
|
||||
auto entities_a = std::vector<Entity> {};
|
||||
auto component_map_a = std::unordered_map<EntityId, Component> {};
|
||||
auto entities_a = std::vector<EntityId> {};
|
||||
|
||||
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<lt::ecs::Entity, Component_B> {};
|
||||
auto component_map_b = std::unordered_map<lt::ecs::EntityId, Component_B> {};
|
||||
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<Component>([&](Entity entity, Component &component) {
|
||||
registry.each<Component>([&](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<Component_B>([&](Entity entity, Component_B &component) {
|
||||
registry.each<Component_B>([&](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<Component, Component_B>(
|
||||
[&](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<Entity, Component> {};
|
||||
auto entities_a = std::vector<Entity> {};
|
||||
auto component_map_a = std::unordered_map<EntityId, Component> {};
|
||||
auto entities_a = std::vector<EntityId> {};
|
||||
|
||||
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<Entity, Component_B> {};
|
||||
auto component_map_b = std::unordered_map<EntityId, Component_B> {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = Entity {};
|
||||
auto entity = EntityId {};
|
||||
if (idx % 3 == 0)
|
||||
{
|
||||
entity = entities_a[idx];
|
||||
|
|
|
@ -1,5 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt {
|
||||
#include <ecs/registry.hpp>
|
||||
|
||||
} // namespace lt
|
||||
namespace lt::ecs {
|
||||
|
||||
/** High-level entity convenience wrapper */
|
||||
class Entity
|
||||
{
|
||||
public:
|
||||
Entity(Ref<Registry> registry, EntityId identifier)
|
||||
: m_registry(std::move(registry))
|
||||
, m_identifier(identifier)
|
||||
{
|
||||
ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier);
|
||||
}
|
||||
|
||||
template<typename Component_T>
|
||||
auto get() -> Component_T &
|
||||
{
|
||||
return m_registry->get<Component_T>(m_identifier);
|
||||
}
|
||||
|
||||
template<typename Component_T>
|
||||
auto get() const -> const Component_T &
|
||||
{
|
||||
return m_registry->get<Component_T>(m_identifier);
|
||||
}
|
||||
|
||||
auto get_registry() -> Ref<Registry>
|
||||
{
|
||||
return m_registry;
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<Registry> m_registry;
|
||||
|
||||
EntityId m_identifier;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt::ecs
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
namespace lt::ecs {
|
||||
|
||||
using Entity = uint32_t;
|
||||
using EntityId = uint32_t;
|
||||
|
||||
constexpr auto null_entity = std::numeric_limits<Entity>::max();
|
||||
constexpr auto null_entity = std::numeric_limits<EntityId>::max();
|
||||
|
||||
/** A registry of components, the heart of an ECS architecture.
|
||||
*
|
||||
|
@ -24,9 +24,9 @@ constexpr auto null_entity = std::numeric_limits<Entity>::max();
|
|||
class Registry
|
||||
{
|
||||
public:
|
||||
using UnderlyingSparseSet_T = TypeErasedSparseSet<Entity>;
|
||||
using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>;
|
||||
|
||||
using Callback_T = std::function<void(Registry &, Entity)>;
|
||||
using Callback_T = std::function<void(Registry &, EntityId)>;
|
||||
|
||||
template<typename Component_T>
|
||||
void connect_on_construct(Callback_T callback)
|
||||
|
@ -52,13 +52,13 @@ public:
|
|||
m_on_destruct_hooks.erase(get_type_id<Component_T>());
|
||||
}
|
||||
|
||||
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<typename Component_T>
|
||||
auto get(Entity entity) -> Component_T &
|
||||
auto get(EntityId entity) const -> const Component_T &
|
||||
{
|
||||
auto &derived_set = get_derived_set<Component_T>();
|
||||
return derived_set.at(entity).second;
|
||||
}
|
||||
|
||||
template<typename Component_T>
|
||||
auto add(Entity entity, Component_T component) -> Component_T &
|
||||
auto get(EntityId entity) -> Component_T &
|
||||
{
|
||||
auto &derived_set = get_derived_set<Component_T>();
|
||||
return derived_set.at(entity).second;
|
||||
}
|
||||
|
||||
template<typename Component_T>
|
||||
auto add(EntityId entity, Component_T component) -> Component_T &
|
||||
{
|
||||
auto &derived_set = get_derived_set<Component_T>();
|
||||
auto &added_component = derived_set.insert(entity, std::move(component)).second;
|
||||
|
@ -90,7 +97,7 @@ public:
|
|||
};
|
||||
|
||||
template<typename Component_T>
|
||||
void remove(Entity entity)
|
||||
void remove(EntityId entity)
|
||||
{
|
||||
if (m_on_destruct_hooks.contains(get_type_id<Component_T>()))
|
||||
{
|
||||
|
@ -102,18 +109,18 @@ public:
|
|||
}
|
||||
|
||||
template<typename Component_T>
|
||||
auto view() -> SparseSet<Component_T, Entity> &
|
||||
auto view() -> SparseSet<Component_T, EntityId> &
|
||||
{
|
||||
return get_derived_set<Component_T>();
|
||||
};
|
||||
|
||||
template<typename ComponentA_T, typename ComponentB_T>
|
||||
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
||||
auto view() -> std::vector<std::tuple<Entity, ComponentA_T &, ComponentB_T &>>
|
||||
auto view() -> std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>>
|
||||
{
|
||||
auto &set_a = get_derived_set<ComponentA_T>();
|
||||
auto &set_b = get_derived_set<ComponentB_T>();
|
||||
auto view = std::vector<std::tuple<Entity, ComponentA_T &, ComponentB_T &>> {};
|
||||
auto view = std::vector<std::tuple<EntityId, ComponentA_T &, ComponentB_T &>> {};
|
||||
|
||||
/* iterate over the "smaller" component-set, and check if its entities have the other
|
||||
* component */
|
||||
|
@ -142,7 +149,7 @@ public:
|
|||
};
|
||||
|
||||
template<typename Component_T>
|
||||
void each(std::function<void(Entity, Component_T &)> functor)
|
||||
void each(std::function<void(EntityId, Component_T &)> functor)
|
||||
{
|
||||
for (auto &[entity, component] : get_derived_set<Component_T>())
|
||||
{
|
||||
|
@ -152,7 +159,7 @@ public:
|
|||
|
||||
template<typename ComponentA_T, typename ComponentB_T>
|
||||
requires(!std::is_same_v<ComponentA_T, ComponentB_T>)
|
||||
void each(std::function<void(Entity, ComponentA_T &, ComponentB_T &)> functor)
|
||||
void each(std::function<void(EntityId, ComponentA_T &, ComponentB_T &)> functor)
|
||||
{
|
||||
auto &set_a = get_derived_set<ComponentA_T>();
|
||||
auto &set_b = get_derived_set<ComponentB_T>();
|
||||
|
@ -223,22 +230,22 @@ private:
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
auto get_derived_set() -> SparseSet<T, Entity> &
|
||||
auto get_derived_set() -> SparseSet<T, EntityId> &
|
||||
{
|
||||
constexpr auto type_id = get_type_id<T>();
|
||||
if (!m_sparsed_sets.contains(type_id))
|
||||
{
|
||||
m_sparsed_sets[type_id] = create_scope<SparseSet<T, Entity>>();
|
||||
m_sparsed_sets[type_id] = create_scope<SparseSet<T, EntityId>>();
|
||||
}
|
||||
|
||||
auto *base_set = m_sparsed_sets[type_id].get();
|
||||
auto *derived_set = dynamic_cast<SparseSet<T, Entity> *>(base_set);
|
||||
auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set);
|
||||
ensure(derived_set, "Failed to downcast to derived set");
|
||||
|
||||
return *derived_set;
|
||||
}
|
||||
|
||||
Entity m_current;
|
||||
EntityId m_current;
|
||||
|
||||
TypeId m_entity_count;
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Add table
Reference in a new issue