diff --git a/libs/util/include/psemek/util/ecs.hpp b/libs/util/include/psemek/util/ecs.hpp index 43908af0..1f2a69c8 100644 --- a/libs/util/include/psemek/util/ecs.hpp +++ b/libs/util/include/psemek/util/ecs.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -19,11 +20,43 @@ namespace psemek::util namespace ecs_detail { - using handle = std::uint32_t; + using species_handle = std::uint16_t; + + // Entity id within it's species + using entity_id = std::uint32_t; + + using entity_version = std::uint32_t; + + // From highest to lowest bits: + // 12: species id + // 20: entity id (within species) + // 32: entity version + using entity_handle = std::uint64_t; + + struct unpacked_handle + { + species_handle species; + entity_id entity; + entity_version version; + }; + + inline unpacked_handle unpack(entity_handle h) + { + return { + h >> 52, + (h >> 32) & 0xFFFFFu, + h & 0xFFFFFFFFu + }; + } + + inline entity_handle pack(species_handle species, entity_id id, entity_version version) + { + return (static_cast(species) << 52) | (static_cast(id) << 32) | static_cast(version); + } struct species_base { - species_base(std::string name, handle id) + species_base(std::string name, species_handle id) : name_(std::move(name)) , id_(id) {} @@ -45,15 +78,12 @@ namespace psemek::util return reinterpret_cast(get_entity_component(typeid(Component))); } - virtual handle entity_count() const = 0; + virtual entity_id entity_count() const = 0; - virtual handle add_entity() = 0; - virtual void remove_entity(handle h) = 0; + virtual entity_handle add_entity() = 0; + virtual void remove_entity(entity_handle h) = 0; - bool entity_active(handle h) - { - return get_free_list()[h] == h; - } + virtual bool entity_active(entity_handle h) const = 0; template void apply(Behavior & behavior) @@ -63,12 +93,14 @@ namespace psemek::util virtual ~species_base() {} - virtual handle const * get_free_list() = 0; - virtual std::size_t list_size() const = 0; + virtual entity_id const * get_free_list() const = 0; + virtual entity_id list_size() const = 0; - private: + virtual entity_version const * get_version_list() const = 0; + + protected: std::string name_; - handle id_; + species_handle id_; template void apply_impl(Behavior & behavior, std::tuple *, std::index_sequence) @@ -86,8 +118,6 @@ namespace psemek::util return; typename Behavior::context ctx; - ctx.species = id_; - ctx.species_name = name_; ((std::get(ctx.components) = get_species_component()), ...); @@ -113,19 +143,21 @@ namespace psemek::util if (list) { // sparse - std::size_t const size = list_size(); - for (std::size_t i = 0; i < size; ++i) + auto const size = list_size(); + auto version = get_version_list(); + for (entity_id i = 0; i < size; ++i) { if (*list == i) { - ctx.entity = i; + ctx.entity.value = pack(id_, i, *version); ctx.remove = false; std::apply(visit, cptrs); if (ctx.remove) - remove_entity(i); + remove_entity(ctx.entity.value); } std::apply(increment, cptrs); ++list; + ++version; } } else @@ -133,11 +165,11 @@ namespace psemek::util // packed for (std::size_t i = 0; i < entity_count();) { - ctx.entity = i; + ctx.entity.value = pack(id_, i, 0); ctx.remove = false; std::apply(visit, cptrs); if (ctx.remove) - remove_entity(i); + remove_entity(ctx.entity.value); else { std::apply(increment, cptrs); @@ -152,7 +184,7 @@ namespace psemek::util struct species_impl_base : species_base { - species_impl_base(std::string name, handle id, Components && ... components) + species_impl_base(std::string name, species_handle id, Components && ... components) : species_base(std::move(name), id) , species_components_{std::move(components)...} {} @@ -199,42 +231,58 @@ namespace psemek::util { using species_impl_base::species_impl_base; - static constexpr handle null = static_cast(-1); + static constexpr entity_id null = static_cast(-1); - handle entity_count() const override + entity_id entity_count() const override { return entity_count_; } - handle add_entity() override + entity_handle add_entity() override { return add_entity_impl(std::make_index_sequence{}); } - void remove_entity(handle h) override + void remove_entity(entity_handle h) override { - list_[h] = list_head_; - list_head_ = h; + auto id = unpack(h).entity; + list_[id] = list_head_; + list_head_ = id; + destroyed_at_[id] = ++version_; --entity_count_; } - handle const * get_free_list() override + bool entity_active(entity_handle h) const override + { + auto u = unpack(h); + return list_[u.entity] == u.entity && u.version >= destroyed_at_[u.entity]; + } + + entity_id const * get_free_list() const override { return list_.data(); } - std::size_t list_size() const override + entity_id list_size() const override { return list_.size(); } + entity_version const * get_version_list() const override + { + return created_at_.data(); + } + private: - std::vector list_; - handle list_head_ = null; - std::size_t entity_count_ = 0; + std::vector list_; + entity_id list_head_ = null; + entity_id entity_count_ = 0; + std::vector created_at_; + std::vector destroyed_at_; + entity_version version_ = 0; template - handle add_entity_impl(std::index_sequence) + entity_handle add_entity_impl(std::index_sequence) { if (list_head_ == null) { @@ -242,6 +290,8 @@ namespace psemek::util std::size_t const new_size = std::max(16, old_size * 2); list_.resize(new_size); + created_at_.resize(new_size, 0); + destroyed_at_.resize(new_size, 0); for (std::size_t i = old_size; i + 1 < new_size; ++i) list_[i] = i + 1; list_[new_size - 1] = null; @@ -250,14 +300,15 @@ namespace psemek::util ((std::get(this->entity_components_).resize(new_size)), ...); } - auto result = list_head_; + auto id = list_head_; - ((std::get(this->entity_components_)[result] = {}), ...); + ((std::get(this->entity_components_)[id] = {}), ...); list_head_ = list_[list_head_]; - list_[result] = result; + list_[id] = id; entity_count_++; - return result; + created_at_[id] = version_; + return pack(this->id_, id, version_); } }; @@ -267,47 +318,58 @@ namespace psemek::util { using species_impl_base::species_impl_base; - handle entity_count() const override + entity_id entity_count() const override { return std::get<0>(this->entity_components_).size(); } - handle add_entity() override + entity_handle add_entity() override { return add_entity_impl(std::make_index_sequence{}); } - void remove_entity(handle h) override + void remove_entity(entity_handle h) override { remove_entity_impl(h, std::make_index_sequence{}); } - handle const * get_free_list() override + bool entity_active(entity_handle h) const override + { + return unpack(h).entity < entity_count(); + } + + entity_id const * get_free_list() const override { return nullptr; } - std::size_t list_size() const override + entity_id list_size() const override { return 0; } + entity_version const * get_version_list() const override + { + return nullptr; + } + private: template - handle add_entity_impl(std::index_sequence) + entity_handle add_entity_impl(std::index_sequence) { - handle result = entity_count(); + entity_id id = entity_count(); ((std::get(this->entity_components_).emplace_back()), ...); - return result; + return pack(this->id_, id, 0); } template - void remove_entity_impl(handle h, std::index_sequence) + void remove_entity_impl(entity_handle h, std::index_sequence) { - if (h + 1 != entity_count()) + auto u = unpack(h); + if (u.entity + 1 != entity_count()) { - (std::swap(std::get(this->entity_components_)[h], std::get(this->entity_components_).back()), ...); + (std::swap(std::get(this->entity_components_)[u.entity], std::get(this->entity_components_).back()), ...); } ((std::get(this->entity_components_).pop_back()), ...); @@ -318,6 +380,20 @@ namespace psemek::util struct ecs { + struct species_handle + { + ecs_detail::species_handle value; + + friend auto operator <=> (species_handle const &, species_handle const &) = default; + }; + + struct entity_handle + { + ecs_detail::entity_handle value; + + friend auto operator <=> (entity_handle const &, entity_handle const &) = default; + }; + template struct behavior { @@ -326,9 +402,7 @@ namespace psemek::util struct context { - ecs_detail::handle species; - std::string_view species_name; - ecs_detail::handle entity; + entity_handle entity; component_ptrs components; @@ -342,8 +416,6 @@ namespace psemek::util }; }; - using handle = ecs_detail::handle; - enum class policy { sparse, @@ -351,139 +423,141 @@ namespace psemek::util }; template - handle register_species(std::string name, policy p, Components && ... components); + species_handle register_species(std::string name, policy p, Components && ... components); - handle species_count() const { return species_.size(); } + ecs_detail::species_handle species_count() const { return species_.size(); } - std::string_view species_name(handle species) const { return species_[species]->name(); } + std::string_view species_name(species_handle species) const { return species_[species.value]->name(); } - handle add_entity(handle species); - handle entity_count(handle species) const; - void remove_entity(handle species, handle entity); - bool entity_active(handle species, handle entity); + species_handle entity_species(entity_handle entity) const { return {ecs_detail::unpack(entity.value).species}; } - handle capacity(handle species) const { return species_[species]->list_size(); } + entity_handle add_entity(species_handle species); + ecs_detail::entity_id entity_count(species_handle species) const; + void remove_entity(entity_handle entity); + bool entity_active(entity_handle entity); template - Component & get(handle species); + Component & get(species_handle species); template - Component const & get(handle species) const; + Component const & get(species_handle species) const; template - typename Component::data & get(handle species, handle entity); + typename Component::data & get(entity_handle entity); template - typename Component::data const & get(handle species, handle entity) const; + typename Component::data const & get(entity_handle entity) const; template - Component * get_if(handle species); + Component * get_if(species_handle species); template - Component const * get_if(handle species) const; + Component const * get_if(species_handle species) const; template - typename Component::data * get_if(handle species, handle entity); + typename Component::data * get_if(entity_handle entity); template - typename Component::data const * get_if(handle species, handle entity) const; + typename Component::data const * get_if(entity_handle entity) const; template void apply(Behavior && behavior); template - void apply(Behavior && behavior, handle species); + void apply(Behavior && behavior, species_handle species); private: std::vector> species_; }; template - ecs::handle ecs::register_species(std::string name, policy p, Components && ... components) + ecs::species_handle ecs::register_species(std::string name, policy p, Components && ... components) { - handle result = species_.size(); + auto result = species_count(); if (p == policy::sparse) species_.push_back(std::make_unique>(std::move(name), result, std::move(components)...)); else species_.push_back(std::make_unique>(std::move(name), result, std::move(components)...)); - return result; + return {result}; } - inline ecs::handle ecs::add_entity(handle species) + inline ecs::entity_handle ecs::add_entity(species_handle species) { - return species_[species]->add_entity(); + return {species_[species.value]->add_entity()}; } - inline ecs::handle ecs::entity_count(handle species) const + inline ecs_detail::entity_id ecs::entity_count(species_handle species) const { - return species_[species]->entity_count(); + return species_[species.value]->entity_count(); } - inline void ecs::remove_entity(handle species, handle entity) + inline void ecs::remove_entity(entity_handle entity) { - species_[species]->remove_entity(entity); + species_[ecs_detail::unpack(entity.value).species]->remove_entity(entity.value); } - inline bool ecs::entity_active(handle species, handle entity) + inline bool ecs::entity_active(entity_handle entity) { - return species_[species]->entity_active(entity); + return species_[ecs_detail::unpack(entity.value).species]->entity_active(entity.value); } template - Component & ecs::get(handle species) + Component & ecs::get(species_handle species) { - auto p = species_[species]->get_species_component(); + auto p = species_[species.value]->get_species_component(); if (!p) - throw std::runtime_error(util::to_string("Component ", type_name(), " is not present in species ", species_[species]->name())); + throw std::runtime_error(util::to_string("Component ", type_name(), " is not present in species ", species_[species.value]->name())); return *p; } template - Component const & ecs::get(handle species) const + Component const & ecs::get(species_handle species) const { return const_cast(const_cast(this)->get(species)); } template - typename Component::data & ecs::get(handle species, handle entity) + typename Component::data & ecs::get(entity_handle entity) { - auto p = species_[species]->get_entity_component(); + auto u = ecs_detail::unpack(entity.value); + auto p = species_[u.species]->get_entity_component(); if (!p) - throw std::runtime_error(util::to_string("Component ", type_name(), " is not present in species ", species_[species]->name())); - return p[entity]; + throw std::runtime_error(util::to_string("Component ", type_name(), " is not present in species ", species_[u.species]->name())); + return p[u.entity]; } template - typename Component::data const & ecs::get(handle species, handle entity) const + typename Component::data const & ecs::get(entity_handle entity) const { - return const_cast(const_cast(this)->get(species, entity)); + return const_cast(const_cast(this)->get(entity)); } template - Component * ecs::get_if(handle species) + Component * ecs::get_if(species_handle species) { - return species_[species]->get_species_component(); + return species_[species.value]->get_species_component(); } template - Component const * ecs::get_if(handle species) const + Component const * ecs::get_if(species_handle species) const { return const_cast(const_cast(this)->get(species)); } template - typename Component::data * ecs::get_if(handle species, handle entity) + typename Component::data * ecs::get_if(entity_handle entity) { - auto p = species_[species]->get_entity_component(); + auto u = ecs_detail::unpack(entity.value); + auto p = species_[u.species]->get_entity_component(); if (p) - return p + entity; + return p + u.entity; return p; } template - typename Component::data const * ecs::get_if(handle species, handle entity) const + typename Component::data const * ecs::get_if(entity_handle entity) const { - return const_cast(const_cast(this)->get_if(species, entity)); + return const_cast(const_cast(this)->get_if(entity)); } template @@ -494,9 +568,32 @@ namespace psemek::util } template - void ecs::apply(Behavior && behavior, handle species) + void ecs::apply(Behavior && behavior, species_handle species) { - species_[species]->apply(behavior); + species_[species.value]->apply(behavior); } } + +namespace std +{ + + template <> + struct hash<::psemek::util::ecs::species_handle> + { + std::size_t operator()(::psemek::util::ecs::species_handle h) const + { + return std::hash<::psemek::util::ecs_detail::species_handle>()(h.value); + } + }; + + template <> + struct hash<::psemek::util::ecs::entity_handle> + { + std::size_t operator()(::psemek::util::ecs::entity_handle h) const + { + return std::hash<::psemek::util::ecs_detail::entity_handle>()(h.value); + } + }; + +}