diff --git a/ecs.hpp b/ecs.hpp index 143b571..aace988 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -157,6 +157,26 @@ namespace ecs_hpp return impl::tuple_contains_impl<0>(t, v); } + // + // tiny_tuple_apply + // + + namespace impl + { + template < typename F, typename Tuple, std::size_t... I > + void tiny_tuple_apply_impl(F&& f, Tuple&& args, std::index_sequence) { + std::forward(f)(std::get(std::forward(args))...); + } + } + + template < typename F, typename Tuple > + void tiny_tuple_apply(F&& f, Tuple&& args) { + impl::tiny_tuple_apply_impl( + std::forward(f), + std::forward(args), + std::make_index_sequence>::value>()); + } + // // next_capacity_size // @@ -317,6 +337,18 @@ namespace ecs_hpp sparse_set(const Indexer& indexer = Indexer()) : indexer_(indexer) {} + sparse_set(const sparse_set& other) = default; + sparse_set& operator=(const sparse_set& other) = default; + + sparse_set(sparse_set&& other) noexcept = default; + sparse_set& operator=(sparse_set&& other) noexcept = default; + + void swap(sparse_set& other) noexcept { + using std::swap; + swap(dense_, other.dense_); + swap(sparse_, other.sparse_); + } + template < typename UT > bool insert(UT&& v) { if ( has(v) ) { @@ -394,6 +426,11 @@ namespace ecs_hpp std::vector dense_; std::vector sparse_; }; + + template < typename T, typename Indexer > + void swap(sparse_set& l, sparse_set& r) noexcept { + l.swap(r); + } } } @@ -447,6 +484,18 @@ namespace ecs_hpp sparse_map(const Indexer& indexer = Indexer()) : keys_(indexer) {} + sparse_map(const sparse_map& other) = default; + sparse_map& operator=(const sparse_map& other) = default; + + sparse_map(sparse_map&& other) noexcept = default; + sparse_map& operator=(sparse_map&& other) noexcept = default; + + void swap(sparse_map& other) noexcept { + using std::swap; + swap(keys_, other.keys_); + swap(values_, other.values_); + } + template < typename UK, typename UT > bool insert(UK&& k, UT&& v) { if ( keys_.has(k) ) { @@ -541,6 +590,11 @@ namespace ecs_hpp sparse_set keys_; std::vector values_; }; + + template < typename K, typename T, typename Indexer > + void swap(sparse_map& l, sparse_map& r) noexcept { + l.swap(r); + } } } @@ -952,6 +1006,72 @@ namespace std }; } +// ----------------------------------------------------------------------------- +// +// prototype +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + namespace detail + { + class component_applier_base; + using component_applier_uptr = std::unique_ptr; + + class component_applier_base { + public: + virtual ~component_applier_base() = default; + virtual component_applier_uptr clone() const = 0; + virtual void apply(entity& ent, bool override) const = 0; + }; + + template < typename T, typename... Args > + class component_applier final : public component_applier_base { + public: + component_applier(std::tuple&& args); + component_applier(const std::tuple& args); + component_applier_uptr clone() const override; + void apply(entity& ent, bool override) const override; + private: + std::tuple args_; + }; + } + + class prototype final { + public: + prototype() = default; + ~prototype() noexcept = default; + + prototype(const prototype& other); + prototype& operator=(const prototype& other); + + prototype(prototype&& other) noexcept; + prototype& operator=(prototype&& other) noexcept; + + void clear() noexcept; + bool empty() const noexcept; + void swap(prototype& other) noexcept; + + template < typename T, typename... Args > + prototype& assign_component(Args&&... args) &; + + template < typename T, typename... Args > + prototype&& assign_component(Args&&... args) &&; + + prototype& merge(const prototype& other, bool override) &; + prototype&& merge(const prototype& other, bool override) &&; + + entity create_entity(registry& owner) const; + private: + detail::sparse_map< + family_id, + detail::component_applier_uptr> appliers_; + }; + + void swap(prototype& l, prototype& r) noexcept; +} + // ----------------------------------------------------------------------------- // // system @@ -1574,6 +1694,129 @@ namespace ecs_hpp } } +// ----------------------------------------------------------------------------- +// +// prototype impl +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + namespace detail + { + template < typename T, typename... Args > + component_applier::component_applier(std::tuple&& args) + : args_(std::move(args)) {} + + template < typename T, typename... Args > + component_applier::component_applier(const std::tuple& args) + : args_(args) {} + + template < typename T, typename... Args > + component_applier_uptr component_applier::clone() const { + return std::make_unique(args_); + } + + template < typename T, typename... Args > + void component_applier::apply(entity& ent, bool override) const { + detail::tiny_tuple_apply([&ent, override](const Args&... args){ + if ( override || !ent.exists_component() ) { + ent.assign_component(args...); + } + }, args_); + } + } + + inline prototype::prototype(const prototype& other) { + for ( const family_id id : other.appliers_ ) { + appliers_.insert(id, other.appliers_.get(id)->clone()); + } + } + + inline prototype& prototype::operator=(const prototype& other) { + if ( this != &other ) { + prototype p(other); + swap(p); + } + return *this; + } + + inline prototype::prototype(prototype&& other) noexcept + : appliers_(std::move(other.appliers_)) {} + + inline prototype& prototype::operator=(prototype&& other) noexcept { + if ( this != &other ) { + swap(other); + other.clear(); + } + return *this; + } + + inline void prototype::clear() noexcept { + appliers_.clear(); + } + + inline bool prototype::empty() const noexcept { + return appliers_.empty(); + } + + inline void prototype::swap(prototype& other) noexcept { + using std::swap; + swap(appliers_, other.appliers_); + } + + template < typename T, typename... Args > + prototype& prototype::assign_component(Args&&... args) & { + using applier_t = detail::component_applier< + T, + std::decay_t...>; + auto applier = std::make_unique( + std::make_tuple(std::forward(args)...)); + const auto family = detail::type_family::id(); + appliers_.emplace(family, std::move(applier)); + return *this; + } + + template < typename T, typename... Args > + prototype&& prototype::assign_component(Args&&... args) && { + assign_component(std::forward(args)...); + return std::move(*this); + } + + prototype& prototype::merge(const prototype& other, bool override) & { + for ( const auto family_id : other.appliers_ ) { + if ( override || !appliers_.has(family_id) ) { + appliers_.insert_or_assign( + family_id, + other.appliers_.get(family_id)->clone()); + } + } + return *this; + } + + prototype&& prototype::merge(const prototype& other, bool override) && { + merge(other, override); + return std::move(*this); + } + + inline entity prototype::create_entity(registry& owner) const { + auto ent = owner.create_entity(); + try { + for ( const auto family_id : appliers_ ) { + appliers_.get(family_id)->apply(ent, true); + } + } catch (...) { + owner.destroy_entity(ent); + throw; + } + return ent; + } + + inline void swap(prototype& l, prototype& r) noexcept { + l.swap(r); + } +} + // ----------------------------------------------------------------------------- // // registry impl diff --git a/ecs_tests.cpp b/ecs_tests.cpp index 1809071..6d8eda5 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -514,6 +514,92 @@ TEST_CASE("registry") { REQUIRE_FALSE(c1.remove()); } } + SECTION("prototypes") { + { + ecs::prototype p; + p.assign_component(1, 2); + + ecs::registry w; + const auto e1 = p.create_entity(w); + const auto e2 = p.create_entity(w); + + REQUIRE(w.entity_count() == 2u); + REQUIRE(w.component_count() == 2u); + REQUIRE(w.component_count() == 0u); + + REQUIRE(e1.component_count() == 1u); + REQUIRE(e1.get_component() == position_c(1,2)); + + REQUIRE(e2.component_count() == 1u); + REQUIRE(e2.get_component() == position_c(1,2)); + } + { + const auto p = ecs::prototype() + .assign_component(1,2) + .assign_component(3,4); + + ecs::registry w; + const auto e1 = p.create_entity(w); + const auto e2 = p.create_entity(w); + + REQUIRE(w.entity_count() == 2u); + REQUIRE(w.component_count() == 2u); + REQUIRE(w.component_count() == 2u); + + REQUIRE(e1.component_count() == 2u); + REQUIRE(e1.get_component() == position_c(1,2)); + REQUIRE(e1.get_component() == velocity_c(3,4)); + + REQUIRE(e2.component_count() == 2u); + REQUIRE(e2.get_component() == position_c(1,2)); + REQUIRE(e2.get_component() == velocity_c(3,4)); + } + { + const auto p1 = ecs::prototype() + .assign_component(1,2) + .assign_component(3,4); + + ecs::prototype p2 = p1; + ecs::prototype p3; + p3 = p2; + + ecs::registry w; + const auto e3 = p3.create_entity(w); + REQUIRE(e3.get_component() == position_c(1,2)); + REQUIRE(e3.get_component() == velocity_c(3,4)); + } + { + const auto p1 = ecs::prototype() + .assign_component(1,2) + .merge(ecs::prototype().assign_component(3,4), false); + + const auto p2 = ecs::prototype() + .assign_component(1,2) + .merge(ecs::prototype().assign_component(3,4), true); + + const auto p3 = ecs::prototype() + .assign_component(1,2) + .merge(ecs::prototype().assign_component(3,4), false); + + const auto p4 = ecs::prototype() + .assign_component(1,2) + .merge(ecs::prototype().assign_component(3,4), true); + + ecs::registry w; + + const auto e1 = p1.create_entity(w); + REQUIRE(e1.get_component() == position_c(1,2)); + const auto e2 = p2.create_entity(w); + REQUIRE(e2.get_component() == position_c(3,4)); + + const auto e3 = p3.create_entity(w); + REQUIRE(e3.get_component() == position_c(1,2)); + REQUIRE(e3.get_component() == velocity_c(3,4)); + const auto e4 = p4.create_entity(w); + REQUIRE(e4.get_component() == position_c(1,2)); + REQUIRE(e4.get_component() == velocity_c(3,4)); + } + } SECTION("component_assigning") { { ecs::registry w;