diff --git a/ecs.hpp b/ecs.hpp index 6224264..3d51e5b 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -426,6 +426,11 @@ namespace ecs_hpp std::size_t size() const noexcept { return dense_.size(); } + + std::size_t memory_usage() const noexcept { + return dense_.capacity() * sizeof(dense_[0]) + + sparse_.capacity() * sizeof(sparse_[0]); + } private: Indexer indexer_; std::vector dense_; @@ -595,6 +600,11 @@ namespace ecs_hpp std::size_t size() const noexcept { return values_.size(); } + + std::size_t memory_usage() const noexcept { + return keys_.memory_usage() + + values_.capacity() * sizeof(values_[0]); + } private: sparse_set keys_; std::vector values_; @@ -646,98 +656,145 @@ namespace ecs_hpp virtual bool remove(entity_id id) noexcept = 0; virtual bool has(entity_id id) const noexcept = 0; virtual void clone(entity_id from, entity_id to) = 0; + virtual std::size_t memory_usage() const noexcept = 0; }; - template < typename T > + template < typename T, bool E = std::is_empty::value > class component_storage final : public component_storage_base { public: - component_storage(registry& owner); + component_storage(registry& owner) + : owner_(owner) {} template < typename... Args > - T& assign(entity_id id, Args&&... args); - bool exists(entity_id id) const noexcept; - bool remove(entity_id id) noexcept override; + T& assign(entity_id id, Args&&... args) { + components_.insert_or_assign(id, T(std::forward(args)...)); + return components_.get(id); + } - T* find(entity_id id) noexcept; - const T* find(entity_id id) const noexcept; + bool exists(entity_id id) const noexcept { + return components_.has(id); + } - std::size_t count() const noexcept; - bool has(entity_id id) const noexcept override; - void clone(entity_id from, entity_id to) override; + bool remove(entity_id id) noexcept override { + return components_.unordered_erase(id); + } + + T* find(entity_id id) noexcept { + return components_.find(id); + } + + const T* find(entity_id id) const noexcept { + return components_.find(id); + } + + std::size_t count() const noexcept { + return components_.size(); + } + + bool has(entity_id id) const noexcept override { + return components_.has(id); + } + + void clone(entity_id from, entity_id to) override { + const T* c = components_.find(from); + if ( c ) { + components_.insert_or_assign(to, *c); + } + } template < typename F > - void for_each_component(F&& f); + void for_each_component(F&& f) { + for ( const entity_id id : components_ ) { + f(id, components_.get(id)); + } + } + template < typename F > - void for_each_component(F&& f) const; + void for_each_component(F&& f) const { + for ( const entity_id id : components_ ) { + f(id, components_.get(id)); + } + } + + std::size_t memory_usage() const noexcept override { + return components_.memory_usage(); + } private: registry& owner_; detail::sparse_map components_; }; template < typename T > - component_storage::component_storage(registry& owner) - : owner_(owner) {} + class component_storage final : public component_storage_base { + public: + component_storage(registry& owner) + : owner_(owner) {} - template < typename T > - template < typename... Args > - T& component_storage::assign(entity_id id, Args&&... args) { - components_.insert_or_assign(id, T(std::forward(args)...)); - return components_.get(id); - } - - template < typename T > - bool component_storage::exists(entity_id id) const noexcept { - return components_.has(id); - } - - template < typename T > - bool component_storage::remove(entity_id id) noexcept { - return components_.unordered_erase(id); - } - - template < typename T > - T* component_storage::find(entity_id id) noexcept { - return components_.find(id); - } - - template < typename T > - const T* component_storage::find(entity_id id) const noexcept { - return components_.find(id); - } - - template < typename T > - std::size_t component_storage::count() const noexcept { - return components_.size(); - } - - template < typename T > - bool component_storage::has(entity_id id) const noexcept { - return components_.has(id); - } - - template < typename T > - void component_storage::clone(entity_id from, entity_id to) { - const T* c = components_.find(from); - if ( c ) { - components_.insert_or_assign(to, *c); + template < typename... Args > + T& assign(entity_id id, Args&&... args) { + components_.insert(id); + return empty_value_; } - } + + bool exists(entity_id id) const noexcept { + return components_.has(id); + } + + bool remove(entity_id id) noexcept override { + return components_.unordered_erase(id); + } + + T* find(entity_id id) noexcept { + return components_.has(id) + ? &empty_value_ + : nullptr; + } + + const T* find(entity_id id) const noexcept { + return components_.has(id) + ? &empty_value_ + : nullptr; + } + + std::size_t count() const noexcept { + return components_.size(); + } + + bool has(entity_id id) const noexcept override { + return components_.has(id); + } + + void clone(entity_id from, entity_id to) override { + if ( components_.has(from) ) { + components_.insert(to); + } + } + + template < typename F > + void for_each_component(F&& f) { + for ( const entity_id id : components_ ) { + f(id, empty_value_); + } + } + + template < typename F > + void for_each_component(F&& f) const { + for ( const entity_id id : components_ ) { + f(id, empty_value_); + } + } + + std::size_t memory_usage() const noexcept override { + return components_.memory_usage(); + } + private: + registry& owner_; + static T empty_value_; + detail::sparse_set components_; + }; template < typename T > - template < typename F > - void component_storage::for_each_component(F&& f) { - for ( const entity_id id : components_ ) { - f(id, components_.get(id)); - } - } - - template < typename T > - template < typename F > - void component_storage::for_each_component(F&& f) const { - for ( const entity_id id : components_ ) { - f(id, components_.get(id)); - } - } + T component_storage::empty_value_; } } @@ -1037,7 +1094,7 @@ namespace ecs_hpp public: virtual ~component_applier_base() = default; virtual component_applier_uptr clone() const = 0; - virtual void apply(entity& ent, bool override) const = 0; + virtual void apply_to(entity& ent, bool override) const = 0; }; template < typename T, typename... Args > @@ -1046,7 +1103,7 @@ namespace ecs_hpp 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; + void apply_to(entity& ent, bool override) const override; private: std::tuple args_; }; @@ -1067,15 +1124,18 @@ namespace ecs_hpp 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) &&; + template < typename T > + bool has_component() const noexcept; - prototype& merge(const prototype& other, bool override) &; - prototype&& merge(const prototype& other, bool override) &&; + template < typename T, typename... Args > + prototype& component(Args&&... args) &; + template < typename T, typename... Args > + prototype&& component(Args&&... args) &&; - void apply(entity& ent, bool override) const; + prototype& merge_with(const prototype& other, bool override) &; + prototype&& merge_with(const prototype& other, bool override) &&; + + void apply_to(entity& ent, bool override) const; private: detail::sparse_map< family_id, @@ -1234,6 +1294,15 @@ namespace ecs_hpp void process_systems_above(priority_t min); void process_systems_below(priority_t max); void process_systems_in_range(priority_t min, priority_t max); + + struct memory_usage_info { + std::size_t entities{0u}; + std::size_t components{0u}; + }; + memory_usage_info memory_usage() const noexcept; + + template < typename T > + std::size_t component_memory_usage() const noexcept; private: template < typename T > detail::component_storage* find_storage_() noexcept; @@ -1732,7 +1801,7 @@ namespace ecs_hpp } template < typename T, typename... Args > - void component_applier::apply(entity& ent, bool override) const { + void component_applier::apply_to(entity& ent, bool override) const { detail::tiny_tuple_apply([&ent, override](const Args&... args){ if ( override || !ent.exists_component() ) { ent.assign_component(args...); @@ -1779,8 +1848,14 @@ namespace ecs_hpp swap(appliers_, other.appliers_); } + template < typename T > + bool prototype::has_component() const noexcept { + const auto family = detail::type_family::id(); + return appliers_.has(family); + } + template < typename T, typename... Args > - prototype& prototype::assign_component(Args&&... args) & { + prototype& prototype::component(Args&&... args) & { using applier_t = detail::component_applier< T, std::decay_t...>; @@ -1792,12 +1867,12 @@ namespace ecs_hpp } template < typename T, typename... Args > - prototype&& prototype::assign_component(Args&&... args) && { - assign_component(std::forward(args)...); + prototype&& prototype::component(Args&&... args) && { + component(std::forward(args)...); return std::move(*this); } - inline prototype& prototype::merge(const prototype& other, bool override) & { + inline prototype& prototype::merge_with(const prototype& other, bool override) & { for ( const auto family_id : other.appliers_ ) { if ( override || !appliers_.has(family_id) ) { appliers_.insert_or_assign( @@ -1808,14 +1883,14 @@ namespace ecs_hpp return *this; } - inline prototype&& prototype::merge(const prototype& other, bool override) && { - merge(other, override); + inline prototype&& prototype::merge_with(const prototype& other, bool override) && { + merge_with(other, override); return std::move(*this); } - inline void prototype::apply(entity& ent, bool override) const { + inline void prototype::apply_to(entity& ent, bool override) const { for ( const auto family_id : appliers_ ) { - appliers_.get(family_id)->apply(ent, override); + appliers_.get(family_id)->apply_to(ent, override); } } @@ -1969,7 +2044,7 @@ namespace ecs_hpp inline entity registry::create_entity(const prototype& proto) { auto ent = create_entity(); try { - proto.apply(ent, true); + proto.apply_to(ent, true); } catch (...) { destroy_entity(ent); throw; @@ -2217,6 +2292,24 @@ namespace ecs_hpp } } + inline registry::memory_usage_info registry::memory_usage() const noexcept { + memory_usage_info info; + info.entities += free_entity_ids_.capacity() * sizeof(free_entity_ids_[0]); + info.entities += entity_ids_.memory_usage(); + for ( const auto family_id : storages_ ) { + info.components += storages_.get(family_id)->memory_usage(); + } + return info; + } + + template < typename T > + std::size_t registry::component_memory_usage() const noexcept { + const detail::component_storage* storage = find_storage_(); + return storage + ? storage->memory_usage() + : 0u; + } + template < typename T > detail::component_storage* registry::find_storage_() noexcept { const auto family = detail::type_family::id(); diff --git a/ecs_tests.cpp b/ecs_tests.cpp index 9299464..3a130fd 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -28,6 +28,10 @@ namespace velocity_c(int nx, int ny) : x(nx), y(ny) {} }; + struct movable_c { + }; + static_assert(std::is_empty::value, "!!!"); + bool operator==(const position_c& l, const position_c& r) noexcept { return l.x == r.x && l.y == r.y; @@ -518,7 +522,7 @@ TEST_CASE("registry") { SECTION("prototypes") { { ecs::prototype p; - p.assign_component(1, 2); + p.component(1, 2); ecs::registry w; const auto e1 = w.create_entity(p); @@ -536,8 +540,8 @@ TEST_CASE("registry") { } { const auto p = ecs::prototype() - .assign_component(1,2) - .assign_component(3,4); + .component(1,2) + .component(3,4); ecs::registry w; const auto e1 = w.create_entity(p); @@ -557,8 +561,8 @@ TEST_CASE("registry") { } { const auto p1 = ecs::prototype() - .assign_component(1,2) - .assign_component(3,4); + .component(1,2) + .component(3,4); ecs::prototype p2 = p1; ecs::prototype p3; @@ -571,20 +575,20 @@ TEST_CASE("registry") { } { const auto p1 = ecs::prototype() - .assign_component(1,2) - .merge(ecs::prototype().assign_component(3,4), false); + .component(1,2) + .merge_with(ecs::prototype().component(3,4), false); const auto p2 = ecs::prototype() - .assign_component(1,2) - .merge(ecs::prototype().assign_component(3,4), true); + .component(1,2) + .merge_with(ecs::prototype().component(3,4), true); const auto p3 = ecs::prototype() - .assign_component(1,2) - .merge(ecs::prototype().assign_component(3,4), false); + .component(1,2) + .merge_with(ecs::prototype().component(3,4), false); const auto p4 = ecs::prototype() - .assign_component(1,2) - .merge(ecs::prototype().assign_component(3,4), true); + .component(1,2) + .merge_with(ecs::prototype().component(3,4), true); ecs::registry w; @@ -1154,6 +1158,102 @@ TEST_CASE("registry") { REQUIRE(e2.get_component().i == 5); } } + SECTION("memory_usage") { + { + ecs::registry w; + REQUIRE(w.memory_usage().entities == 0u); + + auto e1 = w.create_entity(); + auto e2 = w.create_entity(); + + const std::size_t expected_usage = + 2 * sizeof(ecs::entity_id) + // vector free entity ids + 4 * sizeof(std::size_t) + // sparse entity ids (keys) + 2 * sizeof(ecs::entity_id); // sparse entity ids (values) + REQUIRE(w.memory_usage().entities == expected_usage); + + e1.destroy(); + e2.destroy(); + REQUIRE(w.memory_usage().entities == expected_usage); + + e1 = w.create_entity(); + e2 = w.create_entity(); + REQUIRE(w.memory_usage().entities == expected_usage); + } + { + ecs::registry w; + + auto e1 = w.create_entity(); + e1.assign_component(1, 2); + + auto e2 = w.create_entity(); + e2.assign_component(1, 2); + + const std::size_t expected_usage = + 2 * sizeof(position_c) + // vector values + 4 * sizeof(std::size_t) + // sparse keys (keys) + 2 * sizeof(ecs::entity_id); // sparse keys (values) + REQUIRE(w.memory_usage().components == expected_usage); + + REQUIRE(w.component_memory_usage() == + 2 * sizeof(position_c) + + 4 * sizeof(std::size_t) + + 2 * sizeof(ecs::entity_id)); + + REQUIRE_FALSE(w.component_memory_usage()); + } + { + ecs::registry w; + + auto e1 = w.create_entity(); + e1.assign_component(1, 2); + + auto e2 = w.create_entity(); + e2.assign_component(3, 4); + + const std::size_t expected_usage = + sizeof(position_c) + + 2 * sizeof(std::size_t) + + 1 * sizeof(ecs::entity_id) + + sizeof(velocity_c) + + 3 * sizeof(std::size_t) + + 1 * sizeof(ecs::entity_id); + REQUIRE(w.memory_usage().components == expected_usage); + + REQUIRE(w.component_memory_usage() == + sizeof(position_c) + + 2 * sizeof(std::size_t) + + 1 * sizeof(ecs::entity_id)); + + REQUIRE(w.component_memory_usage() == + sizeof(velocity_c) + + 3 * sizeof(std::size_t) + + 1 * sizeof(ecs::entity_id)); + } + { + ecs::registry w; + auto e1 = w.create_entity(); + auto e2 = w.create_entity(); + e1.assign_component(); + e2.assign_component(); + REQUIRE(w.component_memory_usage() == + 4 * sizeof(std::size_t) + + 2 * sizeof(ecs::entity_id)); + } + } + SECTION("empty_component") { + ecs::registry w; + auto e1 = w.create_entity(); + ecs::entity_filler(e1) + .component() + .component(1, 2) + .component(3, 4); + REQUIRE(w.exists_component(e1)); + REQUIRE(w.find_component(e1)); + w.for_joined_components([ + ](const ecs::const_entity&, movable_c&, position_c&){ + }); + } } TEST_CASE("example") {