Non-owning entt::handle (#513)

This commit is contained in:
Indiana Kernick
2020-07-04 23:57:30 +09:30
committed by Michele Caini
parent cec1b932bd
commit 7889ca1ca7
6 changed files with 449 additions and 0 deletions

View File

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

View File

@@ -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
src/entt/entity/handle.hpp Normal file
View File

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

View File

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

View File

@@ -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
test/entt/entity/handle.cpp Normal file
View File

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