registry: updated signatures for callbacks, deprecated dependency

This commit is contained in:
Michele Caini
2019-08-20 16:52:30 +02:00
parent 15e7486ca3
commit a06d5dd7bb
7 changed files with 215 additions and 188 deletions

4
TODO
View File

@@ -19,6 +19,7 @@
* allow to "merge" registries easily
* allow for custom stomp functions
* deprecate/replace snapshot
* remove dependency
* remove prototype
TODO
@@ -27,6 +28,3 @@ TODO
* make meta work across boundaries
- inline variables are fine here, only the head represents a problem
- we should always resolve by looking into the list of types when working across boundaries, no direct resolve
* signals: entity/registry/component instead of registry/entity/component
- dependency becomes a short circuit on the registry
- deprecate dependency

View File

@@ -18,18 +18,18 @@
* [Runtime components](#runtime-components)
* [A journey through a plugin](#a-journey-through-a-plugin)
* [Sorting: is it possible?](#sorting-is-it-possible)
* [Multiple registries to the rescue](#multiple-registries-to-the-rescue)
* [Helpers](#helpers)
* [Null entity](#null-entity)
* [Multiple registries](#multiple-registries)
* [Dependencies](#dependencies)
* [Tags](#tags)
* [Actor](#actor)
* [Context variables](#context-variables)
* [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous)
* [Snapshot loader](#snapshot-loader)
* [Continuous loader](#continuous-loader)
* [Archives](#archives)
* [One example to rule them all](#one-example-to-rule-them-all)
* [The actor class](#the-actor-class)
* [Helpers](#helpers)
* [Dependency function](#dependency-function)
* [Tags](#tags)
* [Null entity](#null-entity)
* [Context variables](#context-variables)
* [Views and Groups](#views-and-groups)
* [Views](#views)
* [Runtime views](#runtime-views)
@@ -365,7 +365,7 @@ The function type of a listener for the construction signal should be equivalent
to the following:
```cpp
void(entt::registry &, entt::entity, Component &);
void(entt::entity, entt::registry &, Component &);
```
Where `Component` is intuitively the type of component of interest. In other
@@ -377,7 +377,7 @@ signature of which is the same of that of the construction signal. The one of
the destruction signal is also similar, except for the `Component` parameter:
```cpp
void(entt::registry &, entt::entity);
void(entt::entity, entt::registry &);
```
This is mainly due to performance reasons. While the component is made available
@@ -632,7 +632,41 @@ separately to the elements that are part of the group and to those that are not,
effectively generating two partitions, both of which can be ordered
independently of each other.
## Multiple registries to the rescue
## Helpers
The so called _helpers_ are small classes and functions mainly designed to offer
built-in support for the most basic functionalities.<br/>
The list of helpers will grow longer as time passes and new ideas come out.
### Null entity
In `EnTT`, there exists a sort of _null entity_ made available to users that is
accessible via the `entt::null` variable.<br/>
The library guarantees that the following expression always returns false:
```cpp
registry.valid(entt::null);
```
In other terms, a registry will reject the null entity in all cases because it
isn't considered valid. It means that the null entity cannot own components for
obvious reasons.<br/>
The type of the null entity is internal and should not be used for any purpose
other than defining the null entity itself. However, there exist implicit
conversions from the null entity to identifiers of any allowed type:
```cpp
entt::entity null = entt::null;
```
Similarly, the null entity can be compared to any other identifier:
```cpp
const auto entity = registry.create();
const bool null = (entity == entt::null);
```
### Multiple registries
The use of multiple registries is quite common. Examples of use are the
separation of the UI from the simulation or the loading of different scenes in
@@ -645,36 +679,109 @@ different containers.
Once there are multiple registries available, however, a method is needed to
transfer information from one container to another and this results in the
`stomp` member function of the `registry` class.<br/>
This function allows to take an entity from a registry and copy it over another
entity in another registry (or even in place, actually making a local copy).
This function allows to take one or more entities from a registry and use them
to _stomp_ other entities in another registry (or even the same, actually making
local copies).<br/>
It opens definitely the doors to a lot of interesting features like migrating
entities between registries, prototypes, shadow registry, prefabs, shared
components without an explicit owner and copy-on-write policies among the other
things.
It opens definitely the doors to a lot of interesting features. Below is a
brief, yet incomplete list of some of them:
### Dependencies
* Prototypes or templates for high level _concepts_ to use to spawn new entities
when needed or to _apply_ a given set of components to an existing
entity.<br/>
Put aside the fact that having the prototypes separated from the simulation is
useful in many cases, they make also the codebase easier to maintain, since
updating a template is less error prone than jumping in the code to update all
the snippets copied and pasted around to initialize entities and components.
The `registry` class is designed to create short circuits between its functions
within certain limits. This allows to easily define dependencies between
different operations.<br/>
For example, the following adds (or replaces) the component `a_type` whenever
`my_type` is assigned to an entity:
* Literally _move_ entities from one registry to another (that is a copy
followed by a destroy), which can be useful for solving problems such as the
migration of entities between different scenes without loss of information.
```cpp
registry.on_construct<my_type>().connect<&entt::registry::assign_or_replace<a_type>>(registry);
```
* Even prefabs, shared instances without explicit owner and copy-on-write
policies among other things are easily achievable in this way and with the
_right_ types in use for the components.
Similarly, the code shown below removes `a_type` from an entity whenever
`my_type` is assigned to it:
* Remove entities that are distant or not visible so as to reduce the processing
time, moving them to a _cold_ registry from which they can be easily resumed
at any time.
```cpp
registry.on_construct<my_type>().connect<&entt::registry::reset<a_type>>(registry);
```
And so on. There are many things that become possible by combining multiple
containers but none is really useful without the right means to move entities
and components between registries.<br/>
Therefore, this seemed a good reason to implement such a feature.
A dependency can also be easily broken as follows:
```cpp
registry.on_construct<my_type>().disconnect<&entt::registry::assign_or_replace<a_type>>(registry);
```
There are many other types of dependencies besides those shown above. In
general, all functions that accept an entity as the first argument are good
candidates for this purpose.
### Tags
There's nothing magical about the way tags can be assigned to entities while
avoiding a performance hit at runtime. Nonetheless, the syntax can be annoying
and that's why a more user-friendly shortcut is provided to do it.<br/>
This shortcut is the alias template `entt::tag`.
If used in combination with hashed strings, it helps to use tags where types
would be required otherwise. As an example:
```cpp
registry.assign<entt::tag<"enemy"_hs>>(entity);
```
### Actor
The `actor` class is designed for those who don't feel immediately comfortable
working with components or for those who are migrating a project and want to
approach it one step at a time.
This class acts as a thin wrapper for an entity and for all its components. It's
constructed with a registry to be used behind the scenes and is in charge of the
destruction of the entity when it goes out of the scope.<br/>
An actor offers all the functionalities required to work with components, such
as the `assign` and` remove` member functions, but also `has`,` get`, `try_get`
and so on.
My advice isn't to use the `actor` class to hide entities and components behind
a more object-oriented interface. Instead, users should rely on it only where
strictly necessary. In all other cases, it's highly advisable to become familiar
with the model of `EnTT` and work directly with the registry, the views and the
groups, rather than with a tool that could introduce a performance degradation.
### 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. Later on, either `ctx` or `try_ctx` can be
used to retrieve the newly created instance and `unset` is there to literally
reset it if needed.
Example of use:
```cpp
// creates a new context variable initialized with the given values
registry.set<my_type>(42, 'c');
// gets the context variable
const auto &var = registry.ctx<my_type>();
// if in doubts, probe the registry to avoid assertions in case of errors
if(auto *ptr = registry.try_ctx<my_type>(); ptr) {
// uses the context variable associated with the registry, if any
}
// unsets the context variable
registry.unset<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. The `try_ctx`
member function returns a pointer to the context variable if it exists,
otherwise it returns a null pointer. This fits well with the `if` statement with
initializer.
## Snapshot: complete vs continuous
@@ -915,128 +1022,6 @@ the best way to do it. However, feel free to use it at your own risk.
The basic idea is to store everything in a group of queues in memory, then bring
everything back to the registry with different loaders.
## The actor class
The `actor` class is designed for those who don't feel immediately comfortable
working with components or for those who are migrating a project and want to
approach it one step at a time.
This class acts as a thin wrapper for an entity and for all its components. It's
constructed with a registry to be used behind the scenes and is in charge of the
destruction of the entity when it goes out of the scope.<br/>
An actor offers all the functionalities required to work with components, such
as the `assign` and` remove` member functions, but also `has`,` get`, `try_get`
and so on.
My advice isn't to use the `actor` class to hide entities and components behind
a more object-oriented interface. Instead, users should rely on it only where
strictly necessary. In all other cases, it's highly advisable to become familiar
with the model of `EnTT` and work directly with the registry, the views and the
groups, rather than with a tool that could introduce a performance degradation.
## Helpers
The so called _helpers_ are small classes and functions mainly designed to offer
built-in support for the most basic functionalities.<br/>
The list of helpers will grow longer as time passes and new ideas come out.
### Dependency function
A _dependency function_ is a predefined listener, actually a function template
to use to automatically assign components to an entity when a type has a
dependency on some other types.<br/>
The following adds components `a_type` and `another_type` whenever `my_type` is
assigned to an entity:
```cpp
entt::connect<a_type, another_type>(registry.on_construct<my_type>());
```
A component is assigned to an entity and thus default initialized only in case
the entity itself hasn't it yet. It means that already existent components won't
be overriden.<br/>
A dependency can easily be broken by means of the following function template:
```cpp
entt::disconnect<a_type, another_type>(registry.on_construct<my_type>());
```
### Tags
There's nothing magical about the way tags can be assigned to entities while
avoiding a performance hit at runtime. Nonetheless, the syntax can be annoying
and that's why a more user-friendly shortcut is provided to do it.<br/>
This shortcut is the alias template `entt::tag`.
If used in combination with hashed strings, it helps to use tags where types
would be required otherwise. As an example:
```cpp
registry.assign<entt::tag<"enemy"_hs>>(entity);
```
## Null entity
In `EnTT`, there exists a sort of _null entity_ made available to users that is
accessible via the `entt::null` variable.<br/>
The library guarantees that the following expression always returns false:
```cpp
registry.valid(entt::null);
```
In other terms, a registry will reject the null entity in all cases because it
isn't considered valid. It means that the null entity cannot own components for
obvious reasons.<br/>
The type of the null entity is internal and should not be used for any purpose
other than defining the null entity itself. However, there exist implicit
conversions from the null entity to identifiers of any allowed type:
```cpp
entt::entity null = entt::null;
```
Similarly, the null entity can be compared to any other identifier:
```cpp
const auto entity = registry.create();
const bool null = (entity == entt::null);
```
## 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. Later on, either `ctx` or `try_ctx` can be
used to retrieve the newly created instance and `unset` is there to literally
reset it if needed.
Example of use:
```cpp
// creates a new context variable initialized with the given values
registry.set<my_type>(42, 'c');
// gets the context variable
const auto &var = registry.ctx<my_type>();
// if in doubts, probe the registry to avoid assertions in case of errors
if(auto *ptr = registry.try_ctx<my_type>(); ptr) {
// uses the context variable associated with the registry, if any
}
// unsets the context variable
registry.unset<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. The `try_ctx`
member function returns a pointer to the context variable if it exists,
otherwise it returns a null pointer. This fits well with the `if` statement with
initializer.
# Views and Groups
First of all, it is worth answering an obvious question: why views and

View File

@@ -112,6 +112,11 @@ as_group(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<true, Entity>
/**
* @brief Dependency function prototype.
*
* @deprecated
* This function will be wiped out in a future version of the library.<br/>
* Use shortcuts and cross links between registries to achieve the same result
* in a more idiomatic way.
*
* A _dependency function_ is a built-in listener to use to automatically assign
* components to an entity when a type has a dependency on some other types.
*
@@ -120,11 +125,11 @@ as_group(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<true, Entity>
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Dependency Types of components to assign to an entity if triggered.
* @param reg A valid reference to a registry.
* @param entt A valid entity identifier.
* @param reg A valid reference to a registry.
*/
template<typename Entity, typename... Dependency>
void dependency(basic_registry<Entity> &reg, const Entity entt) {
void dependency(const Entity entt, basic_registry<Entity> &reg) {
((reg.template has<Dependency>(entt) ? void() : (reg.template assign<Dependency>(entt), void())), ...);
}
@@ -132,6 +137,11 @@ void dependency(basic_registry<Entity> &reg, const Entity entt) {
/**
* @brief Connects a dependency function to the given sink.
*
* @deprecated
* This function will be wiped out in a future version of the library.<br/>
* Use shortcuts and cross links between registries to achieve the same result
* in a more idiomatic way.
*
* A _dependency function_ is a built-in listener to use to automatically assign
* components to an entity when a type has a dependency on some other types.
*
@@ -148,7 +158,7 @@ void dependency(basic_registry<Entity> &reg, const Entity entt) {
* @param sink A sink object properly initialized.
*/
template<typename... Dependency, typename Component, typename Entity>
void connect(sink<void(basic_registry<Entity> &, const Entity, Component &)> sink) {
void connect(sink<void(const Entity, basic_registry<Entity> &, Component &)> sink) {
constexpr auto function = &dependency<Entity, Dependency...>;
sink.template connect<function>();
}
@@ -157,6 +167,11 @@ void connect(sink<void(basic_registry<Entity> &, const Entity, Component &)> sin
/**
* @brief Disconnects a dependency function from the given sink.
*
* @deprecated
* This function will be wiped out in a future version of the library.<br/>
* Use shortcuts and cross links between registries to achieve the same result
* in a more idiomatic way.
*
* A _dependency function_ is a built-in listener to use to automatically assign
* components to an entity when a type has a dependency on some other types.
*
@@ -173,7 +188,7 @@ void connect(sink<void(basic_registry<Entity> &, const Entity, Component &)> sin
* @param sink A sink object properly initialized.
*/
template<typename... Dependency, typename Component, typename Entity>
void disconnect(sink<void(basic_registry<Entity> &, const Entity, Component &)> sink) {
void disconnect(sink<void(const Entity, basic_registry<Entity> &, Component &)> sink) {
constexpr auto function = &dependency<Entity, Dependency...>;
sink.template disconnect<function>();
}

View File

@@ -173,7 +173,7 @@ class basic_observer {
template<typename... Reject, typename... Require, typename AnyOf>
struct matcher_handler<matcher<matcher<type_list<Reject...>, type_list<Require...>>, AnyOf>> {
template<std::size_t Index>
static void maybe_valid_if(basic_observer &obs, const basic_registry<Entity> &reg, const Entity entt) {
static void maybe_valid_if(basic_observer &obs, const Entity entt, const basic_registry<Entity> &reg) {
if(reg.template has<Require...>(entt) && !(reg.template has<Reject>(entt) || ...)) {
auto *comp = obs.view.try_get(entt);
(comp ? *comp : obs.view.construct(entt)) |= (1 << Index);
@@ -181,7 +181,7 @@ class basic_observer {
}
template<std::size_t Index>
static void discard_if(basic_observer &obs, const basic_registry<Entity> &, const Entity entt) {
static void discard_if(basic_observer &obs, const Entity entt) {
if(auto *value = obs.view.try_get(entt); value && !(*value &= (~(1 << Index)))) {
obs.view.destroy(entt);
}
@@ -206,7 +206,7 @@ class basic_observer {
template<typename... Reject, typename... Require, typename... NoneOf, typename... AllOf>
struct matcher_handler<matcher<matcher<type_list<Reject...>, type_list<Require...>>, type_list<NoneOf...>, type_list<AllOf...>>> {
template<std::size_t Index>
static void maybe_valid_if(basic_observer &obs, const basic_registry<Entity> &reg, const Entity entt) {
static void maybe_valid_if(basic_observer &obs, const Entity entt, const basic_registry<Entity> &reg) {
if(reg.template has<AllOf...>(entt) && !(reg.template has<NoneOf>(entt) || ...)
&& reg.template has<Require...>(entt) && !(reg.template has<Reject>(entt) || ...))
{
@@ -216,7 +216,7 @@ class basic_observer {
}
template<std::size_t Index>
static void discard_if(basic_observer &obs, const basic_registry<Entity> &, const Entity entt) {
static void discard_if(basic_observer &obs, const Entity entt) {
if(auto *value = obs.view.try_get(entt); value && !(*value &= (~(1 << Index)))) {
obs.view.destroy(entt);
}

View File

@@ -21,8 +21,8 @@ namespace entt {
*
* @deprecated
* This class will be wiped out in a future version of the library.<br/>
* Use a shadow registry and the new `registry::stomp` functionality to achieve
* the same result in a more idiomatic way.
* Use a prototype registry and the new `registry::stomp` functionality to
* achieve the same result in a more idiomatic way.
*
* A prototype is used to define a _concept_ in terms of components.<br/>
* Prototypes act as templates for those specific types of an application which

View File

@@ -76,11 +76,11 @@ class basic_registry {
decltype(auto) assign(basic_registry &registry, const Entity entt, Args &&... args) {
if constexpr(std::is_empty_v<Component>) {
storage<Entity, Component>::construct(entt);
construction.publish(registry, entt, Component{});
construction.publish(entt, registry, Component{});
return Component{std::forward<Args>(args)...};
} else {
auto &component = storage<Entity, Component>::construct(entt, std::forward<Args>(args)...);
construction.publish(registry, entt, component);
construction.publish(entt, registry, component);
return component;
}
}
@@ -94,7 +94,7 @@ class basic_registry {
if(!construction.empty()) {
std::for_each(first, last, [this, &registry](const auto entt) {
construction.publish(registry, entt, Component{});
construction.publish(entt, registry, Component{});
});
}
} else {
@@ -102,7 +102,7 @@ class basic_registry {
if(!construction.empty()) {
std::for_each(first, last, [this, &registry, component](const auto entt) mutable {
construction.publish(registry, entt, *(component++));
construction.publish(entt, registry, *(component++));
});
}
}
@@ -111,7 +111,7 @@ class basic_registry {
}
void remove(basic_registry &registry, const Entity entt) {
destruction.publish(registry, entt);
destruction.publish(entt, registry);
storage<Entity, Component>::destroy(entt);
}
@@ -119,20 +119,20 @@ class basic_registry {
decltype(auto) replace(basic_registry &registry, const Entity entt, Args &&... args) {
if constexpr(std::is_empty_v<Component>) {
ENTT_ASSERT((storage<Entity, Component>::has(entt)));
update.publish(registry, entt, Component{});
update.publish(entt, registry, Component{});
return Component{std::forward<Args>(args)...};
} else {
Component component{std::forward<Args>(args)...};
update.publish(registry, entt, component);
update.publish(entt, registry, component);
return (storage<Entity, Component>::get(entt) = std::move(component));
}
}
private:
using reference_type = std::conditional_t<std::is_empty_v<Component>, const Component &, Component &>;
sigh<void(basic_registry &, const Entity, reference_type)> construction{};
sigh<void(basic_registry &, const Entity, reference_type)> update{};
sigh<void(basic_registry &, const Entity)> destruction{};
sigh<void(const Entity, basic_registry &, reference_type)> construction{};
sigh<void(const Entity, basic_registry &, reference_type)> update{};
sigh<void(const Entity, basic_registry &)> destruction{};
};
template<typename Component>
@@ -146,7 +146,7 @@ class basic_registry {
std::tuple<pool_type<Get> *..., pool_type<Exclude> *...> cpools{};
template<typename Component>
void maybe_valid_if(const basic_registry &, const Entity entt) {
void maybe_valid_if(const Entity entt) {
if constexpr(std::disjunction_v<std::is_same<Get, Component>...>) {
if(((std::is_same_v<Component, Get> || std::get<pool_type<Get> *>(cpools)->has(entt)) && ...)
&& !(std::get<pool_type<Exclude> *>(cpools)->has(entt) || ...))
@@ -161,7 +161,7 @@ class basic_registry {
}
}
void discard_if(const basic_registry &, const Entity entt) {
void discard_if(const Entity entt) {
if(this->has(entt)) {
this->destroy(entt);
}
@@ -173,7 +173,7 @@ class basic_registry {
std::tuple<pool_type<Owned> *..., pool_type<Get> *..., pool_type<Exclude> *...> cpools{};
template<typename Component>
void maybe_valid_if(const basic_registry &, const Entity entt) {
void maybe_valid_if(const Entity entt) {
if constexpr(std::disjunction_v<std::is_same<Owned, Component>..., std::is_same<Get, Component>...>) {
if(((std::is_same_v<Component, Owned> || std::get<pool_type<Owned> *>(cpools)->has(entt)) && ...)
&& ((std::is_same_v<Component, Get> || std::get<pool_type<Get> *>(cpools)->has(entt)) && ...)
@@ -193,7 +193,7 @@ class basic_registry {
}
}
void discard_if(const basic_registry &, const Entity entt) {
void discard_if(const Entity entt) {
if(std::get<0>(cpools)->has(entt) && std::get<0>(cpools)->sparse_set<Entity>::get(entt) < this->owned) {
const auto pos = --this->owned;
(std::get<pool_type<Owned> *>(cpools)->swap(std::get<pool_type<Owned> *>(cpools)->sparse_set<Entity>::get(entt), pos), ...);
@@ -906,7 +906,7 @@ public:
* The function type for a listener is equivalent to:
*
* @code{.cpp}
* void(registry<Entity> &, Entity, Component &);
* void(Entity, registry<Entity> &, Component &);
* @endcode
*
* Listeners are invoked **after** the component has been assigned to the
@@ -937,7 +937,7 @@ public:
* The function type for a listener is equivalent to:
*
* @code{.cpp}
* void(registry<Entity> &, Entity, Component &);
* void(Entity, registry<Entity> &, Component &);
* @endcode
*
* Listeners are invoked **before** the component has been replaced. The
@@ -969,7 +969,7 @@ public:
* The function type for a listener is equivalent to:
*
* @code{.cpp}
* void(registry<Entity> &, Entity);
* void(Entity, registry<Entity> &);
* @endcode
*
* Listeners are invoked **before** the component has been removed from the

View File

@@ -15,7 +15,7 @@ struct empty_type {};
struct listener {
template<typename Component>
void incr(entt::registry &registry, entt::entity entity, const Component &) {
void incr(entt::entity entity, entt::registry &registry, const Component &) {
ASSERT_TRUE(registry.valid(entity));
ASSERT_TRUE(registry.has<Component>(entity));
last = entity;
@@ -23,7 +23,7 @@ struct listener {
}
template<typename Component>
void decr(entt::registry &registry, entt::entity entity) {
void decr(entt::entity entity, entt::registry &registry) {
ASSERT_TRUE(registry.valid(entity));
ASSERT_TRUE(registry.has<Component>(entity));
last = entity;
@@ -1524,3 +1524,32 @@ TEST(Registry, MoveOnlyComponent) {
const auto entity = registry.create();
registry.assign<std::unique_ptr<int>>(entity);
}
TEST(Registry, Dependencies) {
entt::registry registry;
const auto entity = registry.create();
registry.on_construct<int>().connect<&entt::registry::assign_or_replace<double>>(registry);
registry.on_destroy<int>().connect<&entt::registry::remove<double>>(registry);
registry.assign<double>(entity, .3);
ASSERT_FALSE(registry.has<int>(entity));
ASSERT_EQ(registry.get<double>(entity), .3);
registry.assign<int>(entity);
ASSERT_TRUE(registry.has<int>(entity));
ASSERT_EQ(registry.get<double>(entity), .0);
registry.remove<int>(entity);
ASSERT_FALSE(registry.has<int>(entity));
ASSERT_FALSE(registry.has<double>(entity));
registry.on_construct<int>().disconnect<&entt::registry::assign_or_replace<double>>(registry);
registry.on_destroy<int>().disconnect<&entt::registry::remove<double>>(registry);
registry.assign<int>(entity);
ASSERT_TRUE(registry.has<int>(entity));
ASSERT_FALSE(registry.has<double>(entity));
}