diff --git a/ecs.hpp b/ecs.hpp index 6f05bb5..76c7650 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -10,16 +10,18 @@ #include #include +#include #include #include +#include #include #include +#include #include #include +#include #include #include -#include -#include // ----------------------------------------------------------------------------- // @@ -29,8 +31,9 @@ namespace ecs_hpp { - class world; class entity; + class system; + class registry; using family_id = std::uint16_t; using entity_id = std::uint32_t; @@ -44,6 +47,23 @@ namespace ecs_hpp "ecs_hpp::entity_id must be an unsigned integer"); } +// ----------------------------------------------------------------------------- +// +// utilities +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + namespace detail + { + template < typename T > + constexpr std::add_const_t& as_const(T& t) noexcept { + return t; + } + } +} + // ----------------------------------------------------------------------------- // // detail::type_family @@ -78,6 +98,306 @@ namespace ecs_hpp } } +// ----------------------------------------------------------------------------- +// +// detail::sparse_set +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + namespace detail + { + template < typename T > + class sparse_set final { + public: + static_assert( + std::is_unsigned::value, + "sparse_set can contain an unsigned integers only"); + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + public: + iterator begin() noexcept { + return dense_.begin(); + } + + iterator end() noexcept { + using dt = typename std::iterator_traits::difference_type; + return begin() + static_cast
(size_); + } + + const_iterator begin() const noexcept { + return dense_.begin(); + } + + const_iterator end() const noexcept { + using dt = typename std::iterator_traits::difference_type; + return begin() + static_cast
(size_); + } + + const_iterator cbegin() const noexcept { + return dense_.cbegin(); + } + + const_iterator cend() const noexcept { + using dt = typename std::iterator_traits::difference_type; + return cbegin() + static_cast
(size_); + } + public: + bool insert(const T v) { + if ( has(v) ) { + return false; + } + if ( v >= capacity_ ) { + reserve(new_capacity_for_(v + 1u)); + } + dense_[size_] = v; + sparse_[v] = size_; + ++size_; + return true; + } + + bool unordered_erase(const T v) noexcept { + if ( !has(v) ) { + return false; + } + const std::size_t index = sparse_[v]; + const T last = dense_[size_ - 1u]; + dense_[index] = last; + sparse_[last] = index; + --size_; + return true; + } + + void clear() noexcept { + size_ = 0u; + } + + bool has(const T v) const noexcept { + return v < capacity_ + && sparse_[v] < size_ + && dense_[sparse_[v]] == v; + } + + const_iterator find(const T v) const noexcept { + return has(v) + ? begin() + sparse_[v] + : end(); + } + + std::size_t get_index(const T v) const { + const auto p = find_index(v); + if ( p.second ) { + return p.first; + } + throw std::out_of_range("sparse_set"); + } + + std::pair find_index(const T v) const noexcept { + return has(v) + ? std::make_pair(sparse_[v], true) + : std::make_pair(std::size_t(-1), false); + } + + bool empty() const noexcept { + return size_ == 0u; + } + + void reserve(std::size_t ncapacity) { + if ( ncapacity > capacity_ ) { + std::vector ndense(ncapacity); + std::vector nsparse(ncapacity); + std::copy(dense_.begin(), dense_.end(), ndense.begin()); + std::copy(sparse_.begin(), sparse_.end(), nsparse.begin()); + ndense.swap(dense_); + nsparse.swap(sparse_); + capacity_ = ncapacity; + } + } + + std::size_t size() const noexcept { + return size_; + } + + std::size_t max_size() const noexcept { + return std::min(dense_.max_size(), sparse_.max_size()); + } + + std::size_t capacity() const noexcept { + return capacity_; + } + private: + std::size_t new_capacity_for_(std::size_t nsize) const { + const std::size_t ms = max_size(); + if ( nsize > ms ) { + throw std::length_error("sparse_set"); + } + if ( capacity_ >= ms / 2u ) { + return ms; + } + return std::max(capacity_ * 2u, nsize); + } + private: + std::vector dense_; + std::vector sparse_; + std::size_t size_{0u}; + std::size_t capacity_{0u}; + }; + } +} + +// ----------------------------------------------------------------------------- +// +// detail::sparse_map +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + namespace detail + { + template < typename K, typename T > + class sparse_map final { + public: + static_assert( + std::is_unsigned::value, + "sparse_map can contain unsigned integers keys only"); + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + public: + iterator begin() noexcept { + return keys_.begin(); + } + + iterator end() noexcept { + return keys_.end(); + } + + const_iterator begin() const noexcept { + return keys_.begin(); + } + + const_iterator end() const noexcept { + return keys_.end(); + } + + const_iterator cbegin() const noexcept { + return keys_.cbegin(); + } + + const_iterator cend() const noexcept { + return keys_.cend(); + } + public: + bool insert(const K k, const T& v) { + if ( keys_.has(k) ) { + return false; + } + values_.push_back(v); + try { + return keys_.insert(k); + } catch (...) { + values_.pop_back(); + throw; + } + } + + bool insert(const K k, T&& v) { + if ( keys_.has(k) ) { + return false; + } + values_.push_back(std::move(v)); + try { + return keys_.insert(k); + } catch (...) { + values_.pop_back(); + throw; + } + } + + template < typename... Args > + bool emplace(const K k, Args&&... args) { + if ( keys_.has(k) ) { + return false; + } + values_.emplace_back(std::forward(args)...); + try { + return keys_.insert(k); + } catch (...) { + values_.pop_back(); + throw; + } + } + + bool unordered_erase(const K k) { + if ( !keys_.has(k) ) { + return false; + } + const std::size_t index = keys_.get_index(k); + values_[index] = std::move(values_.back()); + values_.pop_back(); + keys_.unordered_erase(k); + return true; + } + + void clear() noexcept { + keys_.clear(); + values_.clear(); + } + + bool has(const K k) const noexcept { + return keys_.has(k); + } + + T& get_value(const K k) { + return values_[keys_.get_index(k)]; + } + + const T& get_value(const K k) const { + return values_[keys_.get_index(k)]; + } + + T* find_value(const K k) noexcept { + const auto ip = keys_.find_index(k); + return ip.second + ? &values_[ip.first] + : nullptr; + } + + const T* find_value(const K k) const noexcept { + const auto ip = keys_.find_index(k); + return ip.second + ? &values_[ip.first] + : nullptr; + } + + bool empty() const noexcept { + return values_.empty(); + } + + void reserve(std::size_t ncapacity) { + keys_.reserve(ncapacity); + values_.reserve(ncapacity); + } + + std::size_t size() const noexcept { + return values_.size(); + } + + std::size_t max_size() const noexcept { + return std::min(keys_.max_size(), values_.max_size()); + } + + std::size_t capacity() const noexcept { + return values_.capacity(); + } + private: + sparse_set keys_; + std::vector values_; + }; + } +} + // ----------------------------------------------------------------------------- // // detail::component_storage @@ -98,7 +418,7 @@ namespace ecs_hpp template < typename T > class component_storage : public component_storage_base { public: - component_storage(world& owner); + component_storage(registry& owner); template < typename... Args > void assign(entity_id id, Args&&... args); @@ -112,59 +432,55 @@ namespace ecs_hpp template < typename F > void for_each_component(F&& f) const noexcept; private: - world& owner_; - std::unordered_map components_; + registry& owner_; + detail::sparse_map components_; }; template < typename T > - component_storage::component_storage(world& owner) + component_storage::component_storage(registry& owner) : owner_(owner) {} template < typename T > template < typename... Args > void component_storage::assign(entity_id id, Args&&... args) { - components_[id] = T(std::forward(args)...); + if ( !components_.emplace(id, std::forward(args)...) ) { + components_.get_value(id) = T(std::forward(args)...); + } } template < typename T > bool component_storage::remove(entity_id id) noexcept { - return components_.erase(id) > 0u; + return components_.unordered_erase(id); } template < typename T > bool component_storage::exists(entity_id id) const noexcept { - return components_.find(id) != components_.end(); + return components_.has(id); } template < typename T > T* component_storage::find(entity_id id) noexcept { - const auto iter = components_.find(id); - return iter != components_.end() - ? &iter->second - : nullptr; + return components_.find_value(id); } template < typename T > const T* component_storage::find(entity_id id) const noexcept { - const auto iter = components_.find(id); - return iter != components_.end() - ? &iter->second - : nullptr; + return components_.find_value(id); } template < typename T > template < typename F > void component_storage::for_each_component(F&& f) noexcept { - for ( auto& component_pair : components_ ) { - f(entity(owner_, component_pair.first), component_pair.second); + for ( const auto id : components_ ) { + f(entity(owner_, id), components_.get_value(id)); } } template < typename T > template < typename F > void component_storage::for_each_component(F&& f) const noexcept { - for ( auto& component_pair : components_ ) { - f(entity(owner_, component_pair.first), component_pair.second); + for ( const auto id : components_ ) { + f(entity(owner_, id), components_.get_value(id)); } } } @@ -195,10 +511,10 @@ namespace ecs_hpp { class entity final { public: - entity(world& owner); - entity(world& owner, entity_id id); + entity(registry& owner); + entity(registry& owner, entity_id id); - const world& owner() const noexcept; + const registry& owner() const noexcept; entity_id id() const noexcept; bool destroy(); @@ -226,8 +542,18 @@ namespace ecs_hpp template < typename T > const T* find_component() const noexcept; + + template < typename... Ts > + std::tuple get_components(); + template < typename... Ts > + std::tuple get_components() const; + + template < typename... Ts > + std::tuple find_components() noexcept; + template < typename... Ts > + std::tuple find_components() const noexcept; private: - world& owner_; + registry& owner_; entity_id id_{0u}; }; @@ -249,16 +575,31 @@ namespace std // ----------------------------------------------------------------------------- // -// world +// system // // ----------------------------------------------------------------------------- namespace ecs_hpp { - class world final { + class system { public: - world(); - ~world() noexcept; + virtual ~system() = default; + virtual void process(registry& owner) = 0; + }; +} + +// ----------------------------------------------------------------------------- +// +// registry +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + class registry final { + public: + registry() = default; + ~registry() noexcept = default; entity create_entity(); bool destroy_entity(const entity& ent); @@ -285,6 +626,16 @@ namespace ecs_hpp template < typename T > const T* find_component(const entity& ent) const noexcept; + template < typename... Ts > + std::tuple get_components(const entity& ent); + template < typename... Ts > + std::tuple get_components(const entity& ent) const; + + template < typename... Ts > + std::tuple find_components(const entity& ent) noexcept; + template < typename... Ts > + std::tuple find_components(const entity& ent) const noexcept; + template < typename T, typename F > void for_each_component(F&& f); template < typename T, typename F > @@ -294,6 +645,10 @@ namespace ecs_hpp void for_joined_components(F&& f); template < typename... Ts, typename F > void for_joined_components(F&& f) const; + + template < typename T, typename... Args > + void add_system(Args&&... args); + void process_systems(); private: template < typename T > detail::component_storage* find_storage_() noexcept; @@ -328,10 +683,14 @@ namespace ecs_hpp mutable std::mutex mutex_; entity_id last_entity_id_{0u}; - std::unordered_set entities_; + std::vector free_entity_ids_; + detail::sparse_set entity_ids_; using storage_uptr = std::unique_ptr; - std::unordered_map storages_; + detail::sparse_map storages_; + + using system_uptr = std::unique_ptr; + std::vector systems_; }; } @@ -343,14 +702,14 @@ namespace ecs_hpp namespace ecs_hpp { - inline entity::entity(world& owner) + inline entity::entity(registry& owner) : owner_(owner) {} - inline entity::entity(world& owner, entity_id id) + inline entity::entity(registry& owner, entity_id id) : owner_(owner) , id_(id) {} - inline const world& entity::owner() const noexcept { + inline const registry& entity::owner() const noexcept { return owner_; } @@ -363,7 +722,7 @@ namespace ecs_hpp } inline bool entity::is_alive() const noexcept { - return owner_.is_entity_alive(*this); + return detail::as_const(owner_).is_entity_alive(*this); } template < typename T, typename... Args > @@ -380,7 +739,7 @@ namespace ecs_hpp template < typename T > bool entity::exists_component() const noexcept { - return owner_.exists_component(*this); + return detail::as_const(owner_).exists_component(*this); } inline std::size_t entity::remove_all_components() noexcept { @@ -394,7 +753,7 @@ namespace ecs_hpp template < typename T > const T& entity::get_component() const { - return owner_.get_component(*this); + return detail::as_const(owner_).get_component(*this); } template < typename T > @@ -404,7 +763,27 @@ namespace ecs_hpp template < typename T > const T* entity::find_component() const noexcept { - return owner_.find_component(*this); + return detail::as_const(owner_).find_component(*this); + } + + template < typename... Ts > + std::tuple entity::get_components() { + return owner_.get_components(*this); + } + + template < typename... Ts > + std::tuple entity::get_components() const { + return detail::as_const(owner_).get_components(*this); + } + + template < typename... Ts > + std::tuple entity::find_components() noexcept { + return owner_.find_components(*this); + } + + template < typename... Ts > + std::tuple entity::find_components() const noexcept { + return detail::as_const(owner_).find_components(*this); } inline bool operator==(const entity& l, const entity& r) noexcept { @@ -419,36 +798,44 @@ namespace ecs_hpp // ----------------------------------------------------------------------------- // -// world impl +// registry impl // // ----------------------------------------------------------------------------- namespace ecs_hpp { - inline world::world() = default; - inline world::~world() noexcept = default; - - inline entity world::create_entity() { + inline entity registry::create_entity() { std::lock_guard guard(mutex_); + if ( !free_entity_ids_.empty() ) { + auto ent = entity(*this, free_entity_ids_.back()); + entity_ids_.insert(ent.id()); + free_entity_ids_.pop_back(); + return ent; + + } assert(last_entity_id_ < std::numeric_limits::max()); auto ent = entity(*this, ++last_entity_id_); - entities_.insert(ent); + entity_ids_.insert(ent.id()); return ent; } - inline bool world::destroy_entity(const entity& ent) { + inline bool registry::destroy_entity(const entity& ent) { std::lock_guard guard(mutex_); remove_all_components_impl_(ent); - return entities_.erase(ent) > 0u; + if ( entity_ids_.unordered_erase(ent.id()) ) { + free_entity_ids_.push_back(ent.id()); + return true; + } + return false; } - inline bool world::is_entity_alive(const entity& ent) const noexcept { + inline bool registry::is_entity_alive(const entity& ent) const noexcept { std::lock_guard guard(mutex_); return is_entity_alive_impl_(ent); } template < typename T, typename... Args > - bool world::assign_component(const entity& ent, Args&&... args) { + bool registry::assign_component(const entity& ent, Args&&... args) { std::lock_guard guard(mutex_); if ( !is_entity_alive_impl_(ent) ) { return false; @@ -460,7 +847,7 @@ namespace ecs_hpp } template < typename T > - bool world::remove_component(const entity& ent) { + bool registry::remove_component(const entity& ent) { std::lock_guard guard(mutex_); if ( !is_entity_alive_impl_(ent) ) { return false; @@ -472,7 +859,7 @@ namespace ecs_hpp } template < typename T > - bool world::exists_component(const entity& ent) const noexcept { + bool registry::exists_component(const entity& ent) const noexcept { std::lock_guard guard(mutex_); if ( !is_entity_alive_impl_(ent) ) { return false; @@ -483,13 +870,13 @@ namespace ecs_hpp : false; } - inline std::size_t world::remove_all_components(const entity& ent) const noexcept { + inline std::size_t registry::remove_all_components(const entity& ent) const noexcept { std::lock_guard guard(mutex_); return remove_all_components_impl_(ent); } template < typename T > - T& world::get_component(const entity& ent) { + T& registry::get_component(const entity& ent) { std::lock_guard guard(mutex_); T* component = find_component_impl_(ent); if ( component ) { @@ -499,7 +886,7 @@ namespace ecs_hpp } template < typename T > - const T& world::get_component(const entity& ent) const { + const T& registry::get_component(const entity& ent) const { std::lock_guard guard(mutex_); const T* component = find_component_impl_(ent); if ( component ) { @@ -509,19 +896,39 @@ namespace ecs_hpp } template < typename T > - T* world::find_component(const entity& ent) noexcept { + T* registry::find_component(const entity& ent) noexcept { std::lock_guard guard(mutex_); return find_component_impl_(ent); } template < typename T > - const T* world::find_component(const entity& ent) const noexcept { + const T* registry::find_component(const entity& ent) const noexcept { std::lock_guard guard(mutex_); return find_component_impl_(ent); } + template < typename... Ts > + std::tuple registry::get_components(const entity& ent) { + return std::make_tuple(std::ref(get_component(ent))...); + } + + template < typename... Ts > + std::tuple registry::get_components(const entity& ent) const { + return std::make_tuple(std::cref(get_component(ent))...); + } + + template < typename... Ts > + std::tuple registry::find_components(const entity& ent) noexcept { + return std::make_tuple(find_component(ent)...); + } + + template < typename... Ts > + std::tuple registry::find_components(const entity& ent) const noexcept { + return std::make_tuple(find_component(ent)...); + } + template < typename T, typename F > - void world::for_each_component(F&& f) { + void registry::for_each_component(F&& f) { std::lock_guard guard(mutex_); detail::component_storage* storage = find_storage_(); if ( storage ) { @@ -530,7 +937,7 @@ namespace ecs_hpp } template < typename T, typename F > - void world::for_each_component(F&& f) const { + void registry::for_each_component(F&& f) const { std::lock_guard guard(mutex_); const detail::component_storage* storage = find_storage_(); if ( storage ) { @@ -539,63 +946,73 @@ namespace ecs_hpp } template < typename... Ts, typename F > - void world::for_joined_components(F&& f) { + void registry::for_joined_components(F&& f) { std::lock_guard guard(mutex_); for_joined_components_impl_(std::forward(f)); } template < typename... Ts, typename F > - void world::for_joined_components(F&& f) const { + void registry::for_joined_components(F&& f) const { std::lock_guard guard(mutex_); for_joined_components_impl_(std::forward(f)); } - template < typename T > - detail::component_storage* world::find_storage_() noexcept { - const auto family = detail::type_family::id(); - const auto iter = storages_.find(family); - if ( iter != storages_.end() ) { - return static_cast*>(iter->second.get()); + template < typename T, typename... Args > + void registry::add_system(Args&&... args) { + std::lock_guard guard(mutex_); + systems_.emplace_back( + std::make_unique(std::forward(args)...)); + } + + inline void registry::process_systems() { + for ( auto& s : systems_ ) { + s->process(*this); } - return nullptr; } template < typename T > - const detail::component_storage* world::find_storage_() const noexcept { + detail::component_storage* registry::find_storage_() noexcept { const auto family = detail::type_family::id(); - const auto iter = storages_.find(family); - if ( iter != storages_.end() ) { - return static_cast*>(iter->second.get()); - } - return nullptr; + using raw_storage_ptr = detail::component_storage*; + return storages_.has(family) + ? static_cast(storages_.get_value(family).get()) + : nullptr; } template < typename T > - detail::component_storage& world::get_or_create_storage_() { + const detail::component_storage* registry::find_storage_() const noexcept { + const auto family = detail::type_family::id(); + using raw_storage_ptr = const detail::component_storage*; + return storages_.has(family) + ? static_cast(storages_.get_value(family).get()) + : nullptr; + } + + template < typename T > + detail::component_storage& registry::get_or_create_storage_() { detail::component_storage* storage = find_storage_(); if ( storage ) { return *storage; } const auto family = detail::type_family::id(); - const auto emplace_r = storages_.emplace(std::make_pair( + storages_.emplace( family, - std::make_unique>(*this))); - assert(emplace_r.second && "unexpected internal error"); + std::make_unique>(*this)); return *static_cast*>( - emplace_r.first->second.get()); + storages_.get_value(family).get()); } - inline bool world::is_entity_alive_impl_(const entity& ent) const noexcept { - return entities_.count(ent) > 0u; + inline bool registry::is_entity_alive_impl_(const entity& ent) const noexcept { + return entity_ids_.has(ent.id()); } - inline std::size_t world::remove_all_components_impl_(const entity& ent) const noexcept { + inline std::size_t registry::remove_all_components_impl_(const entity& ent) const noexcept { if ( !is_entity_alive_impl_(ent) ) { return 0u; } std::size_t removed_components = 0u; - for ( auto& storage_p : storages_ ) { - if ( storage_p.second->remove(ent.id()) ) { + for ( const auto id : storages_ ) { + if ( storages_.get_value(id)->remove(ent.id()) ) { ++removed_components; } } @@ -603,7 +1020,7 @@ namespace ecs_hpp } template < typename T > - T* world::find_component_impl_(const entity& ent) noexcept { + T* registry::find_component_impl_(const entity& ent) noexcept { detail::component_storage* storage = find_storage_(); return storage ? storage->find(ent.id()) @@ -611,7 +1028,7 @@ namespace ecs_hpp } template < typename T > - const T* world::find_component_impl_(const entity& ent) const noexcept { + const T* registry::find_component_impl_(const entity& ent) const noexcept { const detail::component_storage* storage = find_storage_(); return storage ? storage->find(ent.id()) @@ -619,14 +1036,14 @@ namespace ecs_hpp } template < typename... Ts, typename F > - void world::for_joined_components_impl_(F&& f) { - for ( const auto& e : entities_ ) { - for_joined_components_impl_(e, std::forward(f)); + void registry::for_joined_components_impl_(F&& f) { + for ( const auto id : entity_ids_ ) { + for_joined_components_impl_(entity(*this, id), std::forward(f)); } } template < typename T, typename... Ts, typename F, typename... Cs > - void world::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) { + void registry::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) { T* c = find_component_impl_(e); if ( c ) { for_joined_components_impl_( @@ -638,19 +1055,19 @@ namespace ecs_hpp } template < typename F, typename... Cs > - void world::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) { + void registry::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) { f(e, std::forward(cs)...); } template < typename... Ts, typename F > - void world::for_joined_components_impl_(F&& f) const { - for ( const auto& e : entities_ ) { - for_joined_components_impl_(e, std::forward(f)); + void registry::for_joined_components_impl_(F&& f) const { + for ( const auto id : entity_ids_ ) { + for_joined_components_impl_(entity(const_cast(*this), id), std::forward(f)); } } template < typename T, typename... Ts, typename F, typename... Cs > - void world::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) const { + void registry::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) const { const T* c = find_component_impl_(e); if ( c ) { for_joined_components_impl_( @@ -662,7 +1079,7 @@ namespace ecs_hpp } template < typename F, typename... Cs > - void world::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) const { + void registry::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) const { f(e, std::forward(cs)...); } } diff --git a/ecs_tests.cpp b/ecs_tests.cpp index d4efab9..ad3184e 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -27,6 +27,16 @@ namespace velocity_c() = default; velocity_c(int nx, int ny) : x(nx), y(ny) {} }; + + bool operator==(const position_c& l, const position_c& r) noexcept { + return l.x == r.x + && l.y == r.y; + } + + bool operator==(const velocity_c& l, const velocity_c& r) noexcept { + return l.x == r.x + && l.y == r.y; + } } TEST_CASE("detail") { @@ -40,12 +50,155 @@ TEST_CASE("detail") { REQUIRE(ecs::detail::type_family::id() == 1u); REQUIRE(ecs::detail::type_family::id() == 2u); } + SECTION("sparse_set") { + using namespace ecs::detail; + { + sparse_set s; + + REQUIRE(s.empty()); + REQUIRE_FALSE(s.size()); + REQUIRE(s.capacity() == 0u); + REQUIRE_FALSE(s.has(42u)); + REQUIRE(s.find(42u) == s.end()); + REQUIRE_FALSE(s.find_index(42u).second); + REQUIRE_THROWS(s.get_index(42u)); + + REQUIRE(s.insert(42u)); + + REQUIRE_FALSE(s.empty()); + REQUIRE(s.size() == 1u); + REQUIRE(s.capacity() == 43u); + REQUIRE(s.has(42u)); + REQUIRE_FALSE(s.has(84u)); + + REQUIRE(s.find_index(42u).second); + REQUIRE(s.find_index(42u).first == 0u); + REQUIRE(s.get_index(42u) == 0u); + + s.clear(); + + REQUIRE(s.empty()); + REQUIRE_FALSE(s.size()); + REQUIRE_FALSE(s.has(42u)); + + REQUIRE(s.insert(84u)); + REQUIRE_FALSE(s.insert(84u)); + + REQUIRE(s.has(84u)); + REQUIRE_FALSE(s.unordered_erase(42u)); + REQUIRE(s.unordered_erase(84u)); + REQUIRE_FALSE(s.has(84u)); + REQUIRE(s.empty()); + REQUIRE_FALSE(s.size()); + REQUIRE(s.capacity() == 43u * 2); + + s.insert(42u); + s.insert(84u); + + REQUIRE(s.has(42u)); + REQUIRE(s.has(84u)); + REQUIRE(s.size() == 2u); + REQUIRE(s.find_index(42u).second); + REQUIRE(s.find_index(42u).first == 0u); + REQUIRE(s.find_index(84u).second); + REQUIRE(s.find_index(84u).first == 1u); + REQUIRE(s.get_index(42u) == 0u); + REQUIRE(s.get_index(84u) == 1u); + + REQUIRE(s.unordered_erase(42u)); + + REQUIRE_FALSE(s.has(42u)); + REQUIRE(s.has(84u)); + REQUIRE(s.size() == 1u); + REQUIRE(s.find_index(84u).second); + REQUIRE(s.find_index(84u).first == 0u); + REQUIRE_THROWS(s.get_index(42u)); + REQUIRE(s.get_index(84u) == 0u); + } + } + SECTION("sparse_map") { + using namespace ecs::detail; + { + struct obj_t { + int x; + obj_t(int nx) : x(nx) {} + }; + + sparse_map m; + + REQUIRE(m.empty()); + REQUIRE_FALSE(m.size()); + REQUIRE(m.capacity() == 0u); + REQUIRE_FALSE(m.has(42u)); + REQUIRE_THROWS(m.get_value(42u)); + REQUIRE_THROWS(as_const(m).get_value(42u)); + REQUIRE_FALSE(m.find_value(42u)); + REQUIRE_FALSE(as_const(m).find_value(42u)); + + { + obj_t o{21u}; + REQUIRE(m.insert(21u, o)); + REQUIRE(m.insert(42u, obj_t{42u})); + REQUIRE(m.emplace(84u, 84u)); + } + + { + obj_t o{21u}; + REQUIRE_FALSE(m.insert(21u, o)); + REQUIRE_FALSE(m.insert(42u, obj_t{42u})); + REQUIRE_FALSE(m.emplace(84u, 84u)); + } + + REQUIRE_FALSE(m.empty()); + REQUIRE(m.size() == 3u); + REQUIRE(m.capacity() >= 3u); + REQUIRE(m.has(21u)); + REQUIRE(m.has(42u)); + REQUIRE(m.has(84u)); + REQUIRE_FALSE(m.has(11u)); + REQUIRE_FALSE(m.has(25u)); + REQUIRE_FALSE(m.has(99u)); + + REQUIRE(m.get_value(21u).x == 21u); + REQUIRE(m.get_value(42u).x == 42u); + REQUIRE(m.get_value(84u).x == 84u); + REQUIRE(as_const(m).get_value(84u).x == 84u); + REQUIRE_THROWS(m.get_value(11u)); + REQUIRE_THROWS(m.get_value(25u)); + REQUIRE_THROWS(m.get_value(99u)); + REQUIRE_THROWS(as_const(m).get_value(99u)); + + REQUIRE(m.find_value(21u)->x == 21u); + REQUIRE(m.find_value(42u)->x == 42u); + REQUIRE(m.find_value(84u)->x == 84u); + REQUIRE(as_const(m).find_value(84u)->x == 84u); + REQUIRE_FALSE(m.find_value(11u)); + REQUIRE_FALSE(m.find_value(25u)); + REQUIRE_FALSE(m.find_value(99u)); + REQUIRE_FALSE(as_const(m).find_value(99u)); + + REQUIRE(m.unordered_erase(42u)); + REQUIRE_FALSE(m.unordered_erase(42u)); + + REQUIRE(m.has(21u)); + REQUIRE_FALSE(m.has(42u)); + REQUIRE(m.has(84u)); + REQUIRE(m.size() == 2u); + + m.clear(); + REQUIRE(m.empty()); + REQUIRE_FALSE(m.size()); + REQUIRE_FALSE(m.has(21u)); + REQUIRE_FALSE(m.has(42u)); + REQUIRE_FALSE(m.has(84u)); + } + } } -TEST_CASE("world") { +TEST_CASE("registry") { SECTION("entities") { { - ecs::world w; + ecs::registry w; ecs::entity e1{w}; ecs::entity e2{w}; @@ -58,7 +211,7 @@ TEST_CASE("world") { REQUIRE_FALSE(w.destroy_entity(e2)); } { - ecs::world w; + ecs::registry w; auto e1 = w.create_entity(); auto e2 = w.create_entity(); @@ -81,7 +234,7 @@ TEST_CASE("world") { } SECTION("component_assigning") { { - ecs::world w; + ecs::registry w; ecs::entity e1 = w.create_entity(); { @@ -120,7 +273,7 @@ TEST_CASE("world") { } } { - ecs::world w; + ecs::registry w; auto e1 = w.create_entity(); auto e2 = w.create_entity(); @@ -140,7 +293,7 @@ TEST_CASE("world") { REQUIRE(w.exists_component(e2)); } { - ecs::world w; + ecs::registry w; auto e1 = w.create_entity(); REQUIRE(e1.destroy()); REQUIRE_FALSE(e1.assign_component()); @@ -149,7 +302,7 @@ TEST_CASE("world") { } SECTION("component_accessing") { { - ecs::world w; + ecs::registry w; auto e1 = w.create_entity(); auto e2 = w.create_entity(); @@ -170,7 +323,7 @@ TEST_CASE("world") { REQUIRE_THROWS_AS(e2.get_component(), ecs::basic_exception); } { - ecs::world w; + ecs::registry w; const auto e1 = w.create_entity(); const auto e2 = w.create_entity(); @@ -185,7 +338,7 @@ TEST_CASE("world") { REQUIRE(e2.find_component()->y == 4); { - const ecs::world& ww = w; + const ecs::registry& ww = w; REQUIRE(ww.get_component(e1).x == 1); REQUIRE(ww.get_component(e1).y == 2); @@ -202,10 +355,79 @@ TEST_CASE("world") { REQUIRE_FALSE(ww.find_component(e2)); } } + { + ecs::registry w; + + auto e1 = w.create_entity(); + + REQUIRE(e1.find_components<>() == + std::make_tuple()); + REQUIRE(e1.find_components() == + std::make_tuple(nullptr)); + REQUIRE(e1.find_components() == + std::make_tuple(nullptr, nullptr)); + + REQUIRE(e1.get_components<>() == std::make_tuple()); + REQUIRE_THROWS(e1.get_components()); + REQUIRE_THROWS(e1.get_components()); + + { + const auto ee1 = e1; + + REQUIRE(ee1.find_components<>() == + std::make_tuple()); + REQUIRE(ee1.find_components() == + std::make_tuple(nullptr)); + REQUIRE(ee1.find_components() == + std::make_tuple(nullptr, nullptr)); + + REQUIRE(ee1.get_components<>() == std::make_tuple()); + REQUIRE_THROWS(ee1.get_components()); + REQUIRE_THROWS(ee1.get_components()); + } + + e1.assign_component(3, 4); + + REQUIRE(e1.find_components() == + std::make_tuple(e1.find_component())); + REQUIRE(e1.find_components() == + std::make_tuple(nullptr, e1.find_component())); + + REQUIRE(e1.get_components() == + std::make_tuple(e1.get_component())); + REQUIRE_THROWS(e1.get_components()); + + { + const auto ee1 = e1; + + REQUIRE(ee1.find_components() == + std::make_tuple(e1.find_component())); + REQUIRE(ee1.find_components() == + std::make_tuple(nullptr, e1.find_component())); + + REQUIRE(ee1.get_components() == + std::make_tuple(ee1.get_component())); + REQUIRE_THROWS(ee1.get_components()); + } + + e1.assign_component(1, 2); + + auto p = e1.get_components(); + std::get<0>(p).x = 10; + std::get<1>(p).x = 30; + REQUIRE(e1.get_component().x == 10); + REQUIRE(e1.get_component().x == 30); + + auto p2 = e1.find_components(); + std::get<0>(p2)->y = 20; + std::get<1>(p2)->y = 40; + REQUIRE(e1.get_component().y == 20); + REQUIRE(e1.get_component().y == 40); + } } SECTION("for_each_component") { { - ecs::world w; + ecs::registry w; auto e1 = w.create_entity(); auto e2 = w.create_entity(); @@ -227,7 +449,7 @@ TEST_CASE("world") { } { - const ecs::world& ww = w; + const ecs::registry& ww = w; ecs::entity_id acc1 = 0; int acc2 = 0; ww.for_each_component([&acc1, &acc2](ecs::entity e, const position_c& p){ @@ -239,10 +461,9 @@ TEST_CASE("world") { } } } - SECTION("for_joined_components") { { - ecs::world w; + ecs::registry w; auto e1 = w.create_entity(); auto e2 = w.create_entity(); @@ -272,7 +493,7 @@ TEST_CASE("world") { } { - const ecs::world& ww = w; + const ecs::registry& ww = w; ecs::entity_id acc1 = 0; int acc2 = 0; ww.for_joined_components([&acc1, &acc2]( @@ -286,4 +507,38 @@ TEST_CASE("world") { } } } + SECTION("systems") { + { + class movement_system : public ecs::system { + public: + void process(ecs::registry& owner) override { + owner.for_joined_components([]( + ecs::entity e, position_c& p, const velocity_c& v) + { + p.x += v.x; + p.y += v.y; + }); + } + }; + + ecs::registry w; + w.add_system(); + + auto e1 = w.create_entity(); + auto e2 = w.create_entity(); + + e1.assign_component(1, 2); + e1.assign_component(3, 4); + e2.assign_component(5, 6); + e2.assign_component(7, 8); + + w.process_systems(); + + REQUIRE(e1.get_component().x == 1 + 3); + REQUIRE(e1.get_component().y == 2 + 4); + + REQUIRE(e2.get_component().x == 5 + 7); + REQUIRE(e2.get_component().y == 6 + 8); + } + } }