diff --git a/docs/md/entity.md b/docs/md/entity.md index e8836cb10..33b34e4ce 100644 --- a/docs/md/entity.md +++ b/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().connect>(); 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.
+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; +using my_const_handle = entt::basic_handle; +``` + +Handles are also implicitly convertible to const handles out of the box but not +the other way around.
+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 diff --git a/src/entt/entity/fwd.hpp b/src/entt/entity/fwd.hpp index 862eff695..60d1b0f77 100644 --- a/src/entt/entity/fwd.hpp +++ b/src/entt/entity/fwd.hpp @@ -32,6 +32,10 @@ class basic_observer; template struct basic_actor; +/*! @class basic_handle */ +template +struct basic_handle; + /*! @class basic_snapshot */ template class basic_snapshot; @@ -56,6 +60,12 @@ using observer = basic_observer; /*! @brief Alias declaration for the most common use case. */ using actor [[deprecated("Consider using the handle class instead")]] = basic_actor; +/*! @brief Alias declaration for the most common use case. */ +using handle = basic_handle; + +/*! @brief Alias declaration for the most common use case. */ +using const_handle = basic_handle; + /*! @brief Alias declaration for the most common use case. */ using snapshot = basic_snapshot; diff --git a/src/entt/entity/handle.hpp b/src/entt/entity/handle.hpp new file mode 100644 index 000000000..db722a069 --- /dev/null +++ b/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 +struct basic_handle { + /*! @brief Underlying entity identifier. */ + using entity_type = std::remove_const_t; + + /*! @brief Type of registry accepted by the handle. */ + using registry_type = std::conditional_t< + std::is_const_v, + const basic_registry, + basic_registry + >; + + /** + * @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 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 + decltype(auto) emplace(Args &&... args) const { + return reg->template emplace(entt, std::forward(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 + decltype(auto) emplace_or_replace(Args &&... args) const { + return reg->template emplace_or_replace(entt, std::forward(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 + decltype(auto) patch(Func &&... func) const { + return reg->template patch(entt, std::forward(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 + decltype(auto) replace(Args &&... args) const { + return reg->template replace(entt, std::forward(args)...); + } + + /** + * @brief Removes the given components from a handle. + * @sa basic_registry::remove + * @tparam Component Types of components to remove. + */ + template + void remove() const { + reg->template remove(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 + decltype(auto) remove_if_exists() const { + return reg->template remove_if_exists(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 + [[nodiscard]] decltype(auto) has() const { + return reg->template has(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 + [[nodiscard]] decltype(auto) any() const { + return reg->template any(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 + [[nodiscard]] decltype(auto) get() const { + return reg->template get(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 + [[nodiscard]] decltype(auto) get_or_emplace(Args &&... args) const { + return reg->template get_or_emplace(entt, std::forward(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 + [[nodiscard]] decltype(auto) try_get() const { + return reg->template try_get(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 + void visit(Func &&func) const { + reg->visit(entt, std::forward(func)); + } + +private: + registry_type *reg; + entity_type entt; +}; + + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +basic_handle(basic_registry &, Entity) -> basic_handle; + + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +basic_handle(const basic_registry &, Entity) -> basic_handle; + + +} + + +#endif diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp index fee1890d9..c970b23ba 100644 --- a/src/entt/entt.hpp +++ b/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" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 792817c83..87a9b00aa 100644 --- a/test/CMakeLists.txt +++ b/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) diff --git a/test/entt/entity/handle.cpp b/test/entt/entity/handle.cpp new file mode 100644 index 000000000..42c696560 --- /dev/null +++ b/test/entt/entity/handle.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include + +TEST(BasicHandle, Assumptions) { + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_assignable_v); + static_assert(std::is_trivially_destructible_v); + + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_assignable_v); + static_assert(std::is_trivially_destructible_v); +} + +TEST(BasicHandle, DeductionGuide) { + static_assert(std::is_same_v(), {}}), entt::basic_handle>); + static_assert(std::is_same_v(), {}}), entt::basic_handle>); +} + +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); + static_assert(std::is_same_v); + + handle = static_cast(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(3)); + ASSERT_EQ('c', handle.emplace_or_replace('c')); + + const auto &patched = handle.patch([](auto &comp) { comp = 42; }); + + ASSERT_EQ(42, patched); + ASSERT_EQ('a', handle.replace('a')); + ASSERT_TRUE((handle.has())); + + handle.remove(); + + ASSERT_TRUE(registry.empty()); + ASSERT_EQ(0, handle.remove_if_exists()); + + handle.visit([](auto id) { ASSERT_EQ(entt::type_info::id(), id); }); + + ASSERT_TRUE((handle.any())); + ASSERT_FALSE((handle.has())); + ASSERT_FALSE(handle.orphan()); + + handle.remove_all(); + + ASSERT_TRUE(registry.empty()); + ASSERT_TRUE(handle.orphan()); + + ASSERT_EQ(42, handle.get_or_emplace(42)); + ASSERT_EQ(42, handle.get_or_emplace(1)); + ASSERT_EQ(42, handle.get()); + + ASSERT_EQ(42, *handle.try_get()); + ASSERT_EQ(nullptr, handle.try_get()); +} + +TEST(BasicHandle, FromEntity) { + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity, 42); + registry.emplace(entity, 'c'); + + entt::handle handle{registry, entity}; + + ASSERT_TRUE(handle); + ASSERT_EQ(entity, handle.entity()); + ASSERT_TRUE((handle.has())); + ASSERT_EQ(handle.get(), 42); + ASSERT_EQ(handle.get(), 'c'); +} + +TEST(BasicHandle, Lifetime) { + entt::registry registry; + const auto entity = registry.create(); + auto *handle = new entt::handle{registry, entity}; + handle->emplace(); + + ASSERT_FALSE(registry.empty()); + ASSERT_FALSE(registry.empty()); + + registry.each([handle](const auto e) { + ASSERT_EQ(handle->entity(), e); + }); + + delete handle; + + ASSERT_FALSE(registry.empty()); + 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(42); + + ASSERT_EQ(handle.get(), chandle.get()); + ASSERT_EQ(chandle.get(), 42); +}