Browse Source

WIP: bitmask

Michele Caini 8 năm trước cách đây
mục cha
commit
1017b60810
4 tập tin đã thay đổi với 147 bổ sung50 xóa
  1. 27 6
      README.md
  2. 43 0
      src/ident.hpp
  3. 76 44
      src/registry.hpp
  4. 1 0
      test/registry.cpp

+ 27 - 6
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 - 0
src/ident.hpp

@@ -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

+ 76 - 44
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...>> {
+    static_assert(sizeof...(Components) > 1, "!");
+
     using pool_type = Pool<Entity, Components...>;
+    using mask_type = std::bitset<sizeof...(Components)+1>;
 
-public:
-    static_assert(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;
 };
 

+ 1 - 0
test/registry.cpp

@@ -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));