WIP: bitmask
This commit is contained in:
33
README.md
33
README.md
@@ -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
43
src/ident.hpp
Normal 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
|
||||
122
src/registry.hpp
122
src/registry.hpp
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user