ECS handles refactor: handle includes species & version

This commit is contained in:
Nikita Lisitsa 2022-08-15 19:40:50 +03:00
parent b05e209c6d
commit f22b45c5e8

View file

@ -10,6 +10,7 @@
#include <memory>
#include <string>
#include <string_view>
#include <functional>
#include <experimental/type_traits>
@ -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<entity_handle>(species) << 52) | (static_cast<entity_handle>(id) << 32) | static_cast<entity_handle>(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<typename Component::data *>(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 <typename Behavior>
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 <typename Behavior, typename ... Components, std::size_t ... Is>
void apply_impl(Behavior & behavior, std::tuple<Components...> *, std::index_sequence<Is...>)
@ -86,8 +118,6 @@ namespace psemek::util
return;
typename Behavior::context ctx;
ctx.species = id_;
ctx.species_name = name_;
((std::get<Components *>(ctx.components) = get_species_component<Components>()), ...);
@ -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<Components...>::species_impl_base;
static constexpr handle null = static_cast<handle>(-1);
static constexpr entity_id null = static_cast<entity_id>(-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<sizeof...(Components)>{});
}
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<handle> list_;
handle list_head_ = null;
std::size_t entity_count_ = 0;
std::vector<entity_id> list_;
entity_id list_head_ = null;
entity_id entity_count_ = 0;
std::vector<entity_version> created_at_;
std::vector<entity_version> destroyed_at_;
entity_version version_ = 0;
template <std::size_t ... I>
handle add_entity_impl(std::index_sequence<I...>)
entity_handle add_entity_impl(std::index_sequence<I...>)
{
if (list_head_ == null)
{
@ -242,6 +290,8 @@ namespace psemek::util
std::size_t const new_size = std::max<std::size_t>(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<I>(this->entity_components_).resize(new_size)), ...);
}
auto result = list_head_;
auto id = list_head_;
((std::get<I>(this->entity_components_)[result] = {}), ...);
((std::get<I>(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<Components...>::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<sizeof...(Components)>{});
}
void remove_entity(handle h) override
void remove_entity(entity_handle h) override
{
remove_entity_impl(h, std::make_index_sequence<sizeof...(Components)>{});
}
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 <std::size_t ... I>
handle add_entity_impl(std::index_sequence<I...>)
entity_handle add_entity_impl(std::index_sequence<I...>)
{
handle result = entity_count();
entity_id id = entity_count();
((std::get<I>(this->entity_components_).emplace_back()), ...);
return result;
return pack(this->id_, id, 0);
}
template <std::size_t ... I>
void remove_entity_impl(handle h, std::index_sequence<I...>)
void remove_entity_impl(entity_handle h, std::index_sequence<I...>)
{
if (h + 1 != entity_count())
auto u = unpack(h);
if (u.entity + 1 != entity_count())
{
(std::swap(std::get<I>(this->entity_components_)[h], std::get<I>(this->entity_components_).back()), ...);
(std::swap(std::get<I>(this->entity_components_)[u.entity], std::get<I>(this->entity_components_).back()), ...);
}
((std::get<I>(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 <typename ... Components>
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 <typename ... Components>
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 <typename Component>
Component & get(handle species);
Component & get(species_handle species);
template <typename Component>
Component const & get(handle species) const;
Component const & get(species_handle species) const;
template <typename Component>
typename Component::data & get(handle species, handle entity);
typename Component::data & get(entity_handle entity);
template <typename Component>
typename Component::data const & get(handle species, handle entity) const;
typename Component::data const & get(entity_handle entity) const;
template <typename Component>
Component * get_if(handle species);
Component * get_if(species_handle species);
template <typename Component>
Component const * get_if(handle species) const;
Component const * get_if(species_handle species) const;
template <typename Component>
typename Component::data * get_if(handle species, handle entity);
typename Component::data * get_if(entity_handle entity);
template <typename Component>
typename Component::data const * get_if(handle species, handle entity) const;
typename Component::data const * get_if(entity_handle entity) const;
template <typename Behavior>
void apply(Behavior && behavior);
template <typename Behavior>
void apply(Behavior && behavior, handle species);
void apply(Behavior && behavior, species_handle species);
private:
std::vector<std::unique_ptr<ecs_detail::species_base>> species_;
};
template <typename ... Components>
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<ecs_detail::sparse_species_impl<Components...>>(std::move(name), result, std::move(components)...));
else
species_.push_back(std::make_unique<ecs_detail::packed_species_impl<Components...>>(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 <typename Component>
Component & ecs::get(handle species)
Component & ecs::get(species_handle species)
{
auto p = species_[species]->get_species_component<Component>();
auto p = species_[species.value]->get_species_component<Component>();
if (!p)
throw std::runtime_error(util::to_string("Component ", type_name<Component>(), " is not present in species ", species_[species]->name()));
throw std::runtime_error(util::to_string("Component ", type_name<Component>(), " is not present in species ", species_[species.value]->name()));
return *p;
}
template <typename Component>
Component const & ecs::get(handle species) const
Component const & ecs::get(species_handle species) const
{
return const_cast<Component const &>(const_cast<ecs *>(this)->get<Component>(species));
}
template <typename Component>
typename Component::data & ecs::get(handle species, handle entity)
typename Component::data & ecs::get(entity_handle entity)
{
auto p = species_[species]->get_entity_component<Component>();
auto u = ecs_detail::unpack(entity.value);
auto p = species_[u.species]->get_entity_component<Component>();
if (!p)
throw std::runtime_error(util::to_string("Component ", type_name<Component>(), " is not present in species ", species_[species]->name()));
return p[entity];
throw std::runtime_error(util::to_string("Component ", type_name<Component>(), " is not present in species ", species_[u.species]->name()));
return p[u.entity];
}
template <typename Component>
typename Component::data const & ecs::get(handle species, handle entity) const
typename Component::data const & ecs::get(entity_handle entity) const
{
return const_cast<typename Component::data const &>(const_cast<ecs *>(this)->get<Component>(species, entity));
return const_cast<typename Component::data const &>(const_cast<ecs *>(this)->get<Component>(entity));
}
template <typename Component>
Component * ecs::get_if(handle species)
Component * ecs::get_if(species_handle species)
{
return species_[species]->get_species_component<Component>();
return species_[species.value]->get_species_component<Component>();
}
template <typename Component>
Component const * ecs::get_if(handle species) const
Component const * ecs::get_if(species_handle species) const
{
return const_cast<typename Component::data const *>(const_cast<ecs *>(this)->get<Component>(species));
}
template <typename Component>
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<Component>();
auto u = ecs_detail::unpack(entity.value);
auto p = species_[u.species]->get_entity_component<Component>();
if (p)
return p + entity;
return p + u.entity;
return p;
}
template <typename Component>
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<typename Component::data const *>(const_cast<ecs *>(this)->get_if<Component>(species, entity));
return const_cast<typename Component::data const *>(const_cast<ecs *>(this)->get_if<Component>(entity));
}
template <typename Behavior>
@ -494,9 +568,32 @@ namespace psemek::util
}
template <typename Behavior>
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);
}
};
}