From 1017b608108f7870993653d31803a8f4769b4906 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Tue, 6 Jun 2017 22:56:56 +0200 Subject: [PATCH] WIP: bitmask --- README.md | 33 ++++++++++--- src/ident.hpp | 43 ++++++++++++++++ src/registry.hpp | 122 +++++++++++++++++++++++++++++----------------- test/registry.cpp | 1 + 4 files changed, 148 insertions(+), 51 deletions(-) create mode 100644 src/ident.hpp diff --git a/README.md b/README.md index 66e394fc1..4875a4d02 100644 --- a/README.md +++ b/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.
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`: 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`: creates a new entity and assigns it the given components, then returns the entity. diff --git a/src/ident.hpp b/src/ident.hpp new file mode 100644 index 000000000..7ce660f9c --- /dev/null +++ b/src/ident.hpp @@ -0,0 +1,43 @@ +#ifndef ENTT_IDENT_HPP +#define ENTT_IDENT_HPP + + +#include +#include +#include + + +namespace entt { + + +namespace details { + + +template +struct Wrapper { + using type = Type; + constexpr Wrapper(std::size_t index): index{index} {} + const std::size_t index; +}; + +template +struct Identifier final: Wrapper... { + template + constexpr Identifier(std::index_sequence): Wrapper{Indexes}... {} + + template + constexpr std::size_t get() const { return Wrapper>::index; } +}; + + +} + + +template +constexpr auto ident = details::Identifier...>{std::make_index_sequence{}}; + + +} + + +#endif // ENTT_IDENT_HPP diff --git a/src/registry.hpp b/src/registry.hpp index b4e712f90..cf50fe8d4 100644 --- a/src/registry.hpp +++ b/src/registry.hpp @@ -4,10 +4,13 @@ #include #include +#include #include #include #include +#include #include "component_pool.hpp" +#include "ident.hpp" namespace entt { @@ -21,14 +24,16 @@ template class Pool, typename Entity, typename... Componen class View, std::tuple, std::tuple> final { using pool_type = Pool; using entity_type = typename pool_type::entity_type; + using mask_type = std::bitset; class ViewIterator { bool valid() const noexcept { using accumulator_type = bool[]; - bool check = pool.template has(entities[pos-1]); - accumulator_type types = { true, (check = check && pool.template has(entities[pos-1]))... }; - accumulator_type filters = { true, (check = check && not pool.template has(entities[pos-1]))... }; - return void(types), void(filters), check; + auto &bitmask = mask[entities[pos-1]]; + bool all = bitmask.test(ident.template get()); + accumulator_type types = { true, (all = all && bitmask.test(ident.template get()))... }; + accumulator_type filters = { true, (all = all && !bitmask.test(ident.template get()))... }; + return void(types), void(filters), all; } public: @@ -38,8 +43,8 @@ class View, std::tuple, std::tuplepos) { while(!valid() && --this->pos); } } @@ -55,7 +60,7 @@ class View, std::tuple, std::tuple, std::tuple, std::tuple @@ -89,10 +95,11 @@ public: template using view_type = View, std::tuple>; - explicit View(pool_type &pool) noexcept + explicit View(pool_type &pool, mask_type *mask) noexcept : entities{pool.template entities()}, size{pool.template size()}, - pool{pool} + pool{pool}, + mask{mask} { using accumulator_type = int[]; accumulator_type accumulator = { 0, (prefer(), 0)... }; @@ -101,15 +108,15 @@ public: template view_type exclude() noexcept { - return view_type{pool}; + return view_type{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 class Pool, typename Entity, typename... Componen class View, std::tuple, std::tuple<>> final { using pool_type = Pool; using entity_type = typename pool_type::entity_type; + using mask_type = std::bitset; struct ViewIterator { using value_type = entity_type; @@ -177,11 +186,13 @@ public: template using view_type = View, std::tuple>; - explicit View(pool_type &pool) noexcept: pool{pool} {} + explicit View(pool_type &pool, mask_type *mask) noexcept + : pool{pool}, mask{mask} + {} template view_type exclude() noexcept { - return view_type{pool}; + return view_type{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 class Pool, typename Entity, typename... Components> class Registry> { - using pool_type = Pool; - -public: static_assert(sizeof...(Components) > 1, "!"); + using pool_type = Pool; + using mask_type = std::bitset; + + static constexpr auto validity_bit = sizeof...(Components); + +public: using entity_type = typename pool_type::entity_type; - using size_type = typename std::vector::size_type; + using size_type = typename std::vector::size_type; private: - template - void destroy(entity_type entity) { - if(pool.template has(entity)) { - pool.template destroy(entity); - } - } - template void clone(entity_type to, entity_type from) { - if(pool.template has(from)) { - pool.template construct(to, pool.template get(from)); + if(entities[from].test(ident.template get())) { + assign(to, pool.template get(from)); } } template void sync(entity_type to, entity_type from) { - bool src = pool.template has(from); - bool dst = pool.template has(to); + bool src = entities[from].test(ident.template get()); + bool dst = entities[to].test(ident.template get()); if(src && dst) { copy(to, from); @@ -250,7 +258,7 @@ public: template Registry(Args&&... args) - : count{0}, pool{std::forward(args)...} + : pool{std::forward(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 @@ -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 @@ -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(entity), 0)... }; - (void)accumulator; + accumulator_type accumulator = { 0, (reset(entity), 0)... }; available.push_back(entity); + entities[entity].reset(); + (void)accumulator; } template Comp & assign(entity_type entity, Args... args) { + assert(valid(entity)); + entities[entity].set(ident.template get()); return pool.template construct(entity, args...); } template void remove(entity_type entity) { + assert(valid(entity)); + entities[entity].reset(ident.template get()); pool.template destroy(entity); } template 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(entity))... }; + auto &mask = entities[entity]; + accumulator_type accumulator = { true, (all = all && mask.test(ident.template get()))... }; (void)accumulator; return all; } @@ -341,14 +364,17 @@ public: template Comp & accomodate(entity_type entity, Args... args) { - return (pool.template has(entity) + assert(valid(entity)); + + return (entities[entity].test(ident.template get()) ? this->template replace(entity, std::forward(args)...) : this->template assign(entity, std::forward(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(to, from), 0)... }; (void)accumulator; return to; @@ -367,30 +393,36 @@ public: template void reset(entity_type entity) { - if(pool.template has(entity)) { - pool.template destroy(entity); + assert(valid(entity)); + + if(entities[entity].test(ident.template get())) { + remove(entity); } } template void reset() { - pool.template reset(); + for(entity_type entity = 0, last = entity_type(entities.size()); entity < last; ++entity) { + if(entities[entity].test(ident.template get())) { + remove(entity); + } + } } void reset() { + entities.clear(); available.clear(); - count = 0; pool.reset(); } template view_type view() noexcept { - return view_type{pool}; + return view_type{pool, entities.data()}; } private: + std::vector entities; std::vector available; - entity_type count; pool_type pool; }; diff --git a/test/registry.cpp b/test/registry.cpp index 2d488b79d..aa69708fe 100644 --- a/test/registry.cpp +++ b/test/registry.cpp @@ -105,6 +105,7 @@ TEST(DefaultRegistry, Functionalities) { ASSERT_TRUE(registry.empty()); e1 = registry.create(); + e2 = registry.create(); ASSERT_NO_THROW(registry.reset(e1)); ASSERT_NO_THROW(registry.reset(e2));