registry: in-place ::replace, the signal no longer receives an extra argument (close #406)

This commit is contained in:
Michele Caini
2020-02-15 15:54:47 +01:00
parent 60039441a1
commit e7403d8551
3 changed files with 60 additions and 52 deletions

View File

@@ -228,16 +228,10 @@ This function is overloaded and accepts also a couple of iterators in order to:
```
If an entity already has the given component, the `replace` member function
template can be used to replace it:
template can be used to edit it in-place:
```cpp
registry.replace<position>(entity, 0., 0.);
// ...
auto &velocity = registry.replace<velocity>(entity);
vel.dx = 0.;
vel.dy = 0.;
registry.replace<position>(entity, [](auto &pos) { pos.x = pos.y = 0.; });
```
When it's unknown whether an entity already owns an instance of a component,
@@ -245,19 +239,13 @@ When it's unknown whether an entity already owns an instance of a component,
```cpp
registry.assign_or_replace<position>(entity, 0., 0.);
// ...
auto &velocity = registry.assign_or_replace<velocity>(entity);
vel.dx = 0.;
vel.dy = 0.;
```
This is a slightly faster alternative for the following snippet:
```cpp
if(registry.has<comp>(entity)) {
registry.replace<velocity>(entity, 0., 0.);
registry.replace<velocity>(entity, [](auto &vel) { vel.dx = vel.dy = 0.; });
} else {
registry.assign<velocity>(entity, 0., 0.);
}
@@ -352,36 +340,24 @@ To be notified when components are destroyed, use the `on_destroy` member
function instead. Finally, the `on_replace` member function will return a sink
to which to connect listeners to observe changes.
The function type of a listener for the construction and destruction signals
should be equivalent to the following:
The function type of a listener should be equivalent to the following:
```cpp
void(entt::registry &, entt::entity);
```
In both cases, listeners are provided with the registry that triggered the
notification and the entity affected by the change.<br/>
The function type of a listener that observes changes to components is slightly
different instead:
```cpp
void(entt::registry &, entt::entity, Component &);
```
In this case, `Component` is intuitively the type of component of interest. The
extra argument is required because the registry cannot store and therefore
return both the instances of the given type for an entity.
In all cases, listeners are provided with the registry that triggered the
notification and the involved entity.
Note also that:
* Listeners for the construction signal are invoked **after** components have
* Listeners for the construction signals are invoked **after** components have
been assigned to entities.
* Listeners designed to observe changes are invoked **before** components have
been replaced and therefore before newly created instances have been assigned
to entities.
* Listeners designed to observe changes are invoked **after** components have
been updated.
* Listeners for the destruction signal are invoked **before** components have
* Listeners for the destruction signals are invoked **before** components have
been removed from entities.
There are also some limitations on what a listener can and cannot do:
@@ -390,7 +366,7 @@ There are also some limitations on what a listener can and cannot do:
listener should be avoided. It can lead to undefined behavior in some cases.
* Removing the component from within the body of a listener that observes the
construction or replacement of instances of a given type isn't allowed.
construction or update of instances of a given type isn't allowed.
* Assigning and removing components from within the body of a listener that
observes the destruction of instances of a given type should be avoided. It
@@ -646,8 +622,9 @@ A dependency can also be easily broken as follows:
registry.on_construct<my_type>().disconnect<&entt::registry::assign_or_replace<a_type>>();
```
There are many other types of dependencies. In general, all functions that
accept an entity as the first argument are good candidates for this purpose.
There are many other types of dependencies. In general, most of the functions
that accept an entity as the first argument are good candidates for this
purpose.
### Tags

View File

@@ -98,17 +98,16 @@ class basic_registry {
}
}
template<typename... Args>
auto replace(basic_registry &owner, const Entity entt, Args &&... args) -> decltype(this->get(entt)) {
Component component{std::forward<Args>(args)...};
update.publish(owner, entt, component);
return (this->get(entt) = std::move(component));
template<typename... Func>
void replace(basic_registry &owner, const Entity entt, Func &&... func) {
(std::forward<Func>(func)(this->get(entt)), ...);
update.publish(owner, entt);
}
private:
sigh<void(basic_registry &, const Entity)> construction{};
sigh<void(basic_registry &, const Entity)> destruction{};
sigh<void(basic_registry &, const Entity, decltype(std::declval<storage<Entity, Component>>().get({})))> update{};
sigh<void(basic_registry &, const Entity)> update{};
};
struct pool_data {
@@ -717,12 +716,39 @@ public:
decltype(auto) assign_or_replace(const entity_type entity, Args &&... args) {
ENTT_ASSERT(valid(entity));
auto &cpool = assure<Component>();
return cpool.has(entity) ? cpool.replace(*this, entity, std::forward<Args>(args)...) : cpool.assign(*this, entity, std::forward<Args>(args)...);
return cpool.has(entity)
? (cpool.replace(*this, entity, [args = std::forward_as_tuple(std::forward<Args>(args)...)](auto &&component) { component = std::make_from_tuple<Component>(std::move(args)); }), cpool.get(entity))
: cpool.assign(*this, entity, std::forward<Args>(args)...);
}
/**
* @brief Replaces the given component for an entity.
*
* The signature of the functions should be equivalent to the following:
*
* @code{.cpp}
* void(Component &);
* @endcode
*
* Temporary objects are returned for empty types though. Capture them by
* copy or by const reference if needed.
*
* @tparam Component Type of component to replace.
* @tparam Func Types of the function objects to invoke.
* @param entity A valid entity identifier.
* @param func Valid function objects.
*/
template<typename Component, typename... Func>
auto replace(const entity_type entity, Func &&... func)
-> decltype(std::enable_if_t<sizeof...(Func) != 0>(), (func(std::declval<Component &>()), ...), void()) {
ENTT_ASSERT(valid(entity));
assure<Component>().replace(*this, entity, std::forward<Func>(func)...);
}
/**
* @copybrief replace
*
* A new instance of the given component is created and initialized with the
* arguments provided (the component must have a proper constructor or be of
* aggregate type). Then the component is assigned to the given entity.
@@ -738,12 +764,17 @@ public:
* @tparam Args Types of arguments to use to construct the component.
* @param entity A valid entity identifier.
* @param args Parameters to use to initialize the component.
* @return A reference to the newly created component.
* @return A reference to the component being replaced.
*/
template<typename Component, typename... Args>
decltype(auto) replace(const entity_type entity, Args &&... args) {
ENTT_ASSERT(valid(entity));
return assure<Component>().replace(*this, entity, std::forward<Args>(args)...);
[[deprecated("use in-place replace instead")]]
auto replace(const entity_type entity, Args &&... args)
-> decltype(Component{std::forward<Args>(args)...}, assure<Component>().get(entity)) {
replace<Component>(entity, [args = std::forward_as_tuple(std::forward<Args>(args)...)](auto &&component) {
component = std::make_from_tuple<Component>(std::move(args));
});
return assure<Component>().get(entity);
}
/**

View File

@@ -177,7 +177,7 @@ TEST(Registry, Functionalities) {
ASSERT_NE(&registry.get<int>(e0), &registry.get<int>(e2));
ASSERT_NE(&registry.get<char>(e0), &registry.get<char>(e2));
ASSERT_NO_THROW(registry.replace<int>(e0, 0));
ASSERT_NO_THROW(registry.replace<int>(e0, [](auto &instance) { instance = 0; }));
ASSERT_EQ(registry.get<int>(e0), 0);
ASSERT_NO_THROW(registry.assign_or_replace<int>(e0, 1));
@@ -1431,8 +1431,8 @@ TEST(Registry, Stamp) {
ASSERT_EQ(registry.get<int>(entity), 3);
ASSERT_EQ(registry.get<char>(entity), 'c');
registry.replace<int>(prototype, 42);
registry.replace<char>(prototype, 'a');
registry.replace<int>(prototype, [](auto &instance) { instance = 42; });
registry.replace<char>(prototype, [](auto &instance) { instance = 'a'; });
registry.stamp(entity, registry, prototype);
ASSERT_EQ(registry.get<int>(entity), 42);
@@ -1454,7 +1454,7 @@ TEST(Registry, StampExclude) {
ASSERT_FALSE(registry.has<char>(entity));
ASSERT_EQ(registry.get<int>(entity), 3);
registry.replace<int>(prototype, 42);
registry.replace<int>(prototype, [](auto &instance) { instance = 42; });
registry.stamp(entity, registry, prototype, entt::exclude<int>);
ASSERT_TRUE((registry.has<int, char, empty_type>(entity)));