registry: standalone context type (close #575)

This commit is contained in:
Michele Caini
2022-01-20 09:37:31 +01:00
parent 49ac34c405
commit 3878a696af
5 changed files with 145 additions and 202 deletions

View File

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

View File

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

View File

@@ -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.
* @brief Returns the context object, that is, a general purpose container.
* @return The context object, that is, a general purpose container.
*/
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.
*/
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

View File

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

View File

@@ -64,95 +64,84 @@ struct owner {
TEST(Registry, Context) {
entt::registry registry;
auto &ctx = registry.ctx();
const auto &cctx = std::as_const(registry).ctx();
ASSERT_EQ(registry.try_ctx<char>(), nullptr);
ASSERT_EQ(registry.try_ctx<const int>(), nullptr);
ASSERT_EQ(registry.try_ctx<double>(), nullptr);
ASSERT_FALSE(ctx.contains<char>());
ASSERT_FALSE(cctx.contains<const int>());
ASSERT_EQ(ctx.find<char>(), nullptr);
ASSERT_EQ(cctx.find<const int>(), nullptr);
registry.set<char>();
registry.set<int>();
// suppress the warning due to the [[nodiscard]] attribute
static_cast<void>(registry.ctx_or_set<double>());
ctx.emplace<char>();
ctx.emplace<int>();
ASSERT_NE(registry.try_ctx<char>(), nullptr);
ASSERT_NE(registry.try_ctx<const int>(), nullptr);
ASSERT_NE(registry.try_ctx<double>(), nullptr);
ASSERT_TRUE(ctx.contains<char>());
ASSERT_TRUE(cctx.contains<int>());
ASSERT_NE(ctx.find<const char>(), nullptr);
ASSERT_NE(cctx.find<const int>(), nullptr);
registry.unset<int>();
registry.unset<double>();
ctx.erase<int>();
auto count = 0;
ASSERT_TRUE(ctx.contains<const char>());
ASSERT_FALSE(cctx.contains<const int>());
ASSERT_NE(ctx.find<char>(), nullptr);
ASSERT_EQ(cctx.find<int>(), nullptr);
registry.ctx([&count](const auto &info) {
ASSERT_EQ(info.hash(), entt::type_hash<char>::value());
++count;
});
ctx.erase<char>();
ctx.emplace<char>('c');
ctx.emplace<int>(42);
;
ASSERT_EQ(count, 1);
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_NE(registry.try_ctx<char>(), nullptr);
ASSERT_EQ(registry.try_ctx<const int>(), nullptr);
ASSERT_EQ(registry.try_ctx<double>(), nullptr);
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);
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);
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) {