WIP: bitmask

This commit is contained in:
Michele Caini
2017-06-06 22:56:56 +02:00
parent 9328d1aaf4
commit 1017b60810
4 changed files with 148 additions and 51 deletions

View File

@@ -94,14 +94,34 @@ drop-in replacement for it with a minimal effort.
### Performance
As it stands right now, `EnTT` is just fast enough for my requirements if compared to my first choice (that was already
amazingly fast):
amazingly fast).
These are the results of the twos when compiled with GCC 6.3:
| Benchmark | EntityX (master) | EntityX (experimental/compile_time) | EnTT |
| Benchmark | | EntityX (experimental/compile_time) | EnTT |
|-----------|-------------|-------------|-------------|
| Creating 10M entities | 0.281481s | 0.213988s | **0.00542235s** |
| Destroying 10M entities | 0.166156s | 0.0673857s | **0.0582367s** |
| Iterating over 10M entities, unpacking one component | 0.047039s | 0.0297941s | **9.3e-08s** |
| Iterating over 10M entities, unpacking two components | 0.0701693s | 0.0412988s | **0.0206747s** |
| Creating 10M entities | 0.187042s | **0.0928331s** |
| Destroying 10M entities | 0.0735151s | **0.060166s** |
| Iterating over 10M entities, unpacking one component | 0.00784801s | **1.02e-07s** |
| Iterating over 10M entities, unpacking two components | 0.00865273s | **0.00326714s** |
| Iterating over 10M entities, unpacking five components | 0.0122006s | **0.00323354s** |
| Iterating over 10M entities, unpacking ten components | 0.0100089s | **0.00323615s** |
| Iterating over 50M entities, unpacking one component | 0.0394404s | **1.14e-07s** |
| Iterating over 50M entities, unpacking two components | 0.0400407s | **0.0179783s** |
These are the results of the twos when compiled with Clang 3.8.1:
| Benchmark | | EntityX (experimental/compile_time) | EnTT |
|-----------|-------------|-------------|-------------|
| Creating 10M entities | 0.268049s | **0.0899998s** |
| Destroying 10M entities | **0.0713912s** | 0.078663s |
| Iterating over 10M entities, unpacking one component | 0.00863192s | **3.05e-07s** |
| Iterating over 10M entities, unpacking two components | 0.00780158s | **2.5434e-05s** |
| Iterating over 10M entities, unpacking five components | 0.00829669s | **2.5497e-05s** |
| Iterating over 10M entities, unpacking ten components | 0.00789789s | **2.5563e-05s** |
| Iterating over 50M entities, unpacking one component | 0.0423244s | **1.94e-07s** |
| Iterating over 50M entities, unpacking two components | 0.0435464s | **0.00012661s** |
I don't know what Clang does to squeeze out of `EnTT` the performance above, but I'd say that it does it incredibly well.
See [benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp) for further details.<br/>
Of course, I'll try to get out of it more features and better performance anyway in the future, mainly for fun.
@@ -172,6 +192,7 @@ Once you have created a registry, the followings are the exposed member function
* `size`: returns the number of entities still alive.
* `capacity`: returns the maximum number of entities created till now.
* `valid`: returns true if the entity is still in use, false otherwise.
* `empty<Component>`: returns `true` if at least an instance of `Component` exists, `false` otherwise.
* `empty`: returns `true` if all the entities have been destroyed, `false` otherwise.
* `create<Components...>`: creates a new entity and assigns it the given components, then returns the entity.

43
src/ident.hpp Normal file
View File

@@ -0,0 +1,43 @@
#ifndef ENTT_IDENT_HPP
#define ENTT_IDENT_HPP
#include<type_traits>
#include<cstddef>
#include<utility>
namespace entt {
namespace details {
template<typename Type>
struct Wrapper {
using type = Type;
constexpr Wrapper(std::size_t index): index{index} {}
const std::size_t index;
};
template<typename... Types>
struct Identifier final: Wrapper<Types>... {
template<std::size_t... Indexes>
constexpr Identifier(std::index_sequence<Indexes...>): Wrapper<Types>{Indexes}... {}
template<typename Type>
constexpr std::size_t get() const { return Wrapper<std::decay_t<Type>>::index; }
};
}
template<typename... Types>
constexpr auto ident = details::Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
}
#endif // ENTT_IDENT_HPP

View File

@@ -4,10 +4,13 @@
#include <tuple>
#include <vector>
#include <bitset>
#include <utility>
#include <cstddef>
#include <iterator>
#include <cassert>
#include "component_pool.hpp"
#include "ident.hpp"
namespace entt {
@@ -21,14 +24,16 @@ template<template<typename...> class Pool, typename Entity, typename... Componen
class View<Pool<Entity, Components...>, std::tuple<Type, Types...>, std::tuple<Filters...>> final {
using pool_type = Pool<Entity, Components...>;
using entity_type = typename pool_type::entity_type;
using mask_type = std::bitset<sizeof...(Components)+1>;
class ViewIterator {
bool valid() const noexcept {
using accumulator_type = bool[];
bool check = pool.template has<Type>(entities[pos-1]);
accumulator_type types = { true, (check = check && pool.template has<Types>(entities[pos-1]))... };
accumulator_type filters = { true, (check = check && not pool.template has<Filters>(entities[pos-1]))... };
return void(types), void(filters), check;
auto &bitmask = mask[entities[pos-1]];
bool all = bitmask.test(ident<Components...>.template get<Type>());
accumulator_type types = { true, (all = all && bitmask.test(ident<Components...>.template get<Types>()))... };
accumulator_type filters = { true, (all = all && !bitmask.test(ident<Components...>.template get<Filters>()))... };
return void(types), void(filters), all;
}
public:
@@ -38,8 +43,8 @@ class View<Pool<Entity, Components...>, std::tuple<Type, Types...>, std::tuple<F
using pointer = entity_type *;
using iterator_category = std::input_iterator_tag;
ViewIterator(pool_type &pool, const entity_type *entities, typename pool_type::size_type pos) noexcept
: pool{pool}, entities{entities}, pos{pos}
ViewIterator(pool_type &pool, const entity_type *entities, typename pool_type::size_type pos, mask_type *mask) noexcept
: pool{pool}, entities{entities}, pos{pos}, mask{mask}
{
if(this->pos) { while(!valid() && --this->pos); }
}
@@ -55,7 +60,7 @@ class View<Pool<Entity, Components...>, std::tuple<Type, Types...>, std::tuple<F
}
bool operator==(const ViewIterator &other) const noexcept {
return other.entities == entities && other.pos == pos;
return other.entities == entities && other.pos == pos && other.mask == mask;
}
bool operator!=(const ViewIterator &other) const noexcept {
@@ -70,6 +75,7 @@ class View<Pool<Entity, Components...>, std::tuple<Type, Types...>, std::tuple<F
pool_type &pool;
const entity_type *entities;
typename pool_type::size_type pos;
mask_type *mask;
};
template<typename Comp>
@@ -89,10 +95,11 @@ public:
template<typename... Comp>
using view_type = View<pool_type, std::tuple<Type, Types...>, std::tuple<Comp...>>;
explicit View(pool_type &pool) noexcept
explicit View(pool_type &pool, mask_type *mask) noexcept
: entities{pool.template entities<Type>()},
size{pool.template size<Type>()},
pool{pool}
pool{pool},
mask{mask}
{
using accumulator_type = int[];
accumulator_type accumulator = { 0, (prefer<Types>(), 0)... };
@@ -101,15 +108,15 @@ public:
template<typename... Comp>
view_type<Comp...> exclude() noexcept {
return view_type<Comp...>{pool};
return view_type<Comp...>{pool, mask};
}
iterator_type begin() const noexcept {
return ViewIterator{pool, entities, size};
return ViewIterator{pool, entities, size, mask};
}
iterator_type end() const noexcept {
return ViewIterator{pool, entities, 0};
return ViewIterator{pool, entities, 0, mask};
}
void reset() noexcept {
@@ -124,6 +131,7 @@ private:
const entity_type *entities;
size_type size;
pool_type &pool;
mask_type *mask;
};
@@ -131,6 +139,7 @@ template<template<typename...> class Pool, typename Entity, typename... Componen
class View<Pool<Entity, Components...>, std::tuple<Type>, std::tuple<>> final {
using pool_type = Pool<Entity, Components...>;
using entity_type = typename pool_type::entity_type;
using mask_type = std::bitset<sizeof...(Components)+1>;
struct ViewIterator {
using value_type = entity_type;
@@ -177,11 +186,13 @@ public:
template<typename... Comp>
using view_type = View<pool_type, std::tuple<Type>, std::tuple<Comp...>>;
explicit View(pool_type &pool) noexcept: pool{pool} {}
explicit View(pool_type &pool, mask_type *mask) noexcept
: pool{pool}, mask{mask}
{}
template<typename... Comp>
view_type<Comp...> exclude() noexcept {
return view_type<Comp...>{pool};
return view_type<Comp...>{pool, mask};
}
iterator_type begin() const noexcept {
@@ -198,6 +209,7 @@ public:
private:
pool_type &pool;
mask_type *mask;
};
@@ -207,33 +219,29 @@ class Registry;
template<template<typename...> class Pool, typename Entity, typename... Components>
class Registry<Pool<Entity, Components...>> {
using pool_type = Pool<Entity, Components...>;
public:
static_assert(sizeof...(Components) > 1, "!");
using pool_type = Pool<Entity, Components...>;
using mask_type = std::bitset<sizeof...(Components)+1>;
static constexpr auto validity_bit = sizeof...(Components);
public:
using entity_type = typename pool_type::entity_type;
using size_type = typename std::vector<entity_type>::size_type;
using size_type = typename std::vector<mask_type>::size_type;
private:
template<typename Comp>
void destroy(entity_type entity) {
if(pool.template has<Comp>(entity)) {
pool.template destroy<Comp>(entity);
}
}
template<typename Comp>
void clone(entity_type to, entity_type from) {
if(pool.template has<Comp>(from)) {
pool.template construct<Comp>(to, pool.template get<Comp>(from));
if(entities[from].test(ident<Components...>.template get<Comp>())) {
assign<Comp>(to, pool.template get<Comp>(from));
}
}
template<typename Comp>
void sync(entity_type to, entity_type from) {
bool src = pool.template has<Comp>(from);
bool dst = pool.template has<Comp>(to);
bool src = entities[from].test(ident<Components...>.template get<Comp>());
bool dst = entities[to].test(ident<Components...>.template get<Comp>());
if(src && dst) {
copy<Comp>(to, from);
@@ -250,7 +258,7 @@ public:
template<typename... Args>
Registry(Args&&... args)
: count{0}, pool{std::forward<Args>(args)...}
: pool{std::forward<Args>(args)...}
{}
Registry(const Registry &) = delete;
@@ -260,11 +268,11 @@ public:
Registry & operator=(Registry &&) = delete;
size_type size() const noexcept {
return count - available.size();
return entities.size() - available.size();
}
size_type capacity() const noexcept {
return count;
return entities.size();
}
template<typename Comp>
@@ -273,7 +281,11 @@ public:
}
bool empty() const noexcept {
return available.size() == count;
return entities.empty();
}
bool valid(entity_type entity) const noexcept {
return (entity < entities.size() && entities[entity].test(validity_bit));
}
template<typename... Comp>
@@ -289,37 +301,48 @@ public:
entity_type entity;
if(available.empty()) {
entity = count++;
entity = entity_type(entities.size());
entities.emplace_back();
} else {
entity = available.back();
available.pop_back();
}
entities[entity].set(validity_bit);
return entity;
}
void destroy(entity_type entity) {
assert(valid(entity));
using accumulator_type = int[];
accumulator_type accumulator = { 0, (destroy<Components>(entity), 0)... };
(void)accumulator;
accumulator_type accumulator = { 0, (reset<Components>(entity), 0)... };
available.push_back(entity);
entities[entity].reset();
(void)accumulator;
}
template<typename Comp, typename... Args>
Comp & assign(entity_type entity, Args... args) {
assert(valid(entity));
entities[entity].set(ident<Components...>.template get<Comp>());
return pool.template construct<Comp>(entity, args...);
}
template<typename Comp>
void remove(entity_type entity) {
assert(valid(entity));
entities[entity].reset(ident<Components...>.template get<Comp>());
pool.template destroy<Comp>(entity);
}
template<typename... Comp>
bool has(entity_type entity) const noexcept {
assert(valid(entity));
using accumulator_type = bool[];
bool all = true;
accumulator_type accumulator = { true, (all = all && pool.template has<Comp>(entity))... };
auto &mask = entities[entity];
accumulator_type accumulator = { true, (all = all && mask.test(ident<Components...>.template get<Comp>()))... };
(void)accumulator;
return all;
}
@@ -341,14 +364,17 @@ public:
template<typename Comp, typename... Args>
Comp & accomodate(entity_type entity, Args... args) {
return (pool.template has<Comp>(entity)
assert(valid(entity));
return (entities[entity].test(ident<Components...>.template get<Comp>())
? this->template replace<Comp>(entity, std::forward<Args>(args)...)
: this->template assign<Comp>(entity, std::forward<Args>(args)...));
}
entity_type clone(entity_type from) {
auto to = create();
assert(valid(from));
using accumulator_type = int[];
auto to = create();
accumulator_type accumulator = { 0, (clone<Components>(to, from), 0)... };
(void)accumulator;
return to;
@@ -367,30 +393,36 @@ public:
template<typename Comp>
void reset(entity_type entity) {
if(pool.template has<Comp>(entity)) {
pool.template destroy<Comp>(entity);
assert(valid(entity));
if(entities[entity].test(ident<Components...>.template get<Comp>())) {
remove<Comp>(entity);
}
}
template<typename Comp>
void reset() {
pool.template reset<Comp>();
for(entity_type entity = 0, last = entity_type(entities.size()); entity < last; ++entity) {
if(entities[entity].test(ident<Components...>.template get<Comp>())) {
remove<Comp>(entity);
}
}
}
void reset() {
entities.clear();
available.clear();
count = 0;
pool.reset();
}
template<typename... Comp>
view_type<Comp...> view() noexcept {
return view_type<Comp...>{pool};
return view_type<Comp...>{pool, entities.data()};
}
private:
std::vector<mask_type> entities;
std::vector<entity_type> available;
entity_type count;
pool_type pool;
};

View File

@@ -105,6 +105,7 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.empty<char>());
e1 = registry.create<int>();
e2 = registry.create();
ASSERT_NO_THROW(registry.reset<int>(e1));
ASSERT_NO_THROW(registry.reset<int>(e2));