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

85
ecs.hpp
View File

@@ -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<family_id>::value,
"ecs_hpp::family_id must be an unsigned integer");
@@ -44,6 +50,22 @@ namespace ecs_hpp
static_assert(
std::is_unsigned<entity_id>::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<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 {
const std::size_t ms = max_size();
if ( nsize > ms ) {
throw std::length_error("sparse_set<T>");
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<entity_id, T> components_;
detail::sparse_map<entity_id, T, entity_id_indexer> 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<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>;
detail::sparse_map<family_id, storage_uptr> 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<entity_id>::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 >

View File

@@ -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<velocity_c>().x == 3);
REQUIRE(e2.get_component<velocity_c>().y == 4);
REQUIRE_THROWS_AS(e1.get_component<velocity_c>(), ecs::basic_exception);
REQUIRE_THROWS_AS(e2.get_component<position_c>(), ecs::basic_exception);
REQUIRE_THROWS_AS(e1.get_component<velocity_c>(), std::logic_error);
REQUIRE_THROWS_AS(e2.get_component<position_c>(), std::logic_error);
}
{
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).y == 4);
REQUIRE_THROWS_AS(ww.get_component<velocity_c>(e1), ecs::basic_exception);
REQUIRE_THROWS_AS(ww.get_component<position_c>(e2), ecs::basic_exception);
REQUIRE_THROWS_AS(ww.get_component<velocity_c>(e1), std::logic_error);
REQUIRE_THROWS_AS(ww.get_component<position_c>(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<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") {
{