Skip to content

Commit

Permalink
fewer allocations, faster destroy
Browse files Browse the repository at this point in the history
  • Loading branch information
skypjack committed Feb 18, 2018
1 parent 4822f0d commit 7baf608
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 58 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ Dell XPS 13 out of the mid 2014):
| Benchmark | EntityX (compile-time) | EnTT |
|-----------|-------------|-------------|
| Create 10M entities | 0.1289s | **0.0388s** |
| Destroy 10M entities | **0.0531s** | 0.0828s |
| Create 10M entities | 0.1289s | **0.0423s** |
| Destroy 10M entities | 0.0531s | **0.0221s** |
| Standard view, 10M entities, one component | 0.0107s | **7.8e-08s** |
| Standard view, 10M entities, two components | **0.0113s** | 0.0244s |
| Standard view, 10M entities, two components<br/>Half of the entities have all the components | **0.0078s** | 0.0129s |
Expand Down Expand Up @@ -454,8 +454,8 @@ each entity that has it:
registry.reset<Position>();
```

* If neither the entity nor the component are specified, all the entities and
their components are destroyed:
* If neither the entity nor the component are specified, all the entities still
in use and their components are destroyed:

```cpp
registry.reset();
Expand Down Expand Up @@ -855,16 +855,16 @@ mind that it works only with the components of the view itself.

Views are narrow windows on the entire list of entities. They work by filtering
entities according to their components.<br/>
In some cases there may be the need to iterate all the entities regardless of
their components. The registry offers a specific member function to do that:
In some cases there may be the need to iterate all the entities still in use
regardless of their components. The registry offers a specific member function
to do that:

```cpp
registry.each([](auto entity) {
// ...
});
```

Each entity ever created is returned, no matter if it's in use or not.<br/>
Usually, filtering entities that aren't currently in use is more expensive than
iterating them all and filtering out those in which one isn't interested.

Expand Down
81 changes: 38 additions & 43 deletions src/entt/entity/registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ class Registry {
* @return Number of entities still in use.
*/
size_type size() const noexcept {
return entities.size() - available.size();
return entities.size() - available;
}

/**
Expand All @@ -257,7 +257,6 @@ class Registry {
*/
void reserve(size_type cap) {
entities.reserve(cap);
available.reserve(cap);
}

/**
Expand All @@ -284,7 +283,7 @@ class Registry {
* @return True if at least an entity is still in use, false otherwise.
*/
bool empty() const noexcept {
return entities.size() == available.size();
return entities.size() == available;
}

/**
Expand Down Expand Up @@ -408,14 +407,18 @@ class Registry {
entity_type create() noexcept {
entity_type entity;

if(available.empty()) {
if(available) {
const auto entt = next;
const auto version = entities[entt] & (~traits_type::entity_mask);

entity = entt | version;
next = entities[entt] & traits_type::entity_mask;
entities[entt] = entity;
--available;
} else {
entity = entity_type(entities.size());
assert(entity < traits_type::entity_mask);
assert((entity >> traits_type::entity_shift) == entity_type{});
entities.push_back(entity);
} else {
entity = available.back();
available.pop_back();
}

return entity;
Expand All @@ -439,11 +442,12 @@ class Registry {
void destroy(entity_type entity) {
assert(valid(entity));
const auto entt = entity & traits_type::entity_mask;
const auto version = version_type{1} + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
const auto next = entt | (version << traits_type::entity_shift);
const auto version = (entity & (~traits_type::entity_mask)) + (typename traits_type::entity_type{1} << traits_type::entity_shift);
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;

entities[entt] = next;
available.push_back(next);
entities[entt] = node;
next = entt;
++available;

for(auto &&cpool: pools) {
if(cpool && cpool->has(entity)) {
Expand Down Expand Up @@ -879,53 +883,32 @@ class Registry {
if(managed<Component>()) {
auto &cpool = pool<Component>();

for(auto entity: entities) {
each([&cpool](auto entity) {
if(cpool.has(entity)) {
cpool.destroy(entity);
}
}
});
}
}

/**
* @brief Resets a whole registry.
*
* Destroys all the entities. After a call to `reset`, all the entities
* previously created are recycled with a new version number. In case entity
* still in use are recycled with a new version number. In case entity
* identifers are stored around, the `current` member function can be used
* to know if they are still valid.
*/
void reset() {
available.clear();

for(auto &&entity: entities) {
const auto version = version_type{1} + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
entity = (entity & traits_type::entity_mask) | (version << traits_type::entity_shift);
available.push_back(entity);
}

for(auto &&handler: handlers) {
if(handler) {
handler->reset();
}
}

for(auto &&pool: pools) {
if(pool) {
pool->reset();
}
}

for(auto &&tag: tags) {
tag.reset();
}
each([this](auto entity) {
destroy(entity);
});
}

/**
* @brief Iterate entities and applies them the given function object.
*
* The function object is invoked for each entity, no matter if it's in use
* or not.<br/>
* The function object is invoked for each entity still in use.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
Expand All @@ -941,8 +924,19 @@ class Registry {
*/
template<typename Func>
void each(Func func) const {
for(auto pos = entities.size(); pos > size_type{0}; --pos) {
func(entities[pos-1]);
if(available) {
for(auto pos = entities.size(); pos > size_type{0}; --pos) {
const entity_type curr = pos - 1;
const auto entt = entities[curr] & traits_type::entity_mask;

if(curr == entt) {
func(entities[curr]);
}
}
} else {
for(auto pos = entities.size(); pos > size_type{0}; --pos) {
func(entities[pos-1]);
}
}
}

Expand Down Expand Up @@ -1091,8 +1085,9 @@ class Registry {
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
std::vector<std::unique_ptr<Attachee>> tags;
std::vector<entity_type> available;
std::vector<entity_type> entities;
size_type available{};
entity_type next{};
};


Expand Down
56 changes: 48 additions & 8 deletions test/entt/entity/registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,31 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.empty<int>());
}

TEST(DefaultRegistry, CreateDestroyCornerCase) {
entt::DefaultRegistry registry;

auto e0 = registry.create();
auto e1 = registry.create();

registry.destroy(e0);
registry.destroy(e1);

registry.each([](auto) { FAIL(); });

ASSERT_EQ(registry.current(e0), entt::DefaultRegistry::version_type{1});
ASSERT_EQ(registry.current(e1), entt::DefaultRegistry::version_type{1});
}

TEST(DefaultRegistry, Each) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::size_type tot;
entt::DefaultRegistry::size_type match;

registry.create();
registry.create<int>();
registry.create();
registry.create<int>();
registry.create();

tot = 0u;
match = 0u;
Expand All @@ -147,31 +165,37 @@ TEST(DefaultRegistry, Each) {
++tot;
});

ASSERT_EQ(tot, 2u);
ASSERT_EQ(tot, 5u);
ASSERT_EQ(match, 2u);

tot = 0u;
match = 0u;

registry.each([&](auto entity) {
if(registry.has<int>(entity)) { ++match; }
registry.destroy(entity);
if(registry.has<int>(entity)) {
registry.destroy(entity);
++match;
}

++tot;
});

ASSERT_EQ(tot, 4u);
ASSERT_EQ(tot, 10u);
ASSERT_EQ(match, 2u);

tot = 0u;
match = 0u;

registry.each([&](auto entity) {
if(registry.has<int>(entity)) { ++match; }
registry.destroy(entity);
++tot;
});

ASSERT_EQ(tot, 4u);
ASSERT_EQ(tot, 8u);
ASSERT_EQ(match, 0u);

registry.each([&](auto) { FAIL(); });
}


Expand All @@ -187,14 +211,30 @@ TEST(DefaultRegistry, Types) {

TEST(DefaultRegistry, CreateDestroyEntities) {
entt::DefaultRegistry registry;
entt::DefaultRegistry::entity_type pre{}, post{};

for(int i = 0; i < 10; ++i) {
registry.create<double>();
}

registry.reset();

auto pre = registry.create<double>();
registry.destroy(pre);
auto post = registry.create<double>();
for(int i = 0; i < 7; ++i) {
auto entity = registry.create<int>();
if(i == 3) { pre = entity; }
}

registry.reset();

for(int i = 0; i < 5; ++i) {
auto entity = registry.create();
if(i == 3) { post = entity; }
}

ASSERT_FALSE(registry.valid(pre));
ASSERT_TRUE(registry.valid(post));
ASSERT_NE(registry.version(pre), registry.version(post));
ASSERT_EQ(registry.version(pre) + 1, registry.version(post));
ASSERT_EQ(registry.current(pre), registry.current(post));
}

Expand Down

0 comments on commit 7baf608

Please sign in to comment.