delegate: support for curried functions
This commit is contained in:
2
TODO
2
TODO
@@ -7,7 +7,6 @@
|
||||
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
|
||||
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
|
||||
* runner proposal: https://en.wikipedia.org/wiki/Fork%E2%80%93join_model https://slide-rs.github.io/specs/03_dispatcher.html
|
||||
* deep copy of a registry (or use the snapshot stuff to copy components and keep intact ids at least)
|
||||
* is it possible to iterate all the components assigned to an entity through a common base class?
|
||||
* can we do more for shared libraries? who knows... see #144
|
||||
* work stealing job system (see #100)
|
||||
@@ -23,4 +22,3 @@
|
||||
* monostate: make constraint trivially copyable due to atomic optional
|
||||
* travis + windows is now available, try it
|
||||
* events on replace, so that one can track updated components? indagate impact
|
||||
* allow to bind values to delegate on connect with free functions (sbo + check size on costruction to guarantee a zero allocation abstraction)
|
||||
|
||||
@@ -1034,7 +1034,7 @@ Persistent views are used mainly to iterate multiple components at once:
|
||||
auto view = registry.persistent_view<position, velocity>();
|
||||
```
|
||||
|
||||
Moreover, filters can be applied to a persistent view to some extents:
|
||||
Filtering entities by components is also supported:
|
||||
|
||||
```cpp
|
||||
auto view = registry.persistent_view<position, velocity>(entt::type_list<renderable>);
|
||||
@@ -1043,8 +1043,8 @@ auto view = registry.persistent_view<position, velocity>(entt::type_list<rendera
|
||||
In this case, the view will return all the entities that have both components
|
||||
`position` and `velocity` but don't have component `renderable`.<br/>
|
||||
Exclusive filters (ie the entities that have either `position` or `velocity`)
|
||||
aren't supported for performance reasons. Similarly, a filter cannot be applied
|
||||
to a persistent view with an empty template parameters list.
|
||||
aren't directly supported for performance reasons. Similarly, a filter cannot be
|
||||
applied to a persistent view with an empty template parameters list.
|
||||
|
||||
There is no need to store views around for they are extremely cheap to
|
||||
construct, even though they can be copied without problems and reused freely. In
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* [Introduction](#introduction)
|
||||
* [Signals](#signals)
|
||||
* [Delegate](#delegate)
|
||||
* [Currying and free functions](#currying-and-free-functions)
|
||||
* [Event dispatcher](#event-dispatcher)
|
||||
* [Event emitter](#event-emitter)
|
||||
<!--
|
||||
@@ -213,6 +214,39 @@ Probably too much small and pretty poor of functionalities, but the delegate
|
||||
class can help in a lot of cases and it has shown that it is worth keeping it
|
||||
within the library.
|
||||
|
||||
## Currying and free functions
|
||||
|
||||
The delegate class comes with a rather obscure feature to be considered as an
|
||||
_advanced mode_, that is its limited support for curried free functions.
|
||||
|
||||
Consider the following definition:
|
||||
|
||||
```cpp
|
||||
entt::delegate<void(int)> delegate{};
|
||||
```
|
||||
|
||||
As shown previously, it accepts functions having type `void(int)`. However, we
|
||||
can do something more in this case, because of how the delegate class is
|
||||
implemented internally (that is something that goes beyond the purposes of this
|
||||
document).<br/>
|
||||
In particular, the delegate accepts also functions having type `void(T, int)`,
|
||||
as long as `sizeof(T)` is lower than or equal to `sizeof(void *)`. The first
|
||||
parameter is stored directly by the delegate class and passed to the connected
|
||||
function when needed.
|
||||
|
||||
In other terms, this works as well with the above definition:
|
||||
|
||||
```cpp
|
||||
void g(char c, int i) { /* ... */ }
|
||||
|
||||
delegate.connect<&g>('c');
|
||||
delegate(42);
|
||||
```
|
||||
|
||||
In this case, the function `g` is invoked with parameters `'c'` and `42`.
|
||||
However, the function type of the delegate is still `void(int)`, mainly because
|
||||
this is also the signature of its function call operator.
|
||||
|
||||
# Event dispatcher
|
||||
|
||||
The event dispatcher class is designed so as to be used in a loop. It allows
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define ENTT_SIGNAL_DELEGATE_HPP
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
@@ -65,21 +67,22 @@ class delegate;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to send around functions and member functions.
|
||||
* @brief Utility class to use to send around functions and member functions.
|
||||
*
|
||||
* Unmanaged delegate for function pointers and member functions. Users of this
|
||||
* class are in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* A delegate can be used as general purpose invoker with no memory overhead for
|
||||
* free functions and member functions provided along with an instance on which
|
||||
* to invoke them.
|
||||
* to invoke them. It comes also with limited support for curried functions.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class delegate<Ret(Args...)> final {
|
||||
using proto_fn_type = Ret(const void *, Args...);
|
||||
using storage_type = std::aligned_storage_t<sizeof(void *), alignof(void *)>;
|
||||
using proto_fn_type = Ret(storage_type &, Args...);
|
||||
|
||||
public:
|
||||
/*! @brief Function type of the delegate. */
|
||||
@@ -87,11 +90,13 @@ public:
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
delegate() ENTT_NOEXCEPT
|
||||
: fn{nullptr}, ref{nullptr}
|
||||
{}
|
||||
: storage{}, fn{nullptr}
|
||||
{
|
||||
new (&storage) void *{nullptr};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs a delegate and binds a free function to it.
|
||||
* @brief Constructs a delegate and connects a free function to it.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<auto Function>
|
||||
@@ -102,12 +107,12 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs a delegate and binds a member function to it.
|
||||
* @brief Constructs a delegate and connects a member function to it.
|
||||
* @tparam Member Member function to connect to the delegate.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<auto Member, typename Class>
|
||||
template<auto Member, typename Class, typename = std::enable_if_t<std::is_member_function_pointer_v<decltype(Member)>>>
|
||||
delegate(connect_arg_t<Member>, Class *instance) ENTT_NOEXCEPT
|
||||
: delegate{}
|
||||
{
|
||||
@@ -115,60 +120,73 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Binds a free function to a delegate.
|
||||
* @brief Connects a free function to a delegate.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<auto Function>
|
||||
void connect() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Function), Args...>);
|
||||
ref = nullptr;
|
||||
new (&storage) void *{nullptr};
|
||||
|
||||
fn = [](const void *, Args... args) -> Ret {
|
||||
fn = [](storage_type &, Args... args) -> Ret {
|
||||
return std::invoke(Function, args...);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a delegate.
|
||||
* @brief Connects a member function for a given instance or a curried free
|
||||
* function to a delegate.
|
||||
*
|
||||
* The delegate isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* delegate.
|
||||
* When used to connect a member function, the delegate isn't responsible
|
||||
* for the connected object. Users must guarantee that the lifetime of the
|
||||
* instance overcomes the one of the delegate.<br/>
|
||||
* When used to connect a curried free function, the linked value must be at
|
||||
* least copyable and such that its size is lower than or equal to the one
|
||||
* of a `void *`. It means that all primitive types are accepted as well as
|
||||
* pointers. Moreover, the signature of the free function must be such that
|
||||
* the value is the first argument before the ones used to define the
|
||||
* delegate itself.
|
||||
*
|
||||
* @tparam Member Member function to connect to the delegate.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
* @tparam Candidate Member function or curried free function to connect to
|
||||
* the delegate.
|
||||
* @tparam Type Type of class to which the member function belongs or type
|
||||
* of value used for currying.
|
||||
* @param value_or_instance A valid pointer to an instance of class type or
|
||||
* the value to use for currying.
|
||||
*/
|
||||
template<auto Member, typename Class>
|
||||
void connect(Class *instance) ENTT_NOEXCEPT {
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Member), Class, Args...>);
|
||||
ref = instance;
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type value_or_instance) ENTT_NOEXCEPT {
|
||||
static_assert(sizeof(Type) <= sizeof(void *));
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type, Args...>);
|
||||
new (&storage) Type{value_or_instance};
|
||||
|
||||
fn = [](const void *instance, Args... args) -> Ret {
|
||||
if constexpr(std::is_const_v<Class>) {
|
||||
return std::invoke(Member, static_cast<Class *>(instance), args...);
|
||||
} else {
|
||||
return std::invoke(Member, static_cast<Class *>(const_cast<void *>(instance)), args...);
|
||||
}
|
||||
fn = [](storage_type &storage, Args... args) -> Ret {
|
||||
Type value_or_instance = *reinterpret_cast<Type *>(&storage);
|
||||
return std::invoke(Candidate, value_or_instance, args...);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a delegate.
|
||||
*
|
||||
* After a reset, a delegate can be safely invoked with no effect.
|
||||
* After a reset, a delegate cannot be invoked anymore.
|
||||
*/
|
||||
void reset() ENTT_NOEXCEPT {
|
||||
ref = nullptr;
|
||||
new (&storage) void *{nullptr};
|
||||
fn = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the instance bound to a delegate, if any.
|
||||
* @return An opaque pointer to the instance bound to the delegate, if any.
|
||||
* @brief Returns the instance linked to a delegate, if any.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an instance returned by a delegate that doesn't contain
|
||||
* a pointer to a member function results in undefined behavior.
|
||||
*
|
||||
* @return An opaque pointer to the instance linked to the delegate, if any.
|
||||
*/
|
||||
const void * instance() const ENTT_NOEXCEPT {
|
||||
return ref;
|
||||
return *reinterpret_cast<const void **>(&storage);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +204,8 @@ public:
|
||||
* @return The value returned by the underlying function.
|
||||
*/
|
||||
Ret operator()(Args... args) const {
|
||||
return fn(ref, args...);
|
||||
assert(fn);
|
||||
return fn(storage, args...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,27 +219,23 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @param other Delegate with which to compare.
|
||||
* @return True if the two delegates are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
|
||||
return ref == other.ref && fn == other.fn;
|
||||
const char *lhs = reinterpret_cast<const char *>(&storage);
|
||||
const char *rhs = reinterpret_cast<const char *>(&other.storage);
|
||||
return fn == other.fn && std::equal(lhs, lhs + sizeof(storage_type), rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable storage_type storage;
|
||||
proto_fn_type *fn;
|
||||
const void *ref;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid delegate object.
|
||||
|
||||
@@ -5,6 +5,10 @@ int delegate_function(const int &i) {
|
||||
return i*i;
|
||||
}
|
||||
|
||||
int curried_function(int i, int j) {
|
||||
return i+j;
|
||||
}
|
||||
|
||||
struct delegate_functor {
|
||||
int operator()(int i) {
|
||||
return i+i;
|
||||
@@ -64,6 +68,7 @@ TEST(Delegate, Comparison) {
|
||||
entt::delegate<int(int)> lhs;
|
||||
entt::delegate<int(int)> rhs;
|
||||
delegate_functor functor;
|
||||
delegate_functor other;
|
||||
|
||||
ASSERT_EQ(lhs, entt::delegate<int(int)>{});
|
||||
ASSERT_FALSE(lhs != rhs);
|
||||
@@ -98,6 +103,13 @@ TEST(Delegate, Comparison) {
|
||||
ASSERT_TRUE(lhs == rhs);
|
||||
ASSERT_EQ(lhs, rhs);
|
||||
|
||||
lhs.connect<&delegate_functor::operator()>(&other);
|
||||
|
||||
ASSERT_EQ(lhs, (entt::delegate<int(int)>{entt::connect_arg<&delegate_functor::operator()>, &other}));
|
||||
ASSERT_TRUE(lhs != rhs);
|
||||
ASSERT_FALSE(lhs == rhs);
|
||||
ASSERT_NE(lhs, rhs);
|
||||
|
||||
lhs.reset();
|
||||
|
||||
ASSERT_EQ(lhs, (entt::delegate<int(int)>{}));
|
||||
@@ -181,3 +193,11 @@ TEST(Delegate, ConstInstance) {
|
||||
ASSERT_FALSE(delegate);
|
||||
ASSERT_EQ(delegate, entt::delegate<int(int)>{});
|
||||
}
|
||||
|
||||
TEST(Delegate, CurriedFunction) {
|
||||
entt::delegate<int(int)> delegate;
|
||||
delegate.connect<&curried_function>(3);
|
||||
|
||||
ASSERT_TRUE(delegate);
|
||||
ASSERT_EQ(delegate(1), 4);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user