瀏覽代碼

Non-owning entt::handle (#513)

Indiana Kernick 5 年之前
父節點
當前提交
7889ca1ca7
共有 6 個文件被更改,包括 449 次插入0 次删除
  1. 32 0
      docs/md/entity.md
  2. 10 0
      src/entt/entity/fwd.hpp
  3. 272 0
      src/entt/entity/handle.hpp
  4. 1 0
      src/entt/entt.hpp
  5. 1 0
      test/CMakeLists.txt
  6. 133 0
      test/entt/entity/handle.cpp

+ 32 - 0
docs/md/entity.md

@@ -21,6 +21,7 @@
     * [Null entity](#null-entity)
     * [Dependencies](#dependencies)
     * [Invoke](#invoke)
+    * [Handle](#handle)
     * [Context variables](#context-variables)
   * [Meet the runtime](#meet-the-runtime)
     * [Cloning a registry](#cloning-a-registry)
@@ -645,6 +646,37 @@ registry.on_construct<clazz>().connect<entt::invoke<&clazz::func>>();
 All it does is pick up the _right_ component for the received entity and invoke
 the requested method, passing on the arguments if necessary.
 
+### Handle
+
+A handle is a thin wrapper around an entity and a registry. It provides the same
+functions that the registry offers for working with components, such as
+`emplace`, `get`, `patch`, `remove` and so on. The difference being that the
+entity is implicitly passed to the registry.<br/>
+A handle is also non-owning, meaning that it can be freely copied and moved
+around without affecting its entity (in fact, handles happen to be trivially
+copyable). An implication of this is that mutability becomes part of the
+type.
+
+There are two aliases that use `entt::entity` as their default entity:
+`entt::handle` and `entt::const_handle`. Users can also easily create their own
+aliases for custom identifiers as:
+
+```cpp
+using my_handle = entt::basic_handle<my_identifier>;
+using my_const_handle = entt::basic_handle<const my_identifier>;
+```
+
+Handles are also implicitly convertible to const handles out of the box but not
+the other way around.<br/>
+A handle stores a non-const pointer to a registry and therefore it can do all
+the things that can be done with a non-const registry. On the other hand, a
+const handles store const pointers to registries and offer a restricted set of
+functionalities.
+
+This class is intended to simplify function signatures. In case of functions
+that take a registry and an entity and do most of their work on that entity,
+users might want to consider using handles, either const or non-const.
+
 ### Context variables
 
 It is often convenient to assign context variables to a registry, so as to make

+ 10 - 0
src/entt/entity/fwd.hpp

@@ -32,6 +32,10 @@ class basic_observer;
 template <typename>
 struct basic_actor;
 
+/*! @class basic_handle */
+template<typename>
+struct basic_handle;
+
 /*! @class basic_snapshot */
 template<typename>
 class basic_snapshot;
@@ -56,6 +60,12 @@ using observer = basic_observer<entity>;
 /*! @brief Alias declaration for the most common use case. */
 using actor [[deprecated("Consider using the handle class instead")]] = basic_actor<entity>;
 
+/*! @brief Alias declaration for the most common use case. */
+using handle = basic_handle<entity>;
+
+/*! @brief Alias declaration for the most common use case. */
+using const_handle = basic_handle<const entity>;
+
 /*! @brief Alias declaration for the most common use case. */
 using snapshot = basic_snapshot<entity>;
 

+ 272 - 0
src/entt/entity/handle.hpp

@@ -0,0 +1,272 @@
+#ifndef ENTT_ENTITY_HANDLE_HPP
+#define ENTT_ENTITY_HANDLE_HPP
+
+
+#include "registry.hpp"
+
+
+namespace entt {
+
+
+/**
+ * @brief Non-owning handle to an entity.
+ *
+ * Tiny wrapper around a registry and an entity.
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+struct basic_handle {
+    /*! @brief Underlying entity identifier. */
+    using entity_type = std::remove_const_t<Entity>;
+
+    /*! @brief Type of registry accepted by the handle. */
+    using registry_type = std::conditional_t<
+        std::is_const_v<Entity>,
+        const basic_registry<entity_type>,
+        basic_registry<entity_type>
+    >;
+
+    /**
+     * @brief Constructs a handle from a given registry and entity.
+     * @param ref An instance of the registry class.
+     * @param value An entity identifier.
+     */
+    basic_handle(registry_type &ref, entity_type value = null) ENTT_NOEXCEPT
+        : reg{&ref}, entt{value}
+    {}
+
+    /**
+     * @brief Assigns an entity to a handle.
+     * @param value An entity identifier.
+     * @return This handle.
+     */
+    basic_handle & operator=(const entity_type value) ENTT_NOEXCEPT {
+        entt = value;
+        return *this;
+    }
+
+    /**
+     * @brief Constructs a const handle from a non-const one.
+     * @return A const handle referring to the same entity.
+     */
+    [[nodiscard]] operator basic_handle<const entity_type>() const ENTT_NOEXCEPT {
+        return {*reg, entt};
+    }
+
+    /**
+     * @brief Converts a handle to its underlying entity.
+     * @return An entity identifier.
+     */
+    [[nodiscard]] operator entity_type() const ENTT_NOEXCEPT {
+        return entity();
+    }
+
+    /**
+     * @brief Checks if a handle refers to a valid entity or not.
+     * @return True if the handle refers to a valid entity, false otherwise.
+     */
+    [[nodiscard]] explicit operator bool() const {
+        return reg->valid(entt);
+    }
+
+    /**
+     * @brief Returns a reference to the underlying registry.
+     * @return A reference to the underlying registry.
+     */
+    [[nodiscard]] registry_type & registry() const ENTT_NOEXCEPT {
+        return *reg;
+    }
+
+    /**
+     * @brief Returns the entity associated with a handle.
+     * @return The entity associated with the handle.
+     */
+    [[nodiscard]] entity_type entity() const ENTT_NOEXCEPT {
+        return entt;
+    }
+
+    /**
+     * @brief Assigns the given component to a handle.
+     * @sa basic_registry::emplace
+     * @tparam Component Type of component to create.
+     * @tparam Args Types of arguments to use to construct the component.
+     * @param args Parameters to use to initialize the component.
+     * @return A reference to the newly created component.
+     */
+    template<typename Component, typename... Args>
+    decltype(auto) emplace(Args &&... args) const {
+        return reg->template emplace<Component>(entt, std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Assigns or replaces the given component for a handle.
+     * @sa basic_registry::emplace_or_replace
+     * @tparam Component Type of component to assign or replace.
+     * @tparam Args Types of arguments to use to construct the component.
+     * @param args Parameters to use to initialize the component.
+     * @return A reference to the newly created component.
+     */
+    template<typename Component, typename... Args>
+    decltype(auto) emplace_or_replace(Args &&... args) const {
+        return reg->template emplace_or_replace<Component>(entt, std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Patches the given component for a handle.
+     * @sa basic_registry::patch
+     * @tparam Component Type of component to patch.
+     * @tparam Func Types of the function objects to invoke.
+     * @param func Valid function objects.
+     * @return A reference to the patched component.
+     */
+    template<typename Component, typename... Func>
+    decltype(auto) patch(Func &&... func) const {
+        return reg->template patch<Component>(entt, std::forward<Func>(func)...);
+    }
+
+    /**
+     * @brief Replaces the given component for a handle.
+     * @sa basic_registry::replace
+     * @tparam Component Type of component to replace.
+     * @tparam Args Types of arguments to use to construct the component.
+     * @param args Parameters to use to initialize the component.
+     * @return A reference to the component being replaced.
+     */
+    template<typename Component, typename... Args>
+    decltype(auto) replace(Args &&... args) const {
+        return reg->template replace<Component>(entt, std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Removes the given components from a handle.
+     * @sa basic_registry::remove
+     * @tparam Component Types of components to remove.
+     */
+    template<typename... Components>
+    void remove() const {
+        reg->template remove<Components...>(entt);
+    }
+
+    /**
+     * @brief Removes the given components from a handle.
+     * @sa basic_registry::remove_if_exists
+     * @tparam Component Types of components to remove.
+     * @return The number of components actually removed.
+     */
+    template<typename... Components>
+    decltype(auto) remove_if_exists() const {
+        return reg->template remove_if_exists<Components...>(entt);
+    }
+
+    /**
+     * @brief Removes all the components from a handle and makes it orphaned.
+     * @sa basic_registry::remove_all
+     */
+    void remove_all() const {
+        reg->remove_all(entt);
+    }
+
+    /**
+     * @brief Checks if a handle has all the given components.
+     * @sa basic_registry::has
+     * @tparam Component Components for which to perform the check.
+     * @return True if the handle has all the components, false otherwise.
+     */
+    template<typename... Components>
+    [[nodiscard]] decltype(auto) has() const {
+        return reg->template has<Components...>(entt);
+    }
+
+    /**
+     * @brief Checks if a handle has at least one of the given components.
+     * @sa basic_registry::any
+     * @tparam Component Components for which to perform the check.
+     * @return True if the handle has at least one of the given components,
+     * false otherwise.
+     */
+    template<typename... Components>
+    [[nodiscard]] decltype(auto) any() const {
+        return reg->template any<Components...>(entt);
+    }
+
+    /**
+     * @brief Returns references to the given components for a handle.
+     * @sa basic_registry::get
+     * @tparam Component Types of components to get.
+     * @return References to the components owned by the handle.
+     */
+    template<typename... Components>
+    [[nodiscard]] decltype(auto) get() const {
+        return reg->template get<Components...>(entt);
+    }
+
+    /**
+     * @brief Returns a reference to the given component for a handle.
+     * @sa basic_registry::get_or_emplace
+     * @tparam Component Type of component to get.
+     * @tparam Args Types of arguments to use to construct the component.
+     * @param args Parameters to use to initialize the component.
+     * @return Reference to the component owned by the handle.
+     */
+    template<typename Component, typename... Args>
+    [[nodiscard]] decltype(auto) get_or_emplace(Args &&... args) const {
+        return reg->template get_or_emplace<Component>(entt, std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Returns pointers to the given components for a handle.
+     * @sa basic_registry::try_get
+     * @tparam Component Types of components to get.
+     * @return Pointers to the components owned by the handle.
+     */
+    template<typename... Components>
+    [[nodiscard]] decltype(auto) try_get() const {
+        return reg->template try_get<Components...>(entt);
+    }
+
+    /**
+     * @brief Checks if a handle has components assigned.
+     * @return True if the handle has no components assigned, false otherwise.
+     */
+    [[nodiscard]] bool orphan() const {
+        return reg->orphan(entt);
+    }
+
+    /**
+     * @brief Visits a handle and returns the types for its components.
+     * @sa basic_registry::visit
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void visit(Func &&func) const {
+        reg->visit(entt, std::forward<Func>(func));
+    }
+
+private:
+    registry_type *reg;
+    entity_type entt;
+};
+
+
+/**
+ * @brief Deduction guide.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>;
+
+
+/**
+ * @brief Deduction guide.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+basic_handle(const basic_registry<Entity> &, Entity) -> basic_handle<const Entity>;
+
+
+}
+
+
+#endif

+ 1 - 0
src/entt/entt.hpp

@@ -10,6 +10,7 @@
 #include "entity/actor.hpp"
 #include "entity/entity.hpp"
 #include "entity/group.hpp"
+#include "entity/handle.hpp"
 #include "entity/helper.hpp"
 #include "entity/observer.hpp"
 #include "entity/registry.hpp"

+ 1 - 0
test/CMakeLists.txt

@@ -168,6 +168,7 @@ SETUP_BASIC_TEST(utility entt/core/utility.cpp)
 SETUP_BASIC_TEST(actor entt/entity/actor.cpp)
 SETUP_BASIC_TEST(entity entt/entity/entity.cpp)
 SETUP_BASIC_TEST(group entt/entity/group.cpp)
+SETUP_BASIC_TEST(handle entt/entity/handle.cpp)
 SETUP_BASIC_TEST(helper entt/entity/helper.cpp)
 SETUP_BASIC_TEST(observer entt/entity/observer.cpp)
 SETUP_BASIC_TEST(registry entt/entity/registry.cpp)

+ 133 - 0
test/entt/entity/handle.cpp

@@ -0,0 +1,133 @@
+#include <type_traits>
+#include <utility>
+#include <gtest/gtest.h>
+#include <entt/entity/handle.hpp>
+#include <entt/entity/registry.hpp>
+
+TEST(BasicHandle, Assumptions) {
+    static_assert(std::is_trivially_copyable_v<entt::handle>);
+    static_assert(std::is_trivially_assignable_v<entt::handle, entt::handle>);
+    static_assert(std::is_trivially_destructible_v<entt::handle>);
+
+    static_assert(std::is_trivially_copyable_v<entt::const_handle>);
+    static_assert(std::is_trivially_assignable_v<entt::const_handle, entt::const_handle>);
+    static_assert(std::is_trivially_destructible_v<entt::const_handle>);
+}
+
+TEST(BasicHandle, DeductionGuide) {
+    static_assert(std::is_same_v<decltype(entt::basic_handle{std::declval<entt::registry &>(), {}}), entt::basic_handle<entt::entity>>);
+    static_assert(std::is_same_v<decltype(entt::basic_handle{std::declval<const entt::registry &>(), {}}), entt::basic_handle<const entt::entity>>);
+}
+
+TEST(BasicHandle, Construction) {
+    entt::registry registry;
+    const auto entity = registry.create();
+
+    entt::handle handle{registry, entity};
+    entt::const_handle chandle{std::as_const(registry), entity};
+
+    ASSERT_FALSE(entt::null == handle.entity());
+    ASSERT_EQ(entity, handle);
+    ASSERT_TRUE(handle);
+
+    ASSERT_FALSE(entt::null == chandle.entity());
+    ASSERT_EQ(entity, chandle);
+    ASSERT_TRUE(chandle);
+
+    ASSERT_EQ(handle, chandle);
+
+    static_assert(std::is_same_v<entt::registry &, decltype(handle.registry())>);
+    static_assert(std::is_same_v<const entt::registry &, decltype(chandle.registry())>);
+
+    handle = static_cast<entt::entity>(entt::null);
+
+    ASSERT_TRUE(entt::null == handle.entity());
+    ASSERT_NE(entity, handle);
+    ASSERT_FALSE(handle);
+
+    ASSERT_NE(handle, chandle);
+}
+
+TEST(BasicHandle, Component) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    entt::handle handle{registry, entity};
+
+    ASSERT_EQ(3, handle.emplace<int>(3));
+    ASSERT_EQ('c', handle.emplace_or_replace<char>('c'));
+
+    const auto &patched = handle.patch<int>([](auto &comp) { comp = 42; });
+
+    ASSERT_EQ(42, patched);
+    ASSERT_EQ('a', handle.replace<char>('a'));
+    ASSERT_TRUE((handle.has<int, char>()));
+
+    handle.remove<char>();
+
+    ASSERT_TRUE(registry.empty<char>());
+    ASSERT_EQ(0, handle.remove_if_exists<char>());
+
+    handle.visit([](auto id) { ASSERT_EQ(entt::type_info<int>::id(), id); });
+
+    ASSERT_TRUE((handle.any<int, char>()));
+    ASSERT_FALSE((handle.has<int, char>()));
+    ASSERT_FALSE(handle.orphan());
+
+    handle.remove_all();
+
+    ASSERT_TRUE(registry.empty<int>());
+    ASSERT_TRUE(handle.orphan());
+
+    ASSERT_EQ(42, handle.get_or_emplace<int>(42));
+    ASSERT_EQ(42, handle.get_or_emplace<int>(1));
+    ASSERT_EQ(42, handle.get<int>());
+
+    ASSERT_EQ(42, *handle.try_get<int>());
+    ASSERT_EQ(nullptr, handle.try_get<char>());
+}
+
+TEST(BasicHandle, FromEntity) {
+    entt::registry registry;
+    const auto entity = registry.create();
+
+    registry.emplace<int>(entity, 42);
+    registry.emplace<char>(entity, 'c');
+
+    entt::handle handle{registry, entity};
+
+    ASSERT_TRUE(handle);
+    ASSERT_EQ(entity, handle.entity());
+    ASSERT_TRUE((handle.has<int, char>()));
+    ASSERT_EQ(handle.get<int>(), 42);
+    ASSERT_EQ(handle.get<char>(), 'c');
+}
+
+TEST(BasicHandle, Lifetime) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    auto *handle = new entt::handle{registry, entity};
+    handle->emplace<int>();
+
+    ASSERT_FALSE(registry.empty<int>());
+    ASSERT_FALSE(registry.empty());
+
+    registry.each([handle](const auto e) {
+        ASSERT_EQ(handle->entity(), e);
+    });
+
+    delete handle;
+
+    ASSERT_FALSE(registry.empty<int>());
+    ASSERT_FALSE(registry.empty());
+}
+
+TEST(BasicHandle, ImplicitConversions) {
+    entt::registry registry;
+    const entt::handle handle{registry, registry.create()};
+    const entt::const_handle chandle = handle;
+
+    handle.emplace<int>(42);
+
+    ASSERT_EQ(handle.get<int>(), chandle.get<int>());
+    ASSERT_EQ(chandle.get<int>(), 42);
+}