Browse Source

context variables (aka tags revenge)

Michele Caini 7 years ago
parent
commit
1ec7c3afa4
4 changed files with 153 additions and 7 deletions
  1. 2 1
      TODO
  2. 23 0
      docs/entity.md
  3. 107 6
      src/entt/entity/registry.hpp
  4. 21 0
      test/entt/entity/registry.cpp

+ 2 - 1
TODO

@@ -7,7 +7,6 @@
 * work stealing job system (see #100)
 * meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects
 * allow for built-in parallel each if possible
-* tags revenge: if it's possible, reintroduce them but without a link to entities (see #169 for more details)
 * allow to replace std:: with custom implementations
 * allow to sort groups (::respect can already work with begin/end instead of a whole sparse set)
   -it would ease by far the group trick for hierarchies that requires otherwise more boilerplate
@@ -20,3 +19,5 @@
   - each with entity return the shared component multiple times, one per entity that refers to it
   - each components only return actual component, so shared components are returned only once
 * add a sort of "fast each" for when users know they are not to add/remove components, it can use directly raw access and improve even further performance
+* types defined at runtime that refer to the same compile-time type (but to different pools) are possible, the library is almost there
+* view/group iterators that return entities and components? I'd still like to have it :-)

+ 23 - 0
docs/entity.md

@@ -26,6 +26,7 @@
     * [Dependency function](#dependency-function)
     * [Tags](#tags)
   * [Null entity](#null-entity)
+  * [Context variables](#context-variables)
 * [Views and Groups](#views-and-groups)
   * [Views](#views)
   * [Runtime views](#runtime-views)
@@ -825,6 +826,28 @@ const auto entity = registry.create();
 const bool null = (entity == entt::null);
 ```
 
+## Context variables
+
+It is often convenient to assign context variables to a registry, so as to make
+it the only source of truth of an application.<br/>
+This is possible through two member functions named `set` and `ctx`. The former
+is used to create a context variable from a given type, the latter to get the
+newly created instance later on:
+
+```cpp
+// creates a new context variable initialized with the given values
+registry.set<my_type>(42, 'c');
+
+// gets a context variable associated with a registry
+auto &var = registry.ctx<my_type>();
+```
+
+The type of a context variable must be such that it's default constructible and
+can be moved. The `set` member function either creates a new instance of the
+context variable or overwrites an already existing one if any. The `ctx` member
+function requires that the context variable exist to work properly and doesn't
+create it silently under the hood.
+
 # Views and Groups
 
 First of all, it is worth answering an obvious question: why views and

+ 107 - 6
src/entt/entity/registry.hpp

@@ -58,6 +58,7 @@ constexpr exclude_t<Type...> exclude{};
  */
 template<typename Entity>
 class basic_registry {
+    using context_family = family<struct internal_registry_context_family>;
     using component_family = family<struct internal_registry_component_family>;
     using signal_type = sigh<void(basic_registry &, const Entity)>;
     using traits_type = entt_traits<Entity>;
@@ -168,6 +169,25 @@ class basic_registry {
         std::size_t extent;
     };
 
+    struct ctx_wrapper {
+        virtual ~ctx_wrapper() = default;
+        ENTT_ID_TYPE runtime_type;
+    };
+
+    template<typename Type>
+    struct type_wrapper: ctx_wrapper {
+        Type value;
+    };
+
+    template<typename Type, typename Family>
+    static ENTT_ID_TYPE runtime_type() ENTT_NOEXCEPT {
+        if constexpr(is_named_type_v<Type>) {
+            return named_type_traits<Type>::value;
+        } else {
+            return Family::template type<Type>;
+        }
+    }
+
     void release(const Entity entity) {
         // lengthens the implicit list of destroyed entities
         const auto entt = entity & traits_type::entity_mask;
@@ -269,12 +289,8 @@ public:
      * @return Runtime numeric identifier of the given type of component.
      */
     template<typename Component>
-    static component_type type() ENTT_NOEXCEPT {
-        if constexpr(is_named_type_v<Component>) {
-            return named_type_traits<Component>::value;
-        } else {
-            return component_family::type<Component>;
-        }
+    inline static component_type type() ENTT_NOEXCEPT {
+        return runtime_type<Component, component_family>();
     }
 
     /**
@@ -1550,10 +1566,95 @@ public:
         return { (*this = {}), force };
     }
 
+    /**
+     * @brief Binds an object to the context of the registry.
+     *
+     * If the value already exists it is overwritten, otherwise a new instance
+     * of the given type is created and initialized with the arguments provided.
+     *
+     * @tparam Type Type of object to set.
+     * @tparam Args Types of arguments to use to construct the object.
+     * @param args Parameters to use to initialize the value.
+     * @return A reference to the newly created object.
+     */
+    template<typename Type, typename... Args>
+    Type & set(Args &&... args) {
+        const auto ctype = runtime_type<Type, context_family>();
+        ctx_wrapper *wrapper = nullptr;
+
+        if constexpr(is_named_type_v<Type>) {
+            const auto it = std::find_if(vars.begin(), vars.end(), [ctype](const auto &candidate) {
+                return candidate && candidate->runtime_type == ctype;
+            });
+
+            if(it == vars.cend()) {
+                wrapper = vars.emplace_back(std::make_unique<type_wrapper<Type>>()).get();
+            } else {
+                wrapper = it->get();
+            }
+        } else {
+            if(!(ctype < vars.size())) {
+                vars.resize(ctype+1);
+            }
+
+            wrapper = vars[ctype].get();
+
+            if(wrapper && wrapper->runtime_type != ctype) {
+                vars.emplace_back(std::make_unique<type_wrapper<Type>>());
+                std::swap(vars[ctype], vars.back());
+                wrapper = vars[ctype].get();
+            } else if(!wrapper) {
+                wrapper = vars.emplace_back(std::make_unique<type_wrapper<Type>>()).get();
+            }
+        }
+
+        auto &value = static_cast<type_wrapper<Type> *>(wrapper)->value;
+        value = Type{std::forward<Args>(args)...};
+        wrapper->runtime_type = ctype;
+
+        return value;
+    }
+
+    /**
+     * @brief Returns a reference to an object in the context of the registry.
+     *
+     * @warning
+     * Attempting to get an object that doesn't exist in the context of the
+     * registry results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * object isn't part of the context of the registry.
+     *
+     * @tparam Type Type of object to get.
+     * @return A reference to the object.
+     */
+    template<typename Type>
+    const Type & ctx() const ENTT_NOEXCEPT {
+        const auto ctype = runtime_type<Type, context_family>();
+
+        if constexpr(is_named_type_v<Type>) {
+            const auto it = std::find_if(vars.begin(), vars.end(), [ctype](const auto &candidate) {
+                return candidate && candidate->runtime_type == ctype;
+            });
+
+            assert(it != vars.cend());
+            return static_cast<const type_wrapper<Type> *>(it->get())->value;
+        } else {
+            assert(ctype < vars.size() && vars[ctype] && vars[ctype]->runtime_type == ctype);
+            return static_cast<const type_wrapper<Type> *>(vars[ctype].get())->value;
+        }
+    }
+
+    /*! @copydoc ctx */
+    template<typename Type>
+    Type & ctx() ENTT_NOEXCEPT {
+        return const_cast<Type &>(std::as_const(*this).template ctx<Type>());
+    }
+
 private:
     std::vector<pool_data> pools;
     std::vector<owning_group_data> inner_groups;
     std::vector<non_owning_group_data> outer_groups;
+    std::vector<std::unique_ptr<ctx_wrapper>> vars;
     std::vector<entity_type> entities;
     size_type available{};
     entity_type next{};

+ 21 - 0
test/entt/entity/registry.cpp

@@ -32,6 +32,27 @@ struct listener {
     int counter{0};
 };
 
+TEST(Registry, Context) {
+    entt::registry registry;
+
+    const auto &ivalue = registry.set<int>(0);
+    const auto &cvalue = registry.set<char>('c');
+    const auto &dvalue = registry.set<double>(1.);
+    registry.set<int>(42);
+
+    ASSERT_EQ(ivalue, 42);
+    ASSERT_EQ(ivalue, registry.ctx<int>());
+    ASSERT_EQ(registry.ctx<int>(), std::as_const(registry).ctx<int>());
+
+    ASSERT_EQ(cvalue, 'c');
+    ASSERT_EQ(cvalue, registry.ctx<char>());
+    ASSERT_EQ(registry.ctx<char>(), std::as_const(registry).ctx<char>());
+
+    ASSERT_EQ(dvalue, 1.);
+    ASSERT_EQ(dvalue, registry.ctx<double>());
+    ASSERT_EQ(registry.ctx<double>(), std::as_const(registry).ctx<double>());
+}
+
 TEST(Registry, Types) {
     entt::registry registry;
     ASSERT_EQ(registry.type<int>(), registry.type<int>());