entity versions

This commit is contained in:
2018-12-28 08:32:27 +07:00
parent fc1b59cee2
commit 8b97a2bf71
2 changed files with 122 additions and 32 deletions

79
ecs.hpp
View File

@@ -37,6 +37,12 @@ namespace ecs_hpp
using family_id = std::uint16_t; using family_id = std::uint16_t;
using entity_id = std::uint32_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( static_assert(
std::is_unsigned<family_id>::value, std::is_unsigned<family_id>::value,
"ecs_hpp::family_id must be an unsigned integer"); "ecs_hpp::family_id must be an unsigned integer");
@@ -44,6 +50,22 @@ namespace ecs_hpp
static_assert( static_assert(
std::is_unsigned<entity_id>::value, std::is_unsigned<entity_id>::value,
"ecs_hpp::entity_id must be an unsigned integer"); "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 ) { if ( p.second ) {
return p.first; return p.first;
} }
throw std::out_of_range("sparse_set"); throw std::logic_error("ecs_hpp::sparse_set(value not found)");
} }
std::pair<std::size_t,bool> find_index(const T& v) const noexcept { std::pair<std::size_t,bool> 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 { std::size_t new_capacity_for_(std::size_t nsize) const {
const std::size_t ms = max_size(); const std::size_t ms = max_size();
if ( nsize > ms ) { if ( nsize > ms ) {
throw std::length_error("sparse_set<T>"); throw std::length_error("ecs_hpp::sparse_set");
} }
if ( capacity_ >= ms / 2u ) { if ( capacity_ >= ms / 2u ) {
return ms; 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 // detail::component_storage
@@ -542,7 +582,7 @@ namespace ecs_hpp
void for_each_component(F&& f) const noexcept; void for_each_component(F&& f) const noexcept;
private: private:
registry& owner_; registry& owner_;
detail::sparse_map<entity_id, T> components_; detail::sparse_map<entity_id, T, entity_id_indexer> components_;
}; };
template < typename T > 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 // entity
@@ -819,7 +844,7 @@ namespace ecs_hpp
private: private:
entity_id last_entity_id_{0u}; entity_id last_entity_id_{0u};
std::vector<entity_id> free_entity_ids_; std::vector<entity_id> free_entity_ids_;
detail::sparse_set<entity_id> entity_ids_; detail::sparse_set<entity_id, detail::entity_id_indexer> entity_ids_;
using storage_uptr = std::unique_ptr<detail::component_storage_base>; using storage_uptr = std::unique_ptr<detail::component_storage_base>;
detail::sparse_map<family_id, storage_uptr> storages_; detail::sparse_map<family_id, storage_uptr> storages_;
@@ -941,17 +966,23 @@ namespace ecs_hpp
{ {
inline entity registry::create_entity() { inline entity registry::create_entity() {
if ( !free_entity_ids_.empty() ) { if ( !free_entity_ids_.empty() ) {
auto ent = entity(*this, free_entity_ids_.back()); const auto free_ent_id = free_entity_ids_.back();
entity_ids_.insert(ent.id()); 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(); free_entity_ids_.pop_back();
return ent; return ent;
} }
assert(last_entity_id_ < std::numeric_limits<entity_id>::max()); if ( last_entity_id_ < entity_id_index_mask ) {
auto ent = entity(*this, ++last_entity_id_); auto ent = entity(*this, ++last_entity_id_);
entity_ids_.insert(ent.id()); entity_ids_.insert(ent.id());
return ent; return ent;
} }
throw std::logic_error("ecs_hpp::registry(entity index overlow)");
}
inline bool registry::destroy_entity(const entity& ent) { inline bool registry::destroy_entity(const entity& ent) {
remove_all_components_impl_(ent); remove_all_components_impl_(ent);
@@ -1009,7 +1040,7 @@ namespace ecs_hpp
if ( component ) { if ( component ) {
return *component; return *component;
} }
throw basic_exception("component not found"); throw std::logic_error("ecs_hpp::registry(component not found)");
} }
template < typename T > template < typename T >
@@ -1018,7 +1049,7 @@ namespace ecs_hpp
if ( component ) { if ( component ) {
return *component; return *component;
} }
throw basic_exception("component not found"); throw std::logic_error("ecs_hpp::registry(component not found)");
} }
template < typename T > template < typename T >

View File

@@ -303,9 +303,40 @@ TEST_CASE("registry") {
ecs::registry w; ecs::registry w;
const auto e1 = w.create_entity(); const auto e1 = w.create_entity();
w.destroy_entity(e1); w.destroy_entity(e1);
const auto e2 = w.create_entity(); 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") { SECTION("component_assigning") {
@@ -395,8 +426,8 @@ TEST_CASE("registry") {
REQUIRE(e2.get_component<velocity_c>().x == 3); REQUIRE(e2.get_component<velocity_c>().x == 3);
REQUIRE(e2.get_component<velocity_c>().y == 4); REQUIRE(e2.get_component<velocity_c>().y == 4);
REQUIRE_THROWS_AS(e1.get_component<velocity_c>(), ecs::basic_exception); REQUIRE_THROWS_AS(e1.get_component<velocity_c>(), std::logic_error);
REQUIRE_THROWS_AS(e2.get_component<position_c>(), ecs::basic_exception); REQUIRE_THROWS_AS(e2.get_component<position_c>(), std::logic_error);
} }
{ {
ecs::registry w; ecs::registry w;
@@ -421,8 +452,8 @@ TEST_CASE("registry") {
REQUIRE(ww.get_component<velocity_c>(e2).x == 3); REQUIRE(ww.get_component<velocity_c>(e2).x == 3);
REQUIRE(ww.get_component<velocity_c>(e2).y == 4); REQUIRE(ww.get_component<velocity_c>(e2).y == 4);
REQUIRE_THROWS_AS(ww.get_component<velocity_c>(e1), ecs::basic_exception); REQUIRE_THROWS_AS(ww.get_component<velocity_c>(e1), std::logic_error);
REQUIRE_THROWS_AS(ww.get_component<position_c>(e2), ecs::basic_exception); REQUIRE_THROWS_AS(ww.get_component<position_c>(e2), std::logic_error);
ww.remove_all_components(e1); ww.remove_all_components(e1);
ww.remove_all_components(e2); ww.remove_all_components(e2);
@@ -544,6 +575,34 @@ TEST_CASE("registry") {
REQUIRE(acc2 == 6); 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<position_c>(1, 2);
e4.assign_component<position_c>(3, 4);
{
ecs::entity_id acc1 = 0;
int acc2 = 0;
w.for_each_component<position_c>([&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") { SECTION("for_joined_components") {
{ {