From 5644ac5bae58da1c9e08aff81a60413cf80d7eb3 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Mon, 24 Dec 2018 21:19:45 +0700 Subject: [PATCH 1/8] detail::type_family --- .codecov.yml | 5 +++-- ecs.hpp | 44 +++++++++++++++++++++++++++++++++++++- ecs_tests.cpp | 25 ++++++++++++++++------ scripts/upload_coverage.sh | 2 +- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index c1c3711..69ebbfa 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,3 +1,4 @@ ignore: - - catch.hpp - - catch_main.hpp + - "catch.hpp" + - "catch_main.hpp" + - "*_tests.cpp" diff --git a/ecs.hpp b/ecs.hpp index 60e5e16..7f31414 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -7,11 +7,14 @@ #pragma once #include +#include #include #include #include +#include #include +#include #include // ----------------------------------------------------------------------------- @@ -25,7 +28,46 @@ namespace ecs_hpp class world; class entity; - using entity_id = std::uint64_t; + using family_id = std::uint16_t; + using entity_id = std::uint32_t; + + static_assert( + std::is_unsigned::value, + "ecs_hpp::family_id must be an unsigned integer"); + + static_assert( + std::is_unsigned::value, + "ecs_hpp::entity_id must be an unsigned integer"); +} + +// ----------------------------------------------------------------------------- +// +// detail +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + namespace detail + { + template < typename Tag = void > + struct type_family_base { + static family_id last_id_; + }; + + template < typename T > + class type_family : private type_family_base<> { + public: + static family_id id() noexcept { + static family_id self_id = ++last_id_; + assert(self_id > 0u && "ecs_hpp::family_id overflow"); + return self_id; + } + }; + + template < typename Tag > + family_id type_family_base::last_id_{0u}; + } } // ----------------------------------------------------------------------------- diff --git a/ecs_tests.cpp b/ecs_tests.cpp index 57bb26a..c1b2af1 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -12,23 +12,36 @@ namespace ecs = ecs_hpp; namespace { - struct position { + struct position_c { int x{0}; int y{0}; - position() = default; - position(int nx, int ny) : x(nx), y(ny) {} + position_c() = default; + position_c(int nx, int ny) : x(nx), y(ny) {} }; - struct velocity { + struct velocity_c { int dx{0}; int dy{0}; - velocity() = default; - velocity(int ndx, int ndy) : dx(ndx), dy(ndy) {} + velocity_c() = default; + velocity_c(int ndx, int ndy) : dx(ndx), dy(ndy) {} }; } +TEST_CASE("detail") { + SECTION("get_type_id") { + REQUIRE(ecs::detail::type_family::id() == 1u); + REQUIRE(ecs::detail::type_family::id() == 1u); + + REQUIRE(ecs::detail::type_family::id() == 2u); + REQUIRE(ecs::detail::type_family::id() == 2u); + + REQUIRE(ecs::detail::type_family::id() == 1u); + REQUIRE(ecs::detail::type_family::id() == 2u); + } +} + TEST_CASE("world") { SECTION("entities") { { diff --git a/scripts/upload_coverage.sh b/scripts/upload_coverage.sh index d13a427..d7f223a 100755 --- a/scripts/upload_coverage.sh +++ b/scripts/upload_coverage.sh @@ -11,7 +11,7 @@ lcov -d . -z ctest --verbose lcov -d . -c -o "coverage.info" -lcov -r "coverage.info" "*/usr/*" "*/catch.hpp" "*/catch_main.cpp" -o "coverage.info" +lcov -r "coverage.info" "*/usr/*" "*/catch.hpp" "*/catch_main.cpp" "*_tests.cpp" -o "coverage.info" lcov -l "coverage.info" bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" From 89a52de8b8b46328a7d4f8594e4d7f491dfffd2c Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 25 Dec 2018 16:17:30 +0700 Subject: [PATCH 2/8] fix msvc compilation --- ecs.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ecs.hpp b/ecs.hpp index 7f31414..57c49bc 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -50,13 +50,17 @@ namespace ecs_hpp { namespace detail { - template < typename Tag = void > - struct type_family_base { + template < typename Void = void > + class type_family_base { + static_assert( + std::is_void::value, + "unexpected internal error"); + protected: static family_id last_id_; }; template < typename T > - class type_family : private type_family_base<> { + class type_family : public type_family_base<> { public: static family_id id() noexcept { static family_id self_id = ++last_id_; @@ -65,8 +69,8 @@ namespace ecs_hpp } }; - template < typename Tag > - family_id type_family_base::last_id_{0u}; + template < typename Void > + family_id type_family_base::last_id_ = 0u; } } From 828b4ab07ee35f84e3ba8875a56fc3e1d62d2516 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 25 Dec 2018 19:18:26 +0700 Subject: [PATCH 3/8] dummy component storage --- ecs.hpp | 169 +++++++++++++++++++++++++++++++++++++++++++++++++- ecs_tests.cpp | 39 ++++++++++++ 2 files changed, 207 insertions(+), 1 deletion(-) diff --git a/ecs.hpp b/ecs.hpp index 57c49bc..ec624e4 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -11,11 +11,13 @@ #include #include +#include #include #include #include #include #include +#include // ----------------------------------------------------------------------------- // @@ -42,7 +44,7 @@ namespace ecs_hpp // ----------------------------------------------------------------------------- // -// detail +// detail::type_family // // ----------------------------------------------------------------------------- @@ -74,6 +76,52 @@ namespace ecs_hpp } } +// ----------------------------------------------------------------------------- +// +// detail::component_storage +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + namespace detail + { + class component_storage_base { + public: + virtual ~component_storage_base() = default; + virtual bool remove(entity_id id) noexcept = 0; + virtual bool exists(entity_id id) const noexcept = 0; + }; + + template < typename T > + class component_storage : public component_storage_base { + public: + template < typename... Args > + void assign(entity_id id, Args&&... args); + bool remove(entity_id id) noexcept override; + bool exists(entity_id id) const noexcept override; + private: + std::unordered_map components_; + }; + + template < typename T > + template < typename... Args > + void component_storage::assign(entity_id id, Args&&... args) { + components_[id] = T(std::forward(args)...); + } + + template < typename T > + bool component_storage::remove(entity_id id) noexcept { + return components_.erase(id) > 0u; + } + + template < typename T > + bool component_storage::exists(entity_id id) const noexcept { + return components_.find(id) != components_.end(); + } + } +} + // ----------------------------------------------------------------------------- // // entity @@ -91,6 +139,18 @@ namespace ecs_hpp entity_id id() const noexcept; bool destroy(); + bool is_alive() const noexcept; + + template < typename T, typename... Args > + void assign_component(Args&&... args); + + template < typename T > + bool remove_component(); + + template < typename T > + bool exists_component() const noexcept; + + std::size_t remove_all_components() noexcept; private: world& owner_; entity_id id_{0u}; @@ -128,10 +188,30 @@ namespace ecs_hpp entity create_entity(); bool destroy_entity(const entity& ent); bool is_entity_alive(const entity& ent) const noexcept; + + template < typename T, typename... Args > + void assign_component(const entity& ent, Args&&... args); + + template < typename T > + bool remove_component(const entity& ent); + + template < typename T > + bool exists_component(const entity& ent) const noexcept; + + std::size_t remove_all_components(const entity& ent) const noexcept; + private: + template < typename T > + detail::component_storage* find_storage_() const; + template < typename T > + detail::component_storage& get_or_create_storage_(); private: mutable std::mutex mutex_; + entity_id last_entity_id_{0u}; std::unordered_set entities_; + + using storage_uptr = std::unique_ptr; + std::unordered_map storages_; }; } @@ -162,6 +242,31 @@ namespace ecs_hpp return owner_.destroy_entity(*this); } + inline bool entity::is_alive() const noexcept { + return owner_.is_entity_alive(*this); + } + + template < typename T, typename... Args > + void entity::assign_component(Args&&... args) { + owner_.assign_component( + *this, + std::forward(args)...); + } + + template < typename T > + bool entity::remove_component() { + return owner_.remove_component(*this); + } + + template < typename T > + bool entity::exists_component() const noexcept { + return owner_.exists_component(*this); + } + + inline std::size_t entity::remove_all_components() noexcept { + return owner_.remove_all_components(*this); + } + inline bool operator==(const entity& l, const entity& r) noexcept { return l.id() == r.id(); } @@ -199,4 +304,66 @@ namespace ecs_hpp std::lock_guard guard(mutex_); return entities_.count(ent) > 0u; } + + template < typename T, typename... Args > + void world::assign_component(const entity& ent, Args&&... args) { + std::lock_guard guard(mutex_); + get_or_create_storage_().assign( + ent.id(), + std::forward(args)...); + } + + template < typename T > + bool world::remove_component(const entity& ent) { + std::lock_guard guard(mutex_); + const detail::component_storage* storage = find_storage_(); + return storage + ? storage->remove(ent.id()) + : false; + } + + template < typename T > + bool world::exists_component(const entity& ent) const noexcept { + std::lock_guard guard(mutex_); + const detail::component_storage* storage = find_storage_(); + return storage + ? storage->exists(ent.id()) + : false; + } + + inline std::size_t world::remove_all_components(const entity& ent) const noexcept { + std::lock_guard guard(mutex_); + std::size_t removed_components = 0u; + for ( auto& storage_p : storages_ ) { + if ( storage_p.second->remove(ent.id()) ) { + ++removed_components; + } + } + return removed_components; + } + + template < typename T > + detail::component_storage* world::find_storage_() const { + 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; + } + + template < typename T > + detail::component_storage& world::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( + family, + std::make_unique>())); + assert(emplace_r.second && "unexpected internal error"); + return *static_cast*>( + emplace_r.first->second.get()); + } } diff --git a/ecs_tests.cpp b/ecs_tests.cpp index c1b2af1..e5912ef 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -80,5 +80,44 @@ TEST_CASE("world") { } } SECTION("components") { + { + ecs::world w; + ecs::entity e1{w}; + + { + REQUIRE_FALSE(w.exists_component(e1)); + REQUIRE_FALSE(w.exists_component(e1)); + + w.assign_component(e1); + + REQUIRE(w.exists_component(e1)); + REQUIRE_FALSE(w.exists_component(e1)); + + w.assign_component(e1); + + REQUIRE(w.exists_component(e1)); + REQUIRE(w.exists_component(e1)); + + REQUIRE(w.remove_all_components(e1) == 2u); + + REQUIRE_FALSE(w.exists_component(e1)); + REQUIRE_FALSE(w.exists_component(e1)); + } + + { + REQUIRE_FALSE(e1.exists_component()); + REQUIRE_FALSE(e1.exists_component()); + + e1.assign_component(); + + REQUIRE(e1.exists_component()); + REQUIRE_FALSE(e1.exists_component()); + + e1.assign_component(); + + REQUIRE(e1.exists_component()); + REQUIRE(e1.exists_component()); + } + } } } From 64b919763d6fdc56a1ffe0559d7061abe47342c2 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 25 Dec 2018 19:53:21 +0700 Subject: [PATCH 4/8] remove all entity components after destroy --- ecs.hpp | 22 +++++++++++++++------- ecs_tests.cpp | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ecs.hpp b/ecs.hpp index ec624e4..336958e 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -202,8 +202,11 @@ namespace ecs_hpp private: template < typename T > detail::component_storage* find_storage_() const; + template < typename T > detail::component_storage& get_or_create_storage_(); + + std::size_t remove_all_components_impl_(const entity& ent) const noexcept; private: mutable std::mutex mutex_; @@ -297,6 +300,7 @@ namespace ecs_hpp inline bool world::destroy_entity(const entity& ent) { std::lock_guard guard(mutex_); + remove_all_components_impl_(ent); return entities_.erase(ent) > 0u; } @@ -333,13 +337,7 @@ namespace ecs_hpp inline std::size_t world::remove_all_components(const entity& ent) const noexcept { std::lock_guard guard(mutex_); - std::size_t removed_components = 0u; - for ( auto& storage_p : storages_ ) { - if ( storage_p.second->remove(ent.id()) ) { - ++removed_components; - } - } - return removed_components; + return remove_all_components_impl_(ent); } template < typename T > @@ -366,4 +364,14 @@ namespace ecs_hpp return *static_cast*>( emplace_r.first->second.get()); } + + inline std::size_t world::remove_all_components_impl_(const entity& ent) const noexcept { + std::size_t removed_components = 0u; + for ( auto& storage_p : storages_ ) { + if ( storage_p.second->remove(ent.id()) ) { + ++removed_components; + } + } + return removed_components; + } } diff --git a/ecs_tests.cpp b/ecs_tests.cpp index e5912ef..851c72c 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -119,5 +119,25 @@ TEST_CASE("world") { REQUIRE(e1.exists_component()); } } + { + ecs::world w; + + auto e1 = w.create_entity(); + auto e2 = w.create_entity(); + + w.assign_component(e1); + w.assign_component(e1); + + w.assign_component(e2); + w.assign_component(e2); + + w.destroy_entity(e1); + + REQUIRE_FALSE(w.exists_component(e1)); + REQUIRE_FALSE(w.exists_component(e1)); + + REQUIRE(w.exists_component(e2)); + REQUIRE(w.exists_component(e2)); + } } } From e29aa3d4ebdf0ce1d576b6168adf6fa10c9addb4 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 25 Dec 2018 20:08:27 +0700 Subject: [PATCH 5/8] =?UTF-8?q?don=E2=80=99t=20assign=20components=20to=20?= =?UTF-8?q?dead=20entities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ecs.hpp | 33 ++++++++++++++++++++++++++------- ecs_tests.cpp | 27 +++++++++++++++++---------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/ecs.hpp b/ecs.hpp index 336958e..ac46daf 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -142,7 +142,7 @@ namespace ecs_hpp bool is_alive() const noexcept; template < typename T, typename... Args > - void assign_component(Args&&... args); + bool assign_component(Args&&... args); template < typename T > bool remove_component(); @@ -190,7 +190,7 @@ namespace ecs_hpp bool is_entity_alive(const entity& ent) const noexcept; template < typename T, typename... Args > - void assign_component(const entity& ent, Args&&... args); + bool assign_component(const entity& ent, Args&&... args); template < typename T > bool remove_component(const entity& ent); @@ -206,6 +206,7 @@ namespace ecs_hpp template < typename T > detail::component_storage& get_or_create_storage_(); + bool is_entity_alive_impl_(const entity& ent) const noexcept; std::size_t remove_all_components_impl_(const entity& ent) const noexcept; private: mutable std::mutex mutex_; @@ -250,8 +251,8 @@ namespace ecs_hpp } template < typename T, typename... Args > - void entity::assign_component(Args&&... args) { - owner_.assign_component( + bool entity::assign_component(Args&&... args) { + return owner_.assign_component( *this, std::forward(args)...); } @@ -271,7 +272,8 @@ namespace ecs_hpp } inline bool operator==(const entity& l, const entity& r) noexcept { - return l.id() == r.id(); + return &l.owner() == &r.owner() + && l.id() == r.id(); } inline bool operator!=(const entity& l, const entity& r) noexcept { @@ -306,20 +308,27 @@ namespace ecs_hpp inline bool world::is_entity_alive(const entity& ent) const noexcept { std::lock_guard guard(mutex_); - return entities_.count(ent) > 0u; + return is_entity_alive_impl_(ent); } template < typename T, typename... Args > - void world::assign_component(const entity& ent, Args&&... args) { + bool world::assign_component(const entity& ent, Args&&... args) { std::lock_guard guard(mutex_); + if ( !is_entity_alive_impl_(ent) ) { + return false; + } get_or_create_storage_().assign( ent.id(), std::forward(args)...); + return true; } template < typename T > bool world::remove_component(const entity& ent) { std::lock_guard guard(mutex_); + if ( !is_entity_alive_impl_(ent) ) { + return false; + } const detail::component_storage* storage = find_storage_(); return storage ? storage->remove(ent.id()) @@ -329,6 +338,9 @@ namespace ecs_hpp template < typename T > bool world::exists_component(const entity& ent) const noexcept { std::lock_guard guard(mutex_); + if ( !is_entity_alive_impl_(ent) ) { + return false; + } const detail::component_storage* storage = find_storage_(); return storage ? storage->exists(ent.id()) @@ -365,7 +377,14 @@ namespace ecs_hpp emplace_r.first->second.get()); } + inline bool world::is_entity_alive_impl_(const entity& ent) const noexcept { + return entities_.count(ent) > 0u; + } + inline std::size_t world::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()) ) { diff --git a/ecs_tests.cpp b/ecs_tests.cpp index 851c72c..f5bc2dd 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -82,18 +82,18 @@ TEST_CASE("world") { SECTION("components") { { ecs::world w; - ecs::entity e1{w}; + ecs::entity e1 = w.create_entity(); { REQUIRE_FALSE(w.exists_component(e1)); REQUIRE_FALSE(w.exists_component(e1)); - w.assign_component(e1); + REQUIRE(w.assign_component(e1)); REQUIRE(w.exists_component(e1)); REQUIRE_FALSE(w.exists_component(e1)); - w.assign_component(e1); + REQUIRE(w.assign_component(e1)); REQUIRE(w.exists_component(e1)); REQUIRE(w.exists_component(e1)); @@ -108,12 +108,12 @@ TEST_CASE("world") { REQUIRE_FALSE(e1.exists_component()); REQUIRE_FALSE(e1.exists_component()); - e1.assign_component(); + REQUIRE(e1.assign_component()); REQUIRE(e1.exists_component()); REQUIRE_FALSE(e1.exists_component()); - e1.assign_component(); + REQUIRE(e1.assign_component()); REQUIRE(e1.exists_component()); REQUIRE(e1.exists_component()); @@ -125,13 +125,13 @@ TEST_CASE("world") { auto e1 = w.create_entity(); auto e2 = w.create_entity(); - w.assign_component(e1); - w.assign_component(e1); + REQUIRE(w.assign_component(e1)); + REQUIRE(w.assign_component(e1)); - w.assign_component(e2); - w.assign_component(e2); + REQUIRE(w.assign_component(e2)); + REQUIRE(w.assign_component(e2)); - w.destroy_entity(e1); + REQUIRE(w.destroy_entity(e1)); REQUIRE_FALSE(w.exists_component(e1)); REQUIRE_FALSE(w.exists_component(e1)); @@ -139,5 +139,12 @@ TEST_CASE("world") { REQUIRE(w.exists_component(e2)); REQUIRE(w.exists_component(e2)); } + { + ecs::world w; + auto e1 = w.create_entity(); + REQUIRE(e1.destroy()); + REQUIRE_FALSE(e1.assign_component()); + REQUIRE_FALSE(w.exists_component(e1)); + } } } From 078c6c0a4c6bf913679487069b1e3d3a227f4366 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 25 Dec 2018 20:38:17 +0700 Subject: [PATCH 6/8] component access --- ecs.hpp | 130 +++++++++++++++++++++++++++++++++++++++++++++++++- ecs_tests.cpp | 49 ++++++++++++++++++- 2 files changed, 176 insertions(+), 3 deletions(-) diff --git a/ecs.hpp b/ecs.hpp index ac46daf..9affc02 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -100,6 +102,8 @@ namespace ecs_hpp void assign(entity_id id, Args&&... args); bool remove(entity_id id) noexcept override; bool exists(entity_id id) const noexcept override; + T* find(entity_id id) noexcept; + const T* find(entity_id id) const noexcept; private: std::unordered_map components_; }; @@ -119,9 +123,40 @@ namespace ecs_hpp bool component_storage::exists(entity_id id) const noexcept { return components_.find(id) != components_.end(); } + + template < typename T > + T* component_storage::find(entity_id id) noexcept { + const auto iter = components_.find(id); + return iter != components_.end() + ? &iter->second + : nullptr; + } + + 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; + } } } +// ----------------------------------------------------------------------------- +// +// exceptions +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + class basic_exception : public std::logic_error { + public: + basic_exception(const char* msg) + : std::logic_error(msg) {} + }; +} + // ----------------------------------------------------------------------------- // // entity @@ -151,6 +186,18 @@ namespace ecs_hpp bool exists_component() const noexcept; std::size_t remove_all_components() noexcept; + + template < typename T > + T& get_component(); + + template < typename T > + const T& get_component() const; + + template < typename T > + T* find_component() noexcept; + + template < typename T > + const T* find_component() const noexcept; private: world& owner_; entity_id id_{0u}; @@ -199,9 +246,24 @@ namespace ecs_hpp bool exists_component(const entity& ent) const noexcept; std::size_t remove_all_components(const entity& ent) const noexcept; + + template < typename T > + T& get_component(const entity& ent); + + template < typename T > + const T& get_component(const entity& ent) const; + + template < typename T > + T* find_component(const entity& ent) noexcept; + + template < typename T > + const T* find_component(const entity& ent) const noexcept; private: template < typename T > - detail::component_storage* find_storage_() const; + detail::component_storage* find_storage_() noexcept; + + template < typename T > + const detail::component_storage* find_storage_() const noexcept; template < typename T > detail::component_storage& get_or_create_storage_(); @@ -271,6 +333,26 @@ namespace ecs_hpp return owner_.remove_all_components(*this); } + template < typename T > + T& entity::get_component() { + return owner_.get_component(*this); + } + + template < typename T > + const T& entity::get_component() const { + return owner_.get_component(*this); + } + + template < typename T > + T* entity::find_component() noexcept { + return owner_.find_component(*this); + } + + template < typename T > + const T* entity::find_component() const noexcept { + return owner_.find_component(*this); + } + inline bool operator==(const entity& l, const entity& r) noexcept { return &l.owner() == &r.owner() && l.id() == r.id(); @@ -353,7 +435,41 @@ namespace ecs_hpp } template < typename T > - detail::component_storage* world::find_storage_() const { + T& world::get_component(const entity& ent) { + T* component = find_component(ent); + if ( component ) { + return *component; + } + throw basic_exception("component not found"); + } + + template < typename T > + const T& world::get_component(const entity& ent) const { + const T* component = find_component(ent); + if ( component ) { + return *component; + } + throw basic_exception("component not found"); + } + + template < typename T > + T* world::find_component(const entity& ent) noexcept { + detail::component_storage* storage = find_storage_(); + return storage + ? storage->find(ent.id()) + : nullptr; + } + + template < typename T > + const T* world::find_component(const entity& ent) const noexcept { + const detail::component_storage* storage = find_storage_(); + return storage + ? storage->find(ent.id()) + : nullptr; + } + + 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() ) { @@ -362,6 +478,16 @@ namespace ecs_hpp return nullptr; } + template < typename T > + const detail::component_storage* world::find_storage_() const 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; + } + template < typename T > detail::component_storage& world::get_or_create_storage_() { detail::component_storage* storage = find_storage_(); diff --git a/ecs_tests.cpp b/ecs_tests.cpp index f5bc2dd..eeaf611 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -79,7 +79,7 @@ TEST_CASE("world") { REQUIRE_FALSE(w.destroy_entity(e2)); } } - SECTION("components") { + SECTION("component_assigning") { { ecs::world w; ecs::entity e1 = w.create_entity(); @@ -147,4 +147,51 @@ TEST_CASE("world") { REQUIRE_FALSE(w.exists_component(e1)); } } + SECTION("component_accessing") { + { + ecs::world w; + + auto e1 = w.create_entity(); + auto e2 = w.create_entity(); + + REQUIRE_FALSE(e1.find_component()); + REQUIRE_FALSE(e2.find_component()); + + e1.assign_component(1, 2); + e2.assign_component(3, 4); + + REQUIRE(e1.get_component().x == 1); + REQUIRE(e1.get_component().y == 2); + + REQUIRE(e2.get_component().dx == 3); + REQUIRE(e2.get_component().dy == 4); + + REQUIRE_THROWS_AS(e1.get_component(), ecs::basic_exception); + REQUIRE_THROWS_AS(e2.get_component(), ecs::basic_exception); + } + { + ecs::world w; + + const auto e1 = w.create_entity(); + const auto e2 = w.create_entity(); + + REQUIRE_FALSE(e1.find_component()); + REQUIRE_FALSE(e2.find_component()); + + w.assign_component(e1, 1, 2); + w.assign_component(e2, 3, 4); + + { + const ecs::world& ww = w; + REQUIRE(ww.get_component(e1).x == 1); + REQUIRE(ww.get_component(e1).y == 2); + + REQUIRE(ww.get_component(e2).dx == 3); + REQUIRE(ww.get_component(e2).dy == 4); + + REQUIRE_THROWS_AS(ww.get_component(e1), ecs::basic_exception); + REQUIRE_THROWS_AS(ww.get_component(e2), ecs::basic_exception); + } + } + } } From 0d93335267e3fb94352ea15300095f31a963033f Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 25 Dec 2018 21:16:29 +0700 Subject: [PATCH 7/8] world: for_each_component --- ecs.hpp | 64 ++++++++++++++++++++++++++++++++++++++++++++++++--- ecs_tests.cpp | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/ecs.hpp b/ecs.hpp index 9affc02..463da10 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -98,16 +98,28 @@ namespace ecs_hpp template < typename T > class component_storage : public component_storage_base { public: + component_storage(world& owner); + template < typename... Args > void assign(entity_id id, Args&&... args); bool remove(entity_id id) noexcept override; bool exists(entity_id id) const noexcept override; T* find(entity_id id) noexcept; const T* find(entity_id id) const noexcept; + + template < typename F > + void for_each_component(F&& f) noexcept; + template < typename F > + void for_each_component(F&& f) const noexcept; private: + world& owner_; std::unordered_map components_; }; + template < typename T > + component_storage::component_storage(world& owner) + : owner_(owner) {} + template < typename T > template < typename... Args > void component_storage::assign(entity_id id, Args&&... args) { @@ -139,6 +151,22 @@ namespace ecs_hpp ? &iter->second : nullptr; } + + 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); + } + } + + 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); + } + } } } @@ -258,6 +286,12 @@ namespace ecs_hpp template < typename T > const T* find_component(const entity& ent) const noexcept; + + template < typename T, typename F > + void for_each_component(F&& f) noexcept; + + template < typename T, typename F > + void for_each_component(F&& f) const noexcept; private: template < typename T > detail::component_storage* find_storage_() noexcept; @@ -436,7 +470,9 @@ namespace ecs_hpp template < typename T > T& world::get_component(const entity& ent) { - T* component = find_component(ent); + std::lock_guard guard(mutex_); + detail::component_storage* storage = find_storage_(); + T* component = storage ? storage->find(ent.id()) : nullptr; if ( component ) { return *component; } @@ -445,7 +481,9 @@ namespace ecs_hpp template < typename T > const T& world::get_component(const entity& ent) const { - const T* component = find_component(ent); + std::lock_guard guard(mutex_); + const detail::component_storage* storage = find_storage_(); + const T* component = storage ? storage->find(ent.id()) : nullptr; if ( component ) { return *component; } @@ -454,6 +492,7 @@ namespace ecs_hpp template < typename T > T* world::find_component(const entity& ent) noexcept { + std::lock_guard guard(mutex_); detail::component_storage* storage = find_storage_(); return storage ? storage->find(ent.id()) @@ -462,12 +501,31 @@ namespace ecs_hpp template < typename T > const T* world::find_component(const entity& ent) const noexcept { + std::lock_guard guard(mutex_); const detail::component_storage* storage = find_storage_(); return storage ? storage->find(ent.id()) : nullptr; } + template < typename T, typename F > + void world::for_each_component(F&& f) noexcept { + std::lock_guard guard(mutex_); + detail::component_storage* storage = find_storage_(); + if ( storage ) { + storage->for_each_component(std::forward(f)); + } + } + + template < typename T, typename F > + void world::for_each_component(F&& f) const noexcept { + std::lock_guard guard(mutex_); + const detail::component_storage* storage = find_storage_(); + if ( storage ) { + storage->for_each_component(std::forward(f)); + } + } + template < typename T > detail::component_storage* world::find_storage_() noexcept { const auto family = detail::type_family::id(); @@ -497,7 +555,7 @@ namespace ecs_hpp const auto family = detail::type_family::id(); const auto emplace_r = storages_.emplace(std::make_pair( family, - std::make_unique>())); + std::make_unique>(*this))); assert(emplace_r.second && "unexpected internal error"); return *static_cast*>( emplace_r.first->second.get()); diff --git a/ecs_tests.cpp b/ecs_tests.cpp index eeaf611..8f7d9dd 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -181,6 +181,9 @@ TEST_CASE("world") { w.assign_component(e1, 1, 2); w.assign_component(e2, 3, 4); + REQUIRE(e1.find_component()->y == 2); + REQUIRE(e2.find_component()->dy == 4); + { const ecs::world& ww = w; REQUIRE(ww.get_component(e1).x == 1); @@ -191,6 +194,48 @@ TEST_CASE("world") { REQUIRE_THROWS_AS(ww.get_component(e1), ecs::basic_exception); REQUIRE_THROWS_AS(ww.get_component(e2), ecs::basic_exception); + + ww.remove_all_components(e1); + ww.remove_all_components(e2); + + REQUIRE_FALSE(ww.find_component(e1)); + REQUIRE_FALSE(ww.find_component(e2)); + } + } + } + SECTION("for_each_component") { + { + ecs::world w; + + 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); + + { + ecs::entity_id acc1 = 0; + int acc2 = 0; + w.for_each_component([&acc1, &acc2](ecs::entity e, position_c& p){ + acc1 += e.id(); + acc2 += p.x; + }); + REQUIRE(acc1 == e1.id() + e2.id()); + REQUIRE(acc2 == 6); + } + + { + const ecs::world& ww = w; + ecs::entity_id acc1 = 0; + int acc2 = 0; + ww.for_each_component([&acc1, &acc2](ecs::entity e, const position_c& p){ + acc1 += e.id(); + acc2 += p.x; + }); + REQUIRE(acc1 == e1.id() + e2.id()); + REQUIRE(acc2 == 6); } } } From 32fb2f27904a38e84574259d90e3a5f855053a14 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Wed, 26 Dec 2018 02:52:20 +0700 Subject: [PATCH 8/8] for_each and for_joined reference impl --- ecs.hpp | 128 ++++++++++++++++++++++++++++++++++++++++++-------- ecs_tests.cpp | 63 +++++++++++++++++++++---- 2 files changed, 163 insertions(+), 28 deletions(-) diff --git a/ecs.hpp b/ecs.hpp index 463da10..6f05bb5 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -277,25 +277,26 @@ namespace ecs_hpp template < typename T > T& get_component(const entity& ent); - template < typename T > const T& get_component(const entity& ent) const; template < typename T > T* find_component(const entity& ent) noexcept; - template < typename T > const T* find_component(const entity& ent) const noexcept; template < typename T, typename F > - void for_each_component(F&& f) noexcept; - + void for_each_component(F&& f); template < typename T, typename F > - void for_each_component(F&& f) const noexcept; + void for_each_component(F&& f) const; + + template < typename... Ts, typename F > + void for_joined_components(F&& f); + template < typename... Ts, typename F > + void for_joined_components(F&& f) const; private: template < typename T > detail::component_storage* find_storage_() noexcept; - template < typename T > const detail::component_storage* find_storage_() const noexcept; @@ -304,6 +305,25 @@ namespace ecs_hpp bool is_entity_alive_impl_(const entity& ent) const noexcept; std::size_t remove_all_components_impl_(const entity& ent) const noexcept; + + template < typename T > + T* find_component_impl_(const entity& ent) noexcept; + template < typename T > + const T* find_component_impl_(const entity& ent) const noexcept; + + template < typename... Ts, typename F > + void for_joined_components_impl_(F&& f); + template < typename T, typename... Ts, typename F, typename... Cs > + void for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs); + template < typename F, typename... Cs > + void for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs); + + template < typename... Ts, typename F > + void for_joined_components_impl_(F&& f) const; + template < typename T, typename... Ts, typename F, typename... Cs > + void for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) const; + template < typename F, typename... Cs > + void for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) const; private: mutable std::mutex mutex_; @@ -471,8 +491,7 @@ namespace ecs_hpp template < typename T > T& world::get_component(const entity& ent) { std::lock_guard guard(mutex_); - detail::component_storage* storage = find_storage_(); - T* component = storage ? storage->find(ent.id()) : nullptr; + T* component = find_component_impl_(ent); if ( component ) { return *component; } @@ -482,8 +501,7 @@ namespace ecs_hpp template < typename T > const T& world::get_component(const entity& ent) const { std::lock_guard guard(mutex_); - const detail::component_storage* storage = find_storage_(); - const T* component = storage ? storage->find(ent.id()) : nullptr; + const T* component = find_component_impl_(ent); if ( component ) { return *component; } @@ -493,23 +511,17 @@ namespace ecs_hpp template < typename T > T* world::find_component(const entity& ent) noexcept { std::lock_guard guard(mutex_); - detail::component_storage* storage = find_storage_(); - return storage - ? storage->find(ent.id()) - : nullptr; + return find_component_impl_(ent); } template < typename T > const T* world::find_component(const entity& ent) const noexcept { std::lock_guard guard(mutex_); - const detail::component_storage* storage = find_storage_(); - return storage - ? storage->find(ent.id()) - : nullptr; + return find_component_impl_(ent); } template < typename T, typename F > - void world::for_each_component(F&& f) noexcept { + void world::for_each_component(F&& f) { std::lock_guard guard(mutex_); detail::component_storage* storage = find_storage_(); if ( storage ) { @@ -518,7 +530,7 @@ namespace ecs_hpp } template < typename T, typename F > - void world::for_each_component(F&& f) const noexcept { + void world::for_each_component(F&& f) const { std::lock_guard guard(mutex_); const detail::component_storage* storage = find_storage_(); if ( storage ) { @@ -526,6 +538,18 @@ namespace ecs_hpp } } + template < typename... Ts, typename F > + void world::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 { + 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(); @@ -577,4 +601,68 @@ namespace ecs_hpp } return removed_components; } + + template < typename T > + T* world::find_component_impl_(const entity& ent) noexcept { + detail::component_storage* storage = find_storage_(); + return storage + ? storage->find(ent.id()) + : nullptr; + } + + template < typename T > + const T* world::find_component_impl_(const entity& ent) const noexcept { + const detail::component_storage* storage = find_storage_(); + return storage + ? storage->find(ent.id()) + : nullptr; + } + + 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)); + } + } + + template < typename T, typename... Ts, typename F, typename... Cs > + void world::for_joined_components_impl_(const entity& e, F&& f, Cs&&... cs) { + T* c = find_component_impl_(e); + if ( c ) { + for_joined_components_impl_( + e, + std::forward(f), + std::forward(cs)..., + *c); + } + } + + template < typename F, typename... Cs > + void world::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)); + } + } + + template < typename T, typename... Ts, typename F, typename... Cs > + void world::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_( + e, + std::forward(f), + std::forward(cs)..., + *c); + } + } + + template < typename F, typename... Cs > + void world::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 8f7d9dd..d4efab9 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -21,11 +21,11 @@ namespace }; struct velocity_c { - int dx{0}; - int dy{0}; + int x{0}; + int y{0}; velocity_c() = default; - velocity_c(int ndx, int ndy) : dx(ndx), dy(ndy) {} + velocity_c(int nx, int ny) : x(nx), y(ny) {} }; } @@ -163,8 +163,8 @@ TEST_CASE("world") { REQUIRE(e1.get_component().x == 1); REQUIRE(e1.get_component().y == 2); - REQUIRE(e2.get_component().dx == 3); - REQUIRE(e2.get_component().dy == 4); + REQUIRE(e2.get_component().x == 3); + REQUIRE(e2.get_component().y == 4); REQUIRE_THROWS_AS(e1.get_component(), ecs::basic_exception); REQUIRE_THROWS_AS(e2.get_component(), ecs::basic_exception); @@ -182,15 +182,15 @@ TEST_CASE("world") { w.assign_component(e2, 3, 4); REQUIRE(e1.find_component()->y == 2); - REQUIRE(e2.find_component()->dy == 4); + REQUIRE(e2.find_component()->y == 4); { const ecs::world& ww = w; REQUIRE(ww.get_component(e1).x == 1); REQUIRE(ww.get_component(e1).y == 2); - REQUIRE(ww.get_component(e2).dx == 3); - REQUIRE(ww.get_component(e2).dy == 4); + REQUIRE(ww.get_component(e2).x == 3); + REQUIRE(ww.get_component(e2).y == 4); REQUIRE_THROWS_AS(ww.get_component(e1), ecs::basic_exception); REQUIRE_THROWS_AS(ww.get_component(e2), ecs::basic_exception); @@ -239,4 +239,51 @@ TEST_CASE("world") { } } } + + SECTION("for_joined_components") { + { + ecs::world w; + + auto e1 = w.create_entity(); + auto e2 = w.create_entity(); + auto e3 = w.create_entity(); + auto e4 = w.create_entity(); + w.create_entity(); + + e1.assign_component(1, 2); + e1.assign_component(3, 4); + e2.assign_component(5, 6); + e2.assign_component(7, 8); + + e3.assign_component(100, 500); + e4.assign_component(500, 100); + + { + ecs::entity_id acc1 = 0; + int acc2 = 0; + w.for_joined_components([&acc1, &acc2]( + ecs::entity e, const position_c& p, const velocity_c& v) + { + acc1 += e.id(); + acc2 += p.x + v.x; + }); + REQUIRE(acc1 == e1.id() + e2.id()); + REQUIRE(acc2 == 16); + } + + { + const ecs::world& ww = w; + ecs::entity_id acc1 = 0; + int acc2 = 0; + ww.for_joined_components([&acc1, &acc2]( + ecs::entity e, const position_c& p, const velocity_c& v) + { + acc1 += e.id(); + acc2 += p.x + v.x; + }); + REQUIRE(acc1 == e1.id() + e2.id()); + REQUIRE(acc2 == 16); + } + } + } }