Explorar o código

registry: standalone context type (close #575)

Michele Caini %!s(int64=4) %!d(string=hai) anos
pai
achega
3878a696af

+ 22 - 24
docs/md/entity.md

@@ -911,40 +911,39 @@ use the preferred tool.
 
 ## 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 by means of a member function named `set` to use to create a
-context variable from a given type. Either `ctx` or `try_ctx` can be used to
-retrieve the newly created instance, while `unset` is meant to clear the
-variable if needed:
+Each registry has a _context_ associated with it, which is an `any` object map
+accessible by type for convenience.<br/>
+The context is returned via the `ctx` functions and offers a minimal set of
+features including the following:
 
 ```cpp
 // creates a new context variable initialized with the given values
-registry.set<my_type>(42, 'c');
+registry.ctx().emplace<my_type>(42, 'c');
 
 // gets the context variable as a non-const reference from a non-const registry
-auto &var = registry.ctx<my_type>();
+auto &var = registry.ctx().at<my_type>();
 
 // gets the context variable as a const reference from either a const or a non-const registry
-const auto &cvar = registry.ctx<const my_type>();
+const auto &cvar = registry.ctx().at<const my_type>();
 
 // unsets the context variable
-registry.unset<my_type>();
+registry.ctx().erase<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.<br/>
-The `try_ctx` member function returns a pointer to the context variable if it
-exists, otherwise it returns a null pointer. As `ctx`, it supports both const
-and non-const types and requires a const one when used on a const registry:
+can be moved.<br/>
+For all users who want to use the context but don't want to create elements, the
+`contains` and `find` functions are also available:
 
 ```cpp
-if(auto *cptr = registry.try_ctx<const my_type>(); cptr) {
-    // uses the context variable associated with the registry, if any
-}
+const bool contains = registry.ctx().contains<my_type>();
+const my_type *value = registry.ctx().find<const my_type>();
 ```
 
+Both support constant types, as does `at`. Furthermore, since the context is
+essentially a map of `any`s, it offers full support for references and allows
+users to hook externally managed objects to the registry itself.
+
 ### Aliased properties
 
 Context variables can also be used to create aliases for existing variables that
@@ -955,13 +954,13 @@ lvalue is necessarily provided as an argument:
 
 ```cpp
 time clock;
-registry.set<my_type &>(clock);
+registry.ctx().emplace<my_type &>(clock);
 ```
 
 Read-only aliased properties are created using const types instead:
 
 ```cpp
-registry.set<const my_type &>(clock);
+registry.ctx().emplace<const my_type &>(clock);
 ```
 
 From the point of view of the user, there are no differences between a variable
@@ -970,12 +969,11 @@ variables aren't accesible as non-const references:
 
 ```cpp
 // read-only variables only support const access
-const my_type *ptr = registry.try_ctx<const my_type>();
-const my_type &var = registry.ctx<const my_type>();
+const my_type *ptr = registry.ctx().find<const my_type>();
+const my_type &var = registry.ctx().at<const my_type>();
 ```
 
-Aliased properties can be unset and are overwritten when `set` is invoked, as it
-happens with standard variables.
+Aliased properties can also be erased as it happens with any other variable.
 
 ## Pointer stability
 

+ 1 - 1
src/entt/entity/organizer.hpp

@@ -131,7 +131,7 @@ class basic_organizer final {
         } else if constexpr(internal::is_view_v<Type>) {
             return as_view{reg};
         } else {
-            return reg.template ctx_or_set<std::remove_reference_t<Type>>();
+            return reg.ctx().template emplace<std::remove_reference_t<Type>>();
         }
     }
 

+ 60 - 104
src/entt/entity/registry.hpp

@@ -158,6 +158,54 @@ template<typename ILhs, typename IRhs>
     return !(lhs < rhs);
 }
 
+class registry_context {
+    template<typename Type>
+    [[nodiscard]] id_type type_to_key() const ENTT_NOEXCEPT {
+        return type_id<std::remove_const_t<std::remove_reference_t<Type>>>().hash();
+    }
+
+public:
+    template<typename Type, typename... Args>
+    Type &emplace(Args &&...args) {
+        return any_cast<Type &>(data.try_emplace(type_to_key<Type>(), std::in_place_type<Type>, std::forward<Args>(args)...).first->second);
+    }
+
+    template<typename Type>
+    void erase() {
+        data.erase(type_to_key<Type>());
+    }
+
+    template<typename Type>
+    [[nodiscard]] std::add_const_t<Type> &at() const {
+        return any_cast<std::add_const_t<Type> &>(data.at(type_to_key<Type>()));
+    }
+
+    template<typename Type>
+    [[nodiscard]] Type &at() {
+        return any_cast<Type &>(data.at(type_to_key<Type>()));
+    }
+
+    template<typename Type>
+    [[nodiscard]] std::add_const_t<Type> *find() const {
+        auto it = data.find(type_to_key<Type>());
+        return it == data.cend() ? nullptr : any_cast<std::add_const_t<Type>>(&it->second);
+    }
+
+    template<typename Type>
+    [[nodiscard]] Type *find() {
+        auto it = data.find(type_to_key<Type>());
+        return it == data.end() ? nullptr : any_cast<Type>(&it->second);
+    }
+
+    template<typename Type>
+    [[nodiscard]] bool contains() const {
+        return data.contains(type_to_key<Type>());
+    }
+
+private:
+    dense_hash_map<id_type, basic_any<0u>, identity> data;
+};
+
 } // namespace internal
 
 /**
@@ -281,6 +329,8 @@ public:
     using size_type = std::size_t;
     /*! @brief Common type among all storage types. */
     using base_type = basic_common_type;
+    /*! @brief Context type. */
+    using context = internal::registry_context;
 
     /*! @brief Default constructor. */
     basic_registry() = default;
@@ -291,10 +341,10 @@ public:
      */
     basic_registry(basic_registry &&other) ENTT_NOEXCEPT
         : pools{std::move(other.pools)},
-          vars{std::move(other.vars)},
           groups{std::move(other.groups)},
           entities{std::move(other.entities)},
-          free_list{other.free_list} {
+          free_list{other.free_list},
+          vars{std::move(other.vars)} {
         for(auto &&curr: pools) {
             curr.second->bind(forward_as_any(*this));
         }
@@ -307,10 +357,10 @@ public:
      */
     basic_registry &operator=(basic_registry &&other) ENTT_NOEXCEPT {
         pools = std::move(other.pools);
-        vars = std::move(other.vars);
         groups = std::move(other.groups);
         entities = std::move(other.entities);
         free_list = other.free_list;
+        vars = std::move(other.vars);
 
         for(auto &&curr: pools) {
             curr.second->bind(forward_as_any(*this));
@@ -1409,118 +1459,24 @@ public:
     }
 
     /**
-     * @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) {
-        auto &&elem = vars[type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value()];
-        elem.template emplace<Type>(std::forward<Args>(args)...);
-        return any_cast<Type &>(elem);
-    }
-
-    /**
-     * @brief Unsets a context variable if it exists.
-     * @tparam Type Type of object to set.
-     */
-    template<typename Type>
-    void unset() {
-        vars.erase(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value());
-    }
-
-    /**
-     * @brief Binds an object to the context of the registry.
-     *
-     * In case the context doesn't contain the given object, the parameters
-     * provided are used to construct it.
-     *
-     * @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 object.
-     * @return A reference to the object in the context of the registry.
-     */
-    template<typename Type, typename... Args>
-    [[nodiscard]] Type &ctx_or_set(Args &&...args) {
-        return any_cast<Type &>(vars.try_emplace(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value(), std::in_place_type<Type>, std::forward<Args>(args)...).first->second);
-    }
-
-    /**
-     * @brief Returns a pointer to an object in the context of the registry.
-     * @tparam Type Type of object to get.
-     * @return A pointer to the object if it exists in the context of the
-     * registry, a null pointer otherwise.
+     * @brief Returns the context object, that is, a general purpose container.
+     * @return The context object, that is, a general purpose container.
      */
-    template<typename Type>
-    [[nodiscard]] std::add_const_t<Type> *try_ctx() const {
-        auto it = vars.find(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value());
-        return it == vars.cend() ? nullptr : any_cast<std::add_const_t<Type>>(&it->second);
-    }
-
-    /*! @copydoc try_ctx */
-    template<typename Type>
-    [[nodiscard]] Type *try_ctx() {
-        auto it = vars.find(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value());
-        return it == vars.end() ? nullptr : any_cast<Type>(&it->second);
-    }
-
-    /**
-     * @brief Returns a reference to an object in the context of the registry.
-     *
-     * @warning
-     * Attempting to get a context variable that doesn't exist results in
-     * undefined behavior.
-     *
-     * @tparam Type Type of object to get.
-     * @return A valid reference to the object in the context of the registry.
-     */
-    template<typename Type>
-    [[nodiscard]] std::add_const_t<Type> &ctx() const {
-        return any_cast<std::add_const_t<Type> &>(vars.at(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value()));
+    context &ctx() ENTT_NOEXCEPT {
+        return vars;
     }
 
     /*! @copydoc ctx */
-    template<typename Type>
-    [[nodiscard]] Type &ctx() {
-        return any_cast<Type &>(vars.at(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value()));
-    }
-
-    /**
-     * @brief Visits a registry and returns the type info for its context
-     * variables.
-     *
-     * The signature of the function should be equivalent to the following:
-     *
-     * @code{.cpp}
-     * void(const type_info &);
-     * @endcode
-     *
-     * Returned identifiers are those of the context variables currently set.
-     *
-     * @sa type_info
-     *
-     * @tparam Func Type of the function object to invoke.
-     * @param func A valid function object.
-     */
-    template<typename Func>
-    void ctx(Func func) const {
-        for(auto &&curr: vars) {
-            func(curr.second.type());
-        }
+    const context &ctx() const ENTT_NOEXCEPT {
+        return vars;
     }
 
 private:
     dense_hash_map<id_type, std::unique_ptr<base_type>, identity> pools{};
-    dense_hash_map<id_type, basic_any<0u>, identity> vars{};
     std::vector<group_data> groups{};
     std::vector<entity_type> entities{};
     entity_type free_list{tombstone};
+    context vars;
 };
 
 } // namespace entt

+ 8 - 8
test/entt/entity/organizer.cpp

@@ -358,17 +358,17 @@ TEST(Organizer, Prepare) {
 
     const auto graph = organizer.graph();
 
-    ASSERT_EQ(registry.try_ctx<int>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<char>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<double>(), nullptr);
+    ASSERT_FALSE(registry.ctx().contains<int>());
+    ASSERT_FALSE(registry.ctx().contains<char>());
+    ASSERT_FALSE(registry.ctx().contains<double>());
 
     for(auto &&vertex: graph) {
         vertex.prepare(registry);
     }
 
-    ASSERT_EQ(registry.try_ctx<int>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<char>(), nullptr);
-    ASSERT_NE(registry.try_ctx<double>(), nullptr);
+    ASSERT_FALSE(registry.ctx().contains<int>());
+    ASSERT_FALSE(registry.ctx().contains<char>());
+    ASSERT_TRUE(registry.ctx().contains<double>());
 }
 
 TEST(Organizer, Dependencies) {
@@ -422,10 +422,10 @@ TEST(Organizer, ToArgsIntegrity) {
     entt::registry registry;
 
     organizer.emplace<&to_args_integrity>();
-    registry.set<std::size_t>(42u);
+    registry.ctx().emplace<std::size_t>(42u);
 
     auto graph = organizer.graph();
     graph[0u].callback()(graph[0u].data(), registry);
 
-    ASSERT_EQ(registry.ctx<std::size_t>(), 0u);
+    ASSERT_EQ(registry.ctx().at<std::size_t>(), 0u);
 }

+ 63 - 74
test/entt/entity/registry.cpp

@@ -64,95 +64,84 @@ struct owner {
 
 TEST(Registry, Context) {
     entt::registry registry;
-
-    ASSERT_EQ(registry.try_ctx<char>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<const int>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<double>(), nullptr);
-
-    registry.set<char>();
-    registry.set<int>();
-    // suppress the warning due to the [[nodiscard]] attribute
-    static_cast<void>(registry.ctx_or_set<double>());
-
-    ASSERT_NE(registry.try_ctx<char>(), nullptr);
-    ASSERT_NE(registry.try_ctx<const int>(), nullptr);
-    ASSERT_NE(registry.try_ctx<double>(), nullptr);
-
-    registry.unset<int>();
-    registry.unset<double>();
-
-    auto count = 0;
-
-    registry.ctx([&count](const auto &info) {
-        ASSERT_EQ(info.hash(), entt::type_hash<char>::value());
-        ++count;
-    });
-
-    ASSERT_EQ(count, 1);
-
-    ASSERT_NE(registry.try_ctx<char>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<const int>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<double>(), nullptr);
-
-    registry.set<char>('c');
-    registry.set<int>(0);
-    registry.set<double>(1.);
-    registry.set<int>(42);
-
-    ASSERT_EQ(registry.ctx_or_set<char>('a'), 'c');
-    ASSERT_NE(registry.try_ctx<char>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<char>(), &registry.ctx<char>());
-    ASSERT_EQ(registry.ctx<char>(), std::as_const(registry).ctx<const char>());
-
-    ASSERT_EQ(registry.ctx<const int>(), 42);
-    ASSERT_NE(registry.try_ctx<int>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<const int>(), &registry.ctx<int>());
-    ASSERT_EQ(registry.ctx<int>(), std::as_const(registry).ctx<const int>());
-
-    ASSERT_EQ(registry.ctx<const double>(), 1.);
-    ASSERT_NE(registry.try_ctx<double>(), nullptr);
-    ASSERT_EQ(registry.try_ctx<const double>(), &registry.ctx<double>());
-    ASSERT_EQ(registry.ctx<double>(), std::as_const(registry).ctx<const double>());
-
-    ASSERT_EQ(registry.try_ctx<float>(), nullptr);
+    auto &ctx = registry.ctx();
+    const auto &cctx = std::as_const(registry).ctx();
+
+    ASSERT_FALSE(ctx.contains<char>());
+    ASSERT_FALSE(cctx.contains<const int>());
+    ASSERT_EQ(ctx.find<char>(), nullptr);
+    ASSERT_EQ(cctx.find<const int>(), nullptr);
+
+    ctx.emplace<char>();
+    ctx.emplace<int>();
+
+    ASSERT_TRUE(ctx.contains<char>());
+    ASSERT_TRUE(cctx.contains<int>());
+    ASSERT_NE(ctx.find<const char>(), nullptr);
+    ASSERT_NE(cctx.find<const int>(), nullptr);
+
+    ctx.erase<int>();
+
+    ASSERT_TRUE(ctx.contains<const char>());
+    ASSERT_FALSE(cctx.contains<const int>());
+    ASSERT_NE(ctx.find<char>(), nullptr);
+    ASSERT_EQ(cctx.find<int>(), nullptr);
+
+    ctx.erase<char>();
+    ctx.emplace<char>('c');
+    ctx.emplace<int>(42);
+    ;
+
+    ASSERT_EQ(ctx.emplace<char>('a'), 'c');
+    ASSERT_EQ(ctx.find<const char>(), cctx.find<char>());
+    ASSERT_EQ(ctx.at<char>(), cctx.at<const char>());
+    ASSERT_EQ(ctx.at<char>(), 'c');
+
+    ASSERT_EQ(ctx.emplace<const int>(0), 42);
+    ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
+    ASSERT_EQ(ctx.at<int>(), cctx.at<const int>());
+    ASSERT_EQ(ctx.at<int>(), 42);
+
+    ASSERT_EQ(ctx.find<double>(), nullptr);
+    ASSERT_EQ(cctx.find<double>(), nullptr);
 }
 
 TEST(Registry, ContextAsRef) {
     entt::registry registry;
     int value{3};
 
-    registry.set<int &>(value);
+    registry.ctx().emplace<int &>(value);
 
-    ASSERT_NE(registry.try_ctx<int>(), nullptr);
-    ASSERT_NE(registry.try_ctx<const int>(), nullptr);
-    ASSERT_NE(std::as_const(registry).try_ctx<const int>(), nullptr);
-    ASSERT_EQ(registry.ctx<const int>(), 3);
-    ASSERT_EQ(registry.ctx<int>(), 3);
+    ASSERT_NE(registry.ctx().find<int>(), nullptr);
+    ASSERT_NE(registry.ctx().find<const int>(), nullptr);
+    ASSERT_NE(std::as_const(registry).ctx().find<const int>(), nullptr);
+    ASSERT_EQ(registry.ctx().at<const int>(), 3);
+    ASSERT_EQ(registry.ctx().at<int>(), 3);
 
-    registry.ctx<int>() = 42;
+    registry.ctx().at<int>() = 42;
 
-    ASSERT_EQ(registry.ctx<int>(), 42);
+    ASSERT_EQ(registry.ctx().at<int>(), 42);
     ASSERT_EQ(value, 42);
 
     value = 3;
 
-    ASSERT_EQ(std::as_const(registry).ctx<const int>(), 3);
+    ASSERT_EQ(std::as_const(registry).ctx().at<const int>(), 3);
 }
 
 TEST(Registry, ContextAsConstRef) {
     entt::registry registry;
     int value{3};
 
-    registry.set<const int &>(value);
+    registry.ctx().emplace<const int &>(value);
 
-    ASSERT_EQ(registry.try_ctx<int>(), nullptr);
-    ASSERT_NE(registry.try_ctx<const int>(), nullptr);
-    ASSERT_NE(std::as_const(registry).try_ctx<const int>(), nullptr);
-    ASSERT_EQ(registry.ctx<const int>(), 3);
+    ASSERT_EQ(registry.ctx().find<int>(), nullptr);
+    ASSERT_NE(registry.ctx().find<const int>(), nullptr);
+    ASSERT_NE(std::as_const(registry).ctx().find<const int>(), nullptr);
+    ASSERT_EQ(registry.ctx().at<const int>(), 3);
 
     value = 42;
 
-    ASSERT_EQ(std::as_const(registry).ctx<const int>(), 42);
+    ASSERT_EQ(std::as_const(registry).ctx().at<const int>(), 42);
 }
 
 TEST(Registry, Functionalities) {
@@ -1762,11 +1751,11 @@ TEST(Registry, Constness) {
     static_assert((std::is_same_v<decltype(registry.try_get<int>({})), int *>));
     static_assert((std::is_same_v<decltype(registry.try_get<int, const char>({})), std::tuple<int *, const char *>>));
 
-    static_assert((std::is_same_v<decltype(registry.ctx<int>()), int &>));
-    static_assert((std::is_same_v<decltype(registry.ctx<const char>()), const char &>));
+    static_assert((std::is_same_v<decltype(registry.ctx().at<int>()), int &>));
+    static_assert((std::is_same_v<decltype(registry.ctx().at<const char>()), const char &>));
 
-    static_assert((std::is_same_v<decltype(registry.try_ctx<int>()), int *>));
-    static_assert((std::is_same_v<decltype(registry.try_ctx<const char>()), const char *>));
+    static_assert((std::is_same_v<decltype(registry.ctx().find<int>()), int *>));
+    static_assert((std::is_same_v<decltype(registry.ctx().find<const char>()), const char *>));
 
     static_assert((std::is_same_v<decltype(std::as_const(registry).get<int>({})), const int &>));
     static_assert((std::is_same_v<decltype(std::as_const(registry).get<int, const char>({})), std::tuple<const int &, const char &>>));
@@ -1774,11 +1763,11 @@ TEST(Registry, Constness) {
     static_assert((std::is_same_v<decltype(std::as_const(registry).try_get<int>({})), const int *>));
     static_assert((std::is_same_v<decltype(std::as_const(registry).try_get<int, const char>({})), std::tuple<const int *, const char *>>));
 
-    static_assert((std::is_same_v<decltype(std::as_const(registry).ctx<int>()), const int &>));
-    static_assert((std::is_same_v<decltype(std::as_const(registry).ctx<const char>()), const char &>));
+    static_assert((std::is_same_v<decltype(std::as_const(registry).ctx().at<int>()), const int &>));
+    static_assert((std::is_same_v<decltype(std::as_const(registry).ctx().at<const char>()), const char &>));
 
-    static_assert((std::is_same_v<decltype(std::as_const(registry).try_ctx<int>()), const int *>));
-    static_assert((std::is_same_v<decltype(std::as_const(registry).try_ctx<const char>()), const char *>));
+    static_assert((std::is_same_v<decltype(std::as_const(registry).ctx().find<int>()), const int *>));
+    static_assert((std::is_same_v<decltype(std::as_const(registry).ctx().find<const char>()), const char *>));
 }
 
 TEST(Registry, MoveOnlyComponent) {