384 lines
10 KiB
C++
384 lines
10 KiB
C++
#pragma once
|
|
|
|
#include <psemek/util/to_string.hpp>
|
|
#include <psemek/util/type_name.hpp>
|
|
|
|
#include <cstdint>
|
|
#include <typeindex>
|
|
#include <tuple>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include <experimental/type_traits>
|
|
|
|
namespace psemek::util
|
|
{
|
|
|
|
namespace ecs_detail
|
|
{
|
|
|
|
using handle = std::uint32_t;
|
|
|
|
struct species_base
|
|
{
|
|
virtual std::string_view name() const = 0;
|
|
|
|
virtual void * get_species_component(std::type_index component_type) = 0;
|
|
virtual void * get_entity_component(std::type_index component_type) = 0;
|
|
|
|
template <typename Component>
|
|
Component * get_species_component()
|
|
{
|
|
return reinterpret_cast<Component *>(get_species_component(typeid(Component)));
|
|
}
|
|
|
|
template <typename Component>
|
|
typename Component::data * get_entity_component()
|
|
{
|
|
return reinterpret_cast<typename Component::data *>(get_entity_component(typeid(Component)));
|
|
}
|
|
|
|
virtual handle entity_count() const = 0;
|
|
|
|
virtual handle add_entity() = 0;
|
|
virtual void remove_entity(handle h) = 0;
|
|
|
|
bool entity_active(handle h)
|
|
{
|
|
return get_free_list()[h] == h;
|
|
}
|
|
|
|
template <typename Behavior>
|
|
void apply(Behavior & behavior)
|
|
{
|
|
apply_impl(behavior, static_cast<typename Behavior::component_types *>(nullptr), std::make_index_sequence<std::tuple_size_v<typename Behavior::component_types>>{});
|
|
}
|
|
|
|
virtual ~species_base() {}
|
|
|
|
static constexpr handle null = static_cast<handle>(-1);
|
|
|
|
virtual handle const * get_free_list() = 0;
|
|
virtual std::size_t list_size() const = 0;
|
|
|
|
private:
|
|
|
|
template <typename Behavior, typename ... Components, std::size_t ... Is>
|
|
void apply_impl(Behavior & behavior, std::tuple<Components...> *, std::index_sequence<Is...>)
|
|
{
|
|
std::tuple<typename Components::data * ...> cptrs;
|
|
|
|
((std::get<Is>(cptrs) = get_entity_component<Components>()), ...);
|
|
|
|
auto all_nonzero = [](auto * ... ptrs)
|
|
{
|
|
return ((ptrs != nullptr) && ...);
|
|
};
|
|
|
|
if (!std::apply(all_nonzero, cptrs))
|
|
return;
|
|
|
|
std::tuple<Components *...> cs;
|
|
|
|
((std::get<Components *>(cs) = get_species_component<Components>()), ...);
|
|
|
|
auto visit = [&](auto * ... ptrs)
|
|
{
|
|
if constexpr (std::is_invocable_v<Behavior, typename Components::data & ..., std::tuple<Components *...> const &>)
|
|
{
|
|
behavior(*ptrs..., cs);
|
|
}
|
|
else
|
|
{
|
|
behavior(*ptrs...);
|
|
}
|
|
};
|
|
|
|
auto increment = [](auto * & ... ptrs)
|
|
{
|
|
((++ptrs), ...);
|
|
};
|
|
|
|
auto list = get_free_list();
|
|
|
|
std::size_t const count = list_size();
|
|
|
|
for (std::size_t i = 0; i < count; ++i)
|
|
{
|
|
if (*list == i)
|
|
std::apply(visit, cptrs);
|
|
std::apply(increment, cptrs);
|
|
++list;
|
|
}
|
|
}
|
|
};
|
|
|
|
template <typename ... Components>
|
|
struct species_impl
|
|
: species_base
|
|
{
|
|
species_impl(std::string name, Components && ... components)
|
|
: name_(std::move(name))
|
|
, species_components_{std::move(components)...}
|
|
{}
|
|
|
|
std::string_view name() const override
|
|
{
|
|
return name_;
|
|
}
|
|
|
|
using species_base::get_species_component;
|
|
using species_base::get_entity_component;
|
|
|
|
void * get_species_component(std::type_index component_type) override
|
|
{
|
|
return get_species_component_impl(component_type, std::make_index_sequence<sizeof...(Components)>{});
|
|
}
|
|
|
|
void * get_entity_component(std::type_index component_type) override
|
|
{
|
|
return get_entity_component_impl(component_type, std::make_index_sequence<sizeof...(Components)>{});
|
|
}
|
|
|
|
handle entity_count() const override
|
|
{
|
|
return entity_count_;
|
|
}
|
|
|
|
handle add_entity() override
|
|
{
|
|
return add_entity_impl(std::make_index_sequence<sizeof...(Components)>{});
|
|
}
|
|
|
|
void remove_entity(handle h) override
|
|
{
|
|
list_[h] = list_head_;
|
|
list_head_ = h;
|
|
--entity_count_;
|
|
}
|
|
|
|
handle const * get_free_list() override
|
|
{
|
|
return list_.data();
|
|
}
|
|
|
|
std::size_t list_size() const override
|
|
{
|
|
return list_.size();
|
|
}
|
|
|
|
private:
|
|
std::string name_;
|
|
std::tuple<Components...> species_components_;
|
|
std::tuple<std::vector<typename Components::data>...> entity_components_;
|
|
std::vector<handle> list_;
|
|
handle list_head_ = null;
|
|
std::size_t entity_count_ = 0;
|
|
|
|
template <std::size_t ... I>
|
|
void * get_species_component_impl(std::type_index component_type, std::index_sequence<I...>)
|
|
{
|
|
void * result = nullptr;
|
|
|
|
((result = (component_type == typeid(std::tuple_element_t<I, std::tuple<Components...>>)) ? &std::get<I>(species_components_) : result), ...);
|
|
return result;
|
|
}
|
|
|
|
template <std::size_t ... I>
|
|
void * get_entity_component_impl(std::type_index component_type, std::index_sequence<I...>)
|
|
{
|
|
void * result = nullptr;
|
|
|
|
((result = (component_type == typeid(std::tuple_element_t<I, std::tuple<Components...>>)) ? std::get<I>(entity_components_).data() : result), ...);
|
|
return result;
|
|
}
|
|
|
|
template <std::size_t ... I>
|
|
handle add_entity_impl(std::index_sequence<I...>)
|
|
{
|
|
if (list_head_ == null)
|
|
{
|
|
std::size_t const old_size = list_.size();
|
|
std::size_t const new_size = std::max<std::size_t>(16, old_size * 2);
|
|
|
|
list_.resize(new_size);
|
|
for (std::size_t i = old_size; i + 1 < new_size; ++i)
|
|
list_[i] = i + 1;
|
|
list_[new_size - 1] = null;
|
|
list_head_ = old_size;
|
|
|
|
((std::get<I>(entity_components_).resize(new_size)), ...);
|
|
}
|
|
|
|
auto result = list_head_;
|
|
list_head_ = list_[list_head_];
|
|
list_[result] = result;
|
|
entity_count_++;
|
|
return result;
|
|
}
|
|
};
|
|
|
|
template <typename Behavior>
|
|
using prepare_helper = decltype(std::declval<Behavior>().prepare());
|
|
|
|
template <typename Behavior>
|
|
using has_prepare = std::experimental::is_detected<prepare_helper, Behavior>;
|
|
|
|
}
|
|
|
|
struct ecs
|
|
{
|
|
template <typename ... Components>
|
|
struct behavior
|
|
{
|
|
using component_types = std::tuple<Components...>;
|
|
using component_ptrs = std::tuple<Components *...>;
|
|
};
|
|
|
|
using handle = ecs_detail::handle;
|
|
|
|
template <typename ... Components>
|
|
handle register_species(std::string name, Components && ... components);
|
|
|
|
handle species_count() const { return species_.size(); }
|
|
|
|
std::string_view species_name(handle species) const { return species_[species]->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);
|
|
|
|
handle capacity(handle species) const { return species_[species]->list_size(); }
|
|
|
|
template <typename Component>
|
|
Component & get(handle species);
|
|
|
|
template <typename Component>
|
|
Component const & get(handle species) const;
|
|
|
|
template <typename Component>
|
|
typename Component::data & get(handle species, handle entity);
|
|
|
|
template <typename Component>
|
|
typename Component::data const & get(handle species, handle entity) const;
|
|
|
|
template <typename Component>
|
|
Component * get_if(handle species);
|
|
|
|
template <typename Component>
|
|
Component const * get_if(handle species) const;
|
|
|
|
template <typename Component>
|
|
typename Component::data * get_if(handle species, handle entity);
|
|
|
|
template <typename Component>
|
|
typename Component::data const * get_if(handle species, handle entity) const;
|
|
|
|
template <typename Behavior>
|
|
void apply(Behavior && behavior) const;
|
|
|
|
private:
|
|
std::vector<std::unique_ptr<ecs_detail::species_base>> species_;
|
|
};
|
|
|
|
template <typename ... Components>
|
|
ecs::handle ecs::register_species(std::string name, Components && ... components)
|
|
{
|
|
handle result = species_.size();
|
|
species_.push_back(std::make_unique<ecs_detail::species_impl<Components...>>(std::move(name), std::move(components)...));
|
|
return result;
|
|
}
|
|
|
|
inline ecs::handle ecs::add_entity(handle species)
|
|
{
|
|
return species_[species]->add_entity();
|
|
}
|
|
|
|
inline ecs::handle ecs::entity_count(handle species) const
|
|
{
|
|
return species_[species]->entity_count();
|
|
}
|
|
|
|
inline void ecs::remove_entity(handle species, handle entity)
|
|
{
|
|
species_[species]->remove_entity(entity);
|
|
}
|
|
|
|
inline bool ecs::entity_active(handle species, handle entity)
|
|
{
|
|
return species_[species]->entity_active(entity);
|
|
}
|
|
|
|
template <typename Component>
|
|
Component & ecs::get(handle species)
|
|
{
|
|
auto p = species_[species]->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()));
|
|
return *p;
|
|
}
|
|
|
|
template <typename Component>
|
|
Component const & ecs::get(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(handle species, handle entity)
|
|
{
|
|
auto p = species_[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];
|
|
}
|
|
|
|
template <typename Component>
|
|
typename Component::data const & ecs::get(handle species, handle entity) const
|
|
{
|
|
return const_cast<typename Component::data const &>(const_cast<ecs *>(this)->get<Component>(species, entity));
|
|
}
|
|
|
|
template <typename Component>
|
|
Component * ecs::get_if(handle species)
|
|
{
|
|
return species_[species]->get_species_component<Component>();
|
|
}
|
|
|
|
template <typename Component>
|
|
Component const * ecs::get_if(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)
|
|
{
|
|
auto p = species_[species]->get_entity_component<Component>();
|
|
if (p)
|
|
return p + entity;
|
|
return p;
|
|
}
|
|
|
|
template <typename Component>
|
|
typename Component::data const * ecs::get_if(handle species, handle entity) const
|
|
{
|
|
return const_cast<typename Component::data const *>(const_cast<ecs *>(this)->get<Component>(species, entity));
|
|
}
|
|
|
|
template <typename Behavior>
|
|
void ecs::apply(Behavior && behavior) const
|
|
{
|
|
if constexpr (ecs_detail::has_prepare<Behavior>::value)
|
|
{
|
|
behavior.prepare();
|
|
}
|
|
|
|
for (auto & s : species_)
|
|
s->apply(behavior);
|
|
}
|
|
|
|
}
|