From 8b97a2bf714129fcfb04941cad385405cd2bcabd Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 28 Dec 2018 08:32:27 +0700 Subject: [PATCH] entity versions --- ecs.hpp | 85 +++++++++++++++++++++++++++++++++++---------------- ecs_tests.cpp | 69 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 32 deletions(-) diff --git a/ecs.hpp b/ecs.hpp index fd3676e..c100d11 100644 --- a/ecs.hpp +++ b/ecs.hpp @@ -37,6 +37,12 @@ namespace ecs_hpp using family_id = std::uint16_t; using entity_id = std::uint32_t; + const std::size_t entity_id_index_bits = 22; + const std::size_t entity_id_index_mask = (1 << entity_id_index_bits) - 1; + + const std::size_t entity_id_version_bits = 10; + const std::size_t entity_id_version_mask = (1 << entity_id_version_bits) - 1; + static_assert( std::is_unsigned::value, "ecs_hpp::family_id must be an unsigned integer"); @@ -44,6 +50,22 @@ namespace ecs_hpp static_assert( std::is_unsigned::value, "ecs_hpp::entity_id must be an unsigned integer"); + + static_assert( + sizeof(entity_id) == (entity_id_index_bits + entity_id_version_bits) / 8u, + "ecs_hpp::entity_id mismatch index and version bits"); + + constexpr inline entity_id entity_id_index(entity_id id) noexcept { + return id & entity_id_index_mask; + } + + constexpr inline entity_id entity_id_version(entity_id id) noexcept { + return (id >> entity_id_index_bits) & entity_id_version_mask; + } + + constexpr inline entity_id entity_id_join(entity_id index, entity_id version) noexcept { + return index | (version << entity_id_index_bits); + } } // ----------------------------------------------------------------------------- @@ -293,7 +315,7 @@ namespace ecs_hpp if ( p.second ) { return p.first; } - throw std::out_of_range("sparse_set"); + throw std::logic_error("ecs_hpp::sparse_set(value not found)"); } std::pair find_index(const T& v) const noexcept { @@ -333,7 +355,7 @@ namespace ecs_hpp 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"); + throw std::length_error("ecs_hpp::sparse_set"); } if ( capacity_ >= ms / 2u ) { return ms; @@ -507,6 +529,24 @@ namespace ecs_hpp } } +// ----------------------------------------------------------------------------- +// +// detail::entity_id_indexer +// +// ----------------------------------------------------------------------------- + +namespace ecs_hpp +{ + namespace detail + { + struct entity_id_indexer { + std::size_t operator()(entity_id id) const noexcept { + return entity_id_index(id); + } + }; + } +} + // ----------------------------------------------------------------------------- // // detail::component_storage @@ -542,7 +582,7 @@ namespace ecs_hpp void for_each_component(F&& f) const noexcept; private: registry& owner_; - detail::sparse_map components_; + detail::sparse_map components_; }; template < typename T > @@ -595,21 +635,6 @@ namespace ecs_hpp } } -// ----------------------------------------------------------------------------- -// -// exceptions -// -// ----------------------------------------------------------------------------- - -namespace ecs_hpp -{ - class basic_exception : public std::logic_error { - public: - basic_exception(const char* msg) - : std::logic_error(msg) {} - }; -} - // ----------------------------------------------------------------------------- // // entity @@ -819,7 +844,7 @@ namespace ecs_hpp private: entity_id last_entity_id_{0u}; std::vector free_entity_ids_; - detail::sparse_set entity_ids_; + detail::sparse_set entity_ids_; using storage_uptr = std::unique_ptr; detail::sparse_map storages_; @@ -941,16 +966,22 @@ namespace ecs_hpp { inline entity registry::create_entity() { if ( !free_entity_ids_.empty() ) { - auto ent = entity(*this, free_entity_ids_.back()); - entity_ids_.insert(ent.id()); + const auto free_ent_id = free_entity_ids_.back(); + const auto new_ent_id = entity_id_join( + entity_id_index(free_ent_id), + entity_id_version(free_ent_id) + 1u); + auto ent = entity(*this, new_ent_id); + entity_ids_.insert(new_ent_id); free_entity_ids_.pop_back(); return ent; } - assert(last_entity_id_ < std::numeric_limits::max()); - auto ent = entity(*this, ++last_entity_id_); - entity_ids_.insert(ent.id()); - return ent; + if ( last_entity_id_ < entity_id_index_mask ) { + auto ent = entity(*this, ++last_entity_id_); + entity_ids_.insert(ent.id()); + return ent; + } + throw std::logic_error("ecs_hpp::registry(entity index overlow)"); } inline bool registry::destroy_entity(const entity& ent) { @@ -1009,7 +1040,7 @@ namespace ecs_hpp if ( component ) { return *component; } - throw basic_exception("component not found"); + throw std::logic_error("ecs_hpp::registry(component not found)"); } template < typename T > @@ -1018,7 +1049,7 @@ namespace ecs_hpp if ( component ) { return *component; } - throw basic_exception("component not found"); + throw std::logic_error("ecs_hpp::registry(component not found)"); } template < typename T > diff --git a/ecs_tests.cpp b/ecs_tests.cpp index c6afe4b..277dd58 100644 --- a/ecs_tests.cpp +++ b/ecs_tests.cpp @@ -303,9 +303,40 @@ TEST_CASE("registry") { ecs::registry w; const auto e1 = w.create_entity(); + w.destroy_entity(e1); const auto e2 = w.create_entity(); - REQUIRE(e1 == e2); + REQUIRE(e1 != e2); + REQUIRE(ecs::entity_id_index(e1.id()) == ecs::entity_id_index(e2.id())); + REQUIRE(ecs::entity_id_version(e1.id()) + 1 == ecs::entity_id_version(e2.id())); + + w.destroy_entity(e2); + const auto e3 = w.create_entity(); + REQUIRE(e3 != e2); + REQUIRE(ecs::entity_id_index(e2.id()) == ecs::entity_id_index(e3.id())); + REQUIRE(ecs::entity_id_version(e2.id()) + 1 == ecs::entity_id_version(e3.id())); + } + { + ecs::registry w; + auto e = w.create_entity(); + const auto e_id = e.id(); + for ( std::size_t i = 0; i < ecs::entity_id_version_mask; ++i ) { + e.destroy(); + e = w.create_entity(); + REQUIRE(ecs::entity_id_version(e_id) != ecs::entity_id_version(e.id())); + } + // entity version wraps around + e.destroy(); + e = w.create_entity(); + REQUIRE(ecs::entity_id_version(e_id) == ecs::entity_id_version(e.id())); + } + { + ecs::registry w; + for ( std::size_t i = 0; i < ecs::entity_id_index_mask; ++i ) { + w.create_entity(); + } + // entity index overflow + REQUIRE_THROWS_AS(w.create_entity(), std::logic_error); } } SECTION("component_assigning") { @@ -395,8 +426,8 @@ TEST_CASE("registry") { 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); + REQUIRE_THROWS_AS(e1.get_component(), std::logic_error); + REQUIRE_THROWS_AS(e2.get_component(), std::logic_error); } { ecs::registry w; @@ -421,8 +452,8 @@ TEST_CASE("registry") { 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); + REQUIRE_THROWS_AS(ww.get_component(e1), std::logic_error); + REQUIRE_THROWS_AS(ww.get_component(e2), std::logic_error); ww.remove_all_components(e1); ww.remove_all_components(e2); @@ -544,6 +575,34 @@ TEST_CASE("registry") { REQUIRE(acc2 == 6); } } + { + ecs::registry w; + + { + auto e1 = w.create_entity(); + auto e2 = w.create_entity(); + + e1.destroy(); + e2.destroy(); + } + + auto e3 = w.create_entity(); + auto e4 = w.create_entity(); + + e3.assign_component(1, 2); + e4.assign_component(3, 4); + + { + 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 == e3.id() + e4.id()); + REQUIRE(acc2 == 4); + } + } } SECTION("for_joined_components") { {