Non-owning entt::handle (#513)
This commit is contained in:
committed by
Michele Caini
parent
cec1b932bd
commit
7889ca1ca7
@@ -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
|
||||
|
||||
@@ -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
272
src/entt/entity/handle.hpp
Normal 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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
133
test/entt/entity/handle.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user