organizer: added new type to entt/entity
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
* [Invoke](#invoke)
|
||||
* [Handle](#handle)
|
||||
* [Context variables](#context-variables)
|
||||
* [Organizer](#organizer)
|
||||
* [Meet the runtime](#meet-the-runtime)
|
||||
* [Cloning a registry](#cloning-a-registry)
|
||||
* [Stamping an entity](#stamping-an-entity)
|
||||
@@ -741,6 +742,140 @@ 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.
|
||||
|
||||
### Organizer
|
||||
|
||||
The `organizer` class template offers minimal support (but sufficient in many
|
||||
cases) for creating an execution graph from functions and their requirements on
|
||||
resources.<br/>
|
||||
The resulting tasks aren't executed in any case. This isn't the goal of this
|
||||
tool. Instead, they are returned to the user in the form of a graph that allows
|
||||
for safe execution.
|
||||
|
||||
The functions are added in order of execution to the organizer. Free functions
|
||||
and member functions are supported as template parameters, however there is also
|
||||
the possibility to pass pointers to free functions or decayed lambdas as
|
||||
parameters to the `emplace` member function:
|
||||
|
||||
```cpp
|
||||
entt::organizer organizer;
|
||||
|
||||
// adds a free function to the organizer
|
||||
organizer.emplace<&free_function>();
|
||||
|
||||
// adds a member function and an instance on which to invoke it to the organizer
|
||||
clazz instance;
|
||||
organizer.emplace<&clazz::member_function>(&instance);
|
||||
|
||||
// adds a decayed lambda directly
|
||||
organizer.emplace(+[](const void *, entt::registry &) { /* ... */ });
|
||||
```
|
||||
|
||||
As for free functions and member functions, these are the parameters that can be
|
||||
presented by their function types and that will be correctly handled:
|
||||
|
||||
* A possibly constant reference to a registry. The one passed to the task when
|
||||
it's run will also be passed to the function as-is.
|
||||
|
||||
* An `entt::view` with any possible combination of types. It will be created
|
||||
from the registry passed to the task and supplied directly to the function.
|
||||
|
||||
* A possibly constant reference to any type `T`. It will be interpreted as
|
||||
context variable, which will be created within the registry and passed to the
|
||||
function.
|
||||
|
||||
The function type for free functions and decayed lambdas passed as parameters to
|
||||
`emplace` is `void(const void *, entt::registry &)` instead. The registry is the
|
||||
same as provided to the task. The first parameter is an optional pointer to user
|
||||
defined data to provide upon registration:
|
||||
|
||||
```cpp
|
||||
clazz instance;
|
||||
organizer.emplace(+[](const void *, entt::registry &) { /* ... */ }, &instance);
|
||||
```
|
||||
|
||||
In all cases, it's also possible to associate a name with the task when creating
|
||||
it. For example:
|
||||
|
||||
```cpp
|
||||
organizer.emplace<&free_function>("func");
|
||||
```
|
||||
|
||||
When a function of any type is registered with the organizer, everything it
|
||||
accesses is considered a _resource_ (views are _unpacked_ and their types are
|
||||
treated as resources). The _constness_ of the type also dictates its access mode
|
||||
(RO/RW). In turn, this affects the resulting graph, since it influences the
|
||||
possibility of launching tasks in parallel.<br/>
|
||||
As for the registry, if a function doesn't explicitly request it or requires a
|
||||
constant reference to it, it's considered a read-only access. Otherwise, it's
|
||||
considered as read-write access. All functions will still have the registry
|
||||
among their resources.
|
||||
|
||||
When registering a function, users can also require resources that aren't in the
|
||||
list of parameters of the function itself. These are declared as template
|
||||
parameters:
|
||||
|
||||
```cpp
|
||||
organizer.emplace<&free_function, position, velocity>("func");
|
||||
```
|
||||
|
||||
Similarly, users can override the access mode of a type again via template
|
||||
parameters:
|
||||
|
||||
```cpp
|
||||
organizer.emplace<&free_function, const renderable>("func");
|
||||
```
|
||||
|
||||
In this case, even if `renderable` appears among the parameters of the function
|
||||
as not constant, it will be treated as constant as regards the generation of the
|
||||
task graph.
|
||||
|
||||
To generate the task graph, the organizer offers the `graph` member function:
|
||||
|
||||
```cpp
|
||||
std::vector<entt::organizer::vertex> graph = organizer.graph();
|
||||
```
|
||||
|
||||
The graph is returned in the form of an adjacency list. Each vertex offers the
|
||||
following features:
|
||||
|
||||
* `ro_count` and `rw_count`: they return the number of resources accessed in
|
||||
read-only or read-write mode.
|
||||
|
||||
* `ro_dependency` and `rw_dependency`: useful for retrieving the type info
|
||||
objects associated with the parameters of the underlying function.
|
||||
|
||||
* `top_level`: indicates whether a node is a top level one, that is, it has no
|
||||
entering edges.
|
||||
|
||||
* `info`: returns the type info object associated with the underlying function.
|
||||
|
||||
* `name`: returns the name associated with the given vertex if any, a null
|
||||
pointer otherwise.
|
||||
|
||||
* `callback`: a pointer to the function to execute and whose function type is
|
||||
`void(const void *, entt::registry &)`.
|
||||
|
||||
* `data`: optional data to provide to the callback.
|
||||
|
||||
* `children`: the vertices reachable from the given node, in the form of indices
|
||||
within the adjacency list.
|
||||
|
||||
Since the creation of pools and resources within the registry isn't necessarily
|
||||
thread safe, each vertex also offers a `prepare` function which can be called to
|
||||
setup a registry for execution with the created graph:
|
||||
|
||||
```cpp
|
||||
auto graph = organizer.graph();
|
||||
entt::registry registry;
|
||||
|
||||
for(auto &&node: graph) {
|
||||
node.prepare(registry);
|
||||
}
|
||||
```
|
||||
|
||||
The actual scheduling of the tasks is the responsibility of the user, who can
|
||||
use the preferred tool.
|
||||
|
||||
## Meet the runtime
|
||||
|
||||
Type identifiers are stable in `EnTT` during executions and most of the times
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
template <typename>
|
||||
template<typename>
|
||||
class basic_registry;
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ template<typename>
|
||||
class basic_observer;
|
||||
|
||||
|
||||
template<typename>
|
||||
class basic_organizer;
|
||||
|
||||
|
||||
template <typename>
|
||||
struct basic_actor;
|
||||
|
||||
@@ -60,6 +64,10 @@ using registry = basic_registry<entity>;
|
||||
using observer = basic_observer<entity>;
|
||||
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using organizer = basic_organizer<entity>;
|
||||
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using handle = basic_handle<entity>;
|
||||
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
#ifndef ENTT_ENTITY_ORGANIZER_HPP
|
||||
#define ENTT_ENTITY_ORGANIZER_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../core/type_info.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "helper.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename>
|
||||
struct is_view: std::false_type {};
|
||||
|
||||
template<typename Entity, typename... Exclude, typename... Component>
|
||||
struct is_view<basic_view<Entity, exclude_t<Exclude...>, Component...>>: std::true_type {};
|
||||
|
||||
template<typename Type>
|
||||
inline constexpr bool is_view_v = is_view<Type>::value;
|
||||
|
||||
|
||||
template<typename Type, typename Override>
|
||||
struct unpack_type {
|
||||
using ro = std::conditional_t<
|
||||
type_list_contains_v<Override, std::add_const_t<Type>> || (std::is_const_v<Type> && !type_list_contains_v<Override, std::remove_const_t<Type>>),
|
||||
type_list<std::remove_const_t<Type>>,
|
||||
type_list<>
|
||||
>;
|
||||
|
||||
using rw = std::conditional_t<
|
||||
type_list_contains_v<Override, std::remove_const_t<Type>> || (!std::is_const_v<Type> && !type_list_contains_v<Override, std::add_const_t<Type>>),
|
||||
type_list<Type>,
|
||||
type_list<>
|
||||
>;
|
||||
};
|
||||
|
||||
template<typename Entity, typename... Override>
|
||||
struct unpack_type<basic_registry<Entity>, type_list<Override...>> {
|
||||
using ro = type_list<>;
|
||||
using rw = type_list<>;
|
||||
};
|
||||
|
||||
template<typename Entity, typename... Override>
|
||||
struct unpack_type<const basic_registry<Entity>, type_list<Override...>>
|
||||
: unpack_type<basic_registry<Entity>, type_list<Override...>>
|
||||
{};
|
||||
|
||||
template<typename Entity, typename... Exclude, typename... Component, typename... Override>
|
||||
struct unpack_type<basic_view<Entity, exclude_t<Exclude...>, Component...>, type_list<Override...>> {
|
||||
using ro = type_list_cat_t<type_list<Exclude...>, typename unpack_type<Component, type_list<Override...>>::ro...>;
|
||||
using rw = type_list_cat_t<typename unpack_type<Component, type_list<Override...>>::rw...>;
|
||||
};
|
||||
|
||||
template<typename Entity, typename... Exclude, typename... Component, typename... Override>
|
||||
struct unpack_type<const basic_view<Entity, exclude_t<Exclude...>, Component...>, type_list<Override...>>
|
||||
: unpack_type<basic_view<Entity, exclude_t<Exclude...>, Component...>, type_list<Override...>>
|
||||
{};
|
||||
|
||||
|
||||
template<typename, typename>
|
||||
struct resource;
|
||||
|
||||
template<typename... Args, typename... Req>
|
||||
struct resource<type_list<Args...>, type_list<Req...>> {
|
||||
using args = type_list<std::remove_const_t<Args>...>;
|
||||
using ro = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::ro..., typename unpack_type<Req, type_list<>>::ro...>;
|
||||
using rw = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::rw..., typename unpack_type<Req, type_list<>>::rw...>;
|
||||
};
|
||||
|
||||
|
||||
template<typename... Req, typename Ret, typename... Args>
|
||||
resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> to_resource(Ret(*)(Args...));
|
||||
|
||||
template<typename... Req, typename Ret, typename Class, typename... Args>
|
||||
resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> to_resource(Ret(Class:: *)(Args...));
|
||||
|
||||
template<typename... Req, typename Ret, typename Class, typename... Args>
|
||||
resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> to_resource(Ret(Class:: *)(Args...) const);
|
||||
|
||||
template<typename... Req>
|
||||
resource<type_list<>, type_list<Req...>> to_resource();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class for creating a static task graph.
|
||||
*
|
||||
* This class offers minimal support (but sufficient in many cases) for creating
|
||||
* an execution graph from functions and their requirements on resources.<br/>
|
||||
* Note that the resulting tasks aren't executed in any case. This isn't the
|
||||
* goal of the tool. Instead, they are returned to the user in the form of a
|
||||
* graph that allows for safe execution.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class basic_organizer final {
|
||||
using callback_type = void(const void *, entt::basic_registry<Entity> &);
|
||||
using prepare_type = void(entt::basic_registry<Entity> &);
|
||||
using dependency_type = std::size_t(const bool, type_info *, const std::size_t);
|
||||
|
||||
struct vertex_data final {
|
||||
std::size_t ro_count{};
|
||||
std::size_t rw_count{};
|
||||
const char *name{};
|
||||
const void *payload{};
|
||||
callback_type *callback{};
|
||||
dependency_type *dependency;
|
||||
prepare_type *prepare{};
|
||||
type_info info{};
|
||||
};
|
||||
|
||||
template<typename Type>
|
||||
[[nodiscard]] static decltype(auto) extract(basic_registry<Entity> ®) {
|
||||
if constexpr(std::is_same_v<Type, basic_registry<Entity>>) {
|
||||
return reg;
|
||||
} else if constexpr(internal::is_view_v<Type>) {
|
||||
return as_view{reg};
|
||||
} else {
|
||||
return reg.template ctx_or_set<std::remove_reference_t<Type>>();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
[[nodiscard]] static auto to_args(basic_registry<Entity> ®, type_list<Args...>) {
|
||||
return std::tuple<decltype(extract<Args>(reg))...>(extract<Args>(reg)...);
|
||||
}
|
||||
|
||||
template<typename... Type>
|
||||
static std::size_t fill_dependencies(type_list<Type...>, type_info *buffer, const std::size_t count) {
|
||||
if constexpr(sizeof...(Type) == 0u) {
|
||||
return {};
|
||||
} else {
|
||||
type_info info[sizeof...(Type)]{type_id<Type>()...};
|
||||
const auto length = std::min(count, sizeof...(Type));
|
||||
std::copy_n(info, length, buffer);
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... RO, typename... RW>
|
||||
void track_dependencies(std::size_t index, const bool requires_registry, type_list<RO...>, type_list<RW...>) {
|
||||
dependencies[type_hash<basic_registry<Entity>>::value()].emplace_back(index, requires_registry && (sizeof...(RO) + sizeof...(RW) == 0u));
|
||||
(dependencies[type_hash<RO>::value()].emplace_back(index, false), ...);
|
||||
(dependencies[type_hash<RW>::value()].emplace_back(index, true), ...);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<bool> adjacency_matrix() {
|
||||
const auto length = vertices.size();
|
||||
std::vector<bool> edges(length * length, false);
|
||||
|
||||
// creates the ajacency matrix
|
||||
for(const auto &deps: dependencies) {
|
||||
const auto last = deps.second.cend();
|
||||
auto it = deps.second.cbegin();
|
||||
|
||||
while(it != last) {
|
||||
if(it->second) {
|
||||
// rw item
|
||||
if(auto curr = it++; it != last) {
|
||||
if(it->second) {
|
||||
edges[curr->first * length + it->first] = true;
|
||||
} else {
|
||||
if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) {
|
||||
for(; it != next; ++it) {
|
||||
edges[curr->first * length + it->first] = true;
|
||||
edges[it->first * length + next->first] = true;
|
||||
}
|
||||
} else {
|
||||
for(; it != next; ++it) {
|
||||
edges[curr->first * length + it->first] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ro item, possibly only on first iteration
|
||||
if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) {
|
||||
for(; it != next; ++it) {
|
||||
edges[it->first * length + next->first] = true;
|
||||
}
|
||||
} else {
|
||||
it = last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// computes the transitive closure
|
||||
for(std::size_t vk{}; vk < length; ++vk) {
|
||||
for(std::size_t vi{}; vi < length; ++vi) {
|
||||
for(std::size_t vj{}; vj < length; ++vj) {
|
||||
edges[vi * length + vj] = edges[vi * length + vj] || (edges[vi * length + vk] && edges[vk * length + vj]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applies the transitive reduction
|
||||
for(std::size_t vert{}; vert < length; ++vert) {
|
||||
edges[vert * length + vert] = false;
|
||||
}
|
||||
|
||||
for(std::size_t vj{}; vj < length; ++vj) {
|
||||
for(std::size_t vi{}; vi < length; ++vi) {
|
||||
if(edges[vi * length + vj]) {
|
||||
for(std::size_t vk{}; vk < length; ++vk) {
|
||||
if(edges[vj * length + vk]) {
|
||||
edges[vi * length + vk] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Raw task function type. */
|
||||
using function_type = callback_type;
|
||||
|
||||
/*! @brief Vertex type of a task graph defined as an adjacency list. */
|
||||
struct vertex {
|
||||
/**
|
||||
* @brief Constructs a vertex of the task graph.
|
||||
* @param vtype True if the vertex is a top-level one, false otherwise.
|
||||
* @param data The data associated with the vertex.
|
||||
* @param edges The indices of the children in the adjacency list.
|
||||
*/
|
||||
vertex(const bool vtype, vertex_data data, std::vector<std::size_t> edges)
|
||||
: is_top_level{vtype},
|
||||
node{std::move(data)},
|
||||
reachable{std::move(edges)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Fills a buffer with the type info objects for the writable
|
||||
* resources of a vertex.
|
||||
* @param buffer A buffer pre-allocated by the user.
|
||||
* @param length The length of the user-supplied buffer.
|
||||
* @return The number of type info objects written to the buffer.
|
||||
*/
|
||||
size_type ro_dependency(type_info *buffer, const std::size_t length) const ENTT_NOEXCEPT {
|
||||
return node.dependency(false, buffer, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fills a buffer with the type info objects for the read-only
|
||||
* resources of a vertex.
|
||||
* @param buffer A buffer pre-allocated by the user.
|
||||
* @param length The length of the user-supplied buffer.
|
||||
* @return The number of type info objects written to the buffer.
|
||||
*/
|
||||
size_type rw_dependency(type_info *buffer, const std::size_t length) const ENTT_NOEXCEPT {
|
||||
return node.dependency(true, buffer, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of read-only resources of a vertex.
|
||||
* @return The number of read-only resources of the vertex.
|
||||
*/
|
||||
size_type ro_count() const ENTT_NOEXCEPT {
|
||||
return node.ro_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of writable resources of a vertex.
|
||||
* @return The number of writable resources of the vertex.
|
||||
*/
|
||||
size_type rw_count() const ENTT_NOEXCEPT {
|
||||
return node.rw_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a vertex is also a top-level one.
|
||||
* @return True if the vertex is a top-level one, false otherwise.
|
||||
*/
|
||||
bool top_level() const ENTT_NOEXCEPT {
|
||||
return is_top_level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a type info object associated with a vertex.
|
||||
* @return A properly initialized type info object.
|
||||
*/
|
||||
type_info info() const ENTT_NOEXCEPT {
|
||||
return node.info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a user defined name associated with a vertex, if any.
|
||||
* @return The user defined name associated with the vertex, if any.
|
||||
*/
|
||||
const char * name() const ENTT_NOEXCEPT {
|
||||
return node.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the function associated with a vertex.
|
||||
* @return The function associated with the vertex.
|
||||
*/
|
||||
function_type * callback() const ENTT_NOEXCEPT {
|
||||
return node.callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the payload associated with a vertex, if any.
|
||||
* @return The payload associated with the vertex, if any.
|
||||
*/
|
||||
const void * data() const ENTT_NOEXCEPT {
|
||||
return node.payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the list of nodes reachable from a given vertex.
|
||||
* @return The list of nodes reachable from the vertex.
|
||||
*/
|
||||
const std::vector<std::size_t> & children() const ENTT_NOEXCEPT {
|
||||
return reachable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prepares a registry and assures that all required resources
|
||||
* are properly instantiated before using them.
|
||||
* @param reg A valid registry.
|
||||
*/
|
||||
void prepare(basic_registry<entity_type> ®) const {
|
||||
node.prepare ? node.prepare(reg) : void();
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_top_level;
|
||||
vertex_data node;
|
||||
std::vector<std::size_t> reachable;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Adds a free function to the task list.
|
||||
* @tparam Candidate Function to add to the task list.
|
||||
* @tparam Req Additional requirements and/or override resource access mode.
|
||||
* @param name Optional name to associate with the task.
|
||||
*/
|
||||
template<auto Candidate, typename... Req>
|
||||
void emplace(const char *name = nullptr) {
|
||||
using resource_type = decltype(internal::to_resource<Req...>(Candidate));
|
||||
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>;
|
||||
|
||||
callback_type *callback = +[](const void *, basic_registry<entity_type> ®) {
|
||||
std::apply(Candidate, to_args(reg, typename resource_type::args{}));
|
||||
};
|
||||
|
||||
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
|
||||
|
||||
vertices.push_back({
|
||||
type_list_size_v<typename resource_type::ro>,
|
||||
type_list_size_v<typename resource_type::rw>,
|
||||
name,
|
||||
nullptr,
|
||||
callback,
|
||||
+[](const bool rw, type_info *buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
|
||||
+[](basic_registry<entity_type> ®) { void(to_args(reg, typename resource_type::args{})); },
|
||||
type_id<integral_constant<Candidate>>()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a free function with payload or a member function with an
|
||||
* instance to the task list.
|
||||
* @tparam Candidate Function or member to add to the task list.
|
||||
* @tparam Req Additional requirements and/or override resource access mode.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
* @param name Optional name to associate with the task.
|
||||
*/
|
||||
template<auto Candidate, typename... Req, typename Type>
|
||||
void emplace(Type &value_or_instance, const char *name = nullptr) {
|
||||
using resource_type = decltype(internal::to_resource<Req...>(Candidate));
|
||||
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>;
|
||||
|
||||
callback_type *callback = +[](const void *payload, basic_registry<entity_type> ®) {
|
||||
Type *curr = static_cast<Type *>(const_cast<std::conditional_t<std::is_const_v<Type>, const void *, void *>>(payload));
|
||||
std::apply(Candidate, std::tuple_cat(std::forward_as_tuple(*curr), to_args(reg, typename resource_type::args{})));
|
||||
};
|
||||
|
||||
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
|
||||
|
||||
vertices.push_back({
|
||||
type_list_size_v<typename resource_type::ro>,
|
||||
type_list_size_v<typename resource_type::rw>,
|
||||
name,
|
||||
&value_or_instance,
|
||||
callback,
|
||||
+[](const bool rw, type_info *buffer, const std::size_t length) {
|
||||
return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length);
|
||||
},
|
||||
+[](basic_registry<entity_type> ®) {
|
||||
void(to_args(reg, typename resource_type::args{}));
|
||||
},
|
||||
type_id<integral_constant<Candidate>>()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds an user defined function with optional payload to the task
|
||||
* list.
|
||||
* @tparam Req Additional requirements and/or override resource access mode.
|
||||
* @param func Function to add to the task list.
|
||||
* @param payload User defined arbitrary data.
|
||||
* @param name Optional name to associate with the task.
|
||||
*/
|
||||
template<typename... Req>
|
||||
void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) {
|
||||
using resource_type = decltype(internal::to_resource<Req...>());
|
||||
track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{});
|
||||
|
||||
vertices.push_back({
|
||||
type_list_size_v<typename resource_type::ro>,
|
||||
type_list_size_v<typename resource_type::rw>,
|
||||
name,
|
||||
payload,
|
||||
func,
|
||||
+[](const bool rw, type_info *buffer, const std::size_t length) {
|
||||
return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length);
|
||||
},
|
||||
nullptr,
|
||||
type_info{}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generates a task graph for the current content.
|
||||
* @return The adjacency list of the task graph.
|
||||
*/
|
||||
std::vector<vertex> graph() {
|
||||
const auto edges = adjacency_matrix();
|
||||
|
||||
// creates the adjacency list
|
||||
std::vector<vertex> adjacency_list{};
|
||||
adjacency_list.reserve(vertices.size());
|
||||
|
||||
for(std::size_t col{}, length = vertices.size(); col < length; ++col) {
|
||||
std::vector<std::size_t> reachable{};
|
||||
const auto row = col * length;
|
||||
bool is_top_level = true;
|
||||
|
||||
for(std::size_t next{}; next < length; ++next) {
|
||||
if(edges[row + next]) {
|
||||
reachable.push_back(next);
|
||||
}
|
||||
}
|
||||
|
||||
for(std::size_t next{}; next < length && is_top_level; ++next) {
|
||||
is_top_level = !edges[next * length + col];
|
||||
}
|
||||
|
||||
adjacency_list.emplace_back(is_top_level, vertices[col], std::move(reachable));
|
||||
}
|
||||
|
||||
return adjacency_list;
|
||||
}
|
||||
|
||||
/*! @brief Erases all elements from a container. */
|
||||
void clear() {
|
||||
dependencies.clear();
|
||||
vertices.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<entt::id_type, std::vector<std::pair<std::size_t, bool>>> dependencies;
|
||||
std::vector<vertex_data> vertices;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "entity/handle.hpp"
|
||||
#include "entity/helper.hpp"
|
||||
#include "entity/observer.hpp"
|
||||
#include "entity/organizer.hpp"
|
||||
#include "entity/pool.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/runtime_view.hpp"
|
||||
|
||||
@@ -166,6 +166,7 @@ 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(organizer entt/entity/organizer.cpp)
|
||||
SETUP_BASIC_TEST(registry entt/entity/registry.cpp)
|
||||
SETUP_BASIC_TEST(registry_no_eto entt/entity/registry_no_eto.cpp ENTT_NO_ETO)
|
||||
SETUP_BASIC_TEST(runtime_view entt/entity/runtime_view.cpp)
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/organizer.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
void ro_int_rw_char_double(entt::view<entt::exclude_t<>, const int, char>, double &) {}
|
||||
void ro_char_rw_int(entt::view<entt::exclude_t<>, int, const char>) {}
|
||||
void ro_char_rw_double(entt::view<entt::exclude_t<>, const char>, double &) {}
|
||||
void ro_int_double(entt::view<entt::exclude_t<>, const int>, const double &) {}
|
||||
void sync_point(entt::registry &) {}
|
||||
|
||||
struct clazz {
|
||||
void ro_int_char_double(entt::view<entt::exclude_t<>, const int, const char>, const double &) {}
|
||||
void rw_int(entt::view<entt::exclude_t<>, int>) {}
|
||||
void rw_int_char(entt::view<entt::exclude_t<>, int, char>) {}
|
||||
void rw_int_char_double(entt::view<entt::exclude_t<>, int, char>, double &) {}
|
||||
};
|
||||
|
||||
void to_args_integrity(entt::view<entt::exclude_t<>, int> view, std::size_t &value, entt::registry ®istry) {
|
||||
value = view.size();
|
||||
}
|
||||
|
||||
TEST(Organizer, EmplaceFreeFunction) {
|
||||
entt::organizer organizer;
|
||||
entt::registry registry;
|
||||
|
||||
organizer.emplace<&ro_int_rw_char_double>("t1");
|
||||
organizer.emplace<&ro_char_rw_int>("t2");
|
||||
organizer.emplace<&ro_char_rw_double>("t3");
|
||||
organizer.emplace<&ro_int_double>("t4");
|
||||
|
||||
const auto graph = organizer.graph();
|
||||
|
||||
ASSERT_EQ(graph.size(), 4u);
|
||||
|
||||
ASSERT_STREQ(graph[0u].name(), "t1");
|
||||
ASSERT_STREQ(graph[1u].name(), "t2");
|
||||
ASSERT_STREQ(graph[2u].name(), "t3");
|
||||
ASSERT_STREQ(graph[3u].name(), "t4");
|
||||
|
||||
ASSERT_EQ(graph[0u].ro_count(), 1u);
|
||||
ASSERT_EQ(graph[1u].ro_count(), 1u);
|
||||
ASSERT_EQ(graph[2u].ro_count(), 1u);
|
||||
ASSERT_EQ(graph[3u].ro_count(), 2u);
|
||||
|
||||
ASSERT_EQ(graph[0u].rw_count(), 2u);
|
||||
ASSERT_EQ(graph[1u].rw_count(), 1u);
|
||||
ASSERT_EQ(graph[2u].rw_count(), 1u);
|
||||
ASSERT_EQ(graph[3u].rw_count(), 0u);
|
||||
|
||||
ASSERT_NE(graph[0u].info(), graph[1u].info());
|
||||
ASSERT_NE(graph[1u].info(), graph[2u].info());
|
||||
ASSERT_NE(graph[2u].info(), graph[3u].info());
|
||||
|
||||
ASSERT_TRUE(graph[0u].top_level());
|
||||
ASSERT_FALSE(graph[1u].top_level());
|
||||
ASSERT_FALSE(graph[2u].top_level());
|
||||
ASSERT_FALSE(graph[3u].top_level());
|
||||
|
||||
ASSERT_EQ(graph[0u].children().size(), 2u);
|
||||
ASSERT_EQ(graph[1u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[2u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[3u].children().size(), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].children()[0u], 1u);
|
||||
ASSERT_EQ(graph[0u].children()[1u], 2u);
|
||||
ASSERT_EQ(graph[1u].children()[0u], 3u);
|
||||
ASSERT_EQ(graph[2u].children()[0u], 3u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
|
||||
organizer.clear();
|
||||
|
||||
ASSERT_EQ(organizer.graph().size(), 0u);
|
||||
}
|
||||
|
||||
TEST(Organizer, EmplaceMemberFunction) {
|
||||
entt::organizer organizer;
|
||||
entt::registry registry;
|
||||
clazz instance;
|
||||
|
||||
organizer.emplace<&clazz::ro_int_char_double>(instance, "t1");
|
||||
organizer.emplace<&clazz::rw_int>(instance, "t2");
|
||||
organizer.emplace<&clazz::rw_int_char>(instance, "t3");
|
||||
organizer.emplace<&clazz::rw_int_char_double>(instance, "t4");
|
||||
|
||||
const auto graph = organizer.graph();
|
||||
|
||||
ASSERT_EQ(graph.size(), 4u);
|
||||
|
||||
ASSERT_STREQ(graph[0u].name(), "t1");
|
||||
ASSERT_STREQ(graph[1u].name(), "t2");
|
||||
ASSERT_STREQ(graph[2u].name(), "t3");
|
||||
ASSERT_STREQ(graph[3u].name(), "t4");
|
||||
|
||||
ASSERT_EQ(graph[0u].ro_count(), 3u);
|
||||
ASSERT_EQ(graph[1u].ro_count(), 0u);
|
||||
ASSERT_EQ(graph[2u].ro_count(), 0u);
|
||||
ASSERT_EQ(graph[3u].ro_count(), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].rw_count(), 0u);
|
||||
ASSERT_EQ(graph[1u].rw_count(), 1u);
|
||||
ASSERT_EQ(graph[2u].rw_count(), 2u);
|
||||
ASSERT_EQ(graph[3u].rw_count(), 3u);
|
||||
|
||||
ASSERT_NE(graph[0u].info(), graph[1u].info());
|
||||
ASSERT_NE(graph[1u].info(), graph[2u].info());
|
||||
ASSERT_NE(graph[2u].info(), graph[3u].info());
|
||||
|
||||
ASSERT_TRUE(graph[0u].top_level());
|
||||
ASSERT_FALSE(graph[1u].top_level());
|
||||
ASSERT_FALSE(graph[2u].top_level());
|
||||
ASSERT_FALSE(graph[3u].top_level());
|
||||
|
||||
ASSERT_EQ(graph[0u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[1u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[2u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[3u].children().size(), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].children()[0u], 1u);
|
||||
ASSERT_EQ(graph[1u].children()[0u], 2u);
|
||||
ASSERT_EQ(graph[2u].children()[0u], 3u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
|
||||
organizer.clear();
|
||||
|
||||
ASSERT_EQ(organizer.graph().size(), 0u);
|
||||
}
|
||||
|
||||
TEST(Organizer, EmplaceDirectFunction) {
|
||||
entt::organizer organizer;
|
||||
entt::registry registry;
|
||||
clazz instance;
|
||||
|
||||
// no aggressive comdat
|
||||
auto t1 = +[](const void *, entt::registry ®) { reg.clear<int>(); };
|
||||
auto t2 = +[](const void *, entt::registry ®) { reg.clear<char>(); };
|
||||
auto t3 = +[](const void *, entt::registry ®) { reg.clear<double>(); };
|
||||
auto t4 = +[](const void *, entt::registry ®) { reg.clear(); };
|
||||
|
||||
organizer.emplace<int>(t1, nullptr, "t1");
|
||||
organizer.emplace<const int>(t2, &instance, "t2");
|
||||
organizer.emplace<const int, char>(t3, nullptr, "t3");
|
||||
organizer.emplace<int, char, double>(t4, &instance, "t4");
|
||||
|
||||
const auto graph = organizer.graph();
|
||||
|
||||
ASSERT_EQ(graph.size(), 4u);
|
||||
|
||||
ASSERT_STREQ(graph[0u].name(), "t1");
|
||||
ASSERT_STREQ(graph[1u].name(), "t2");
|
||||
ASSERT_STREQ(graph[2u].name(), "t3");
|
||||
ASSERT_STREQ(graph[3u].name(), "t4");
|
||||
|
||||
ASSERT_EQ(graph[0u].ro_count(), 0u);
|
||||
ASSERT_EQ(graph[1u].ro_count(), 1u);
|
||||
ASSERT_EQ(graph[2u].ro_count(), 1u);
|
||||
ASSERT_EQ(graph[3u].ro_count(), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].rw_count(), 1u);
|
||||
ASSERT_EQ(graph[1u].rw_count(), 0u);
|
||||
ASSERT_EQ(graph[2u].rw_count(), 1u);
|
||||
ASSERT_EQ(graph[3u].rw_count(), 3u);
|
||||
|
||||
ASSERT_TRUE(graph[0u].callback() == t1);
|
||||
ASSERT_TRUE(graph[1u].callback() == t2);
|
||||
ASSERT_TRUE(graph[2u].callback() == t3);
|
||||
ASSERT_TRUE(graph[3u].callback() == t4);
|
||||
|
||||
ASSERT_EQ(graph[0u].data(), nullptr);
|
||||
ASSERT_EQ(graph[1u].data(), &instance);
|
||||
ASSERT_EQ(graph[2u].data(), nullptr);
|
||||
ASSERT_EQ(graph[3u].data(), &instance);
|
||||
|
||||
ASSERT_EQ(graph[0u].info(), entt::type_info{});
|
||||
ASSERT_EQ(graph[1u].info(), entt::type_info{});
|
||||
ASSERT_EQ(graph[2u].info(), entt::type_info{});
|
||||
ASSERT_EQ(graph[3u].info(), entt::type_info{});
|
||||
|
||||
ASSERT_TRUE(graph[0u].top_level());
|
||||
ASSERT_FALSE(graph[1u].top_level());
|
||||
ASSERT_FALSE(graph[2u].top_level());
|
||||
ASSERT_FALSE(graph[3u].top_level());
|
||||
|
||||
ASSERT_EQ(graph[0u].children().size(), 2u);
|
||||
ASSERT_EQ(graph[1u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[2u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[3u].children().size(), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].children()[0u], 1u);
|
||||
ASSERT_EQ(graph[0u].children()[1u], 2u);
|
||||
ASSERT_EQ(graph[1u].children()[0u], 3u);
|
||||
ASSERT_EQ(graph[2u].children()[0u], 3u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
|
||||
organizer.clear();
|
||||
|
||||
ASSERT_EQ(organizer.graph().size(), 0u);
|
||||
}
|
||||
|
||||
TEST(Organizer, SyncPoint) {
|
||||
entt::organizer organizer;
|
||||
entt::registry registry;
|
||||
clazz instance;
|
||||
|
||||
organizer.emplace<&ro_int_double>("before");
|
||||
organizer.emplace<&sync_point>("sync");
|
||||
organizer.emplace<&clazz::ro_int_char_double>(instance, "after");
|
||||
|
||||
const auto graph = organizer.graph();
|
||||
|
||||
ASSERT_EQ(graph.size(), 3u);
|
||||
|
||||
ASSERT_STREQ(graph[0u].name(), "before");
|
||||
ASSERT_STREQ(graph[1u].name(), "sync");
|
||||
ASSERT_STREQ(graph[2u].name(), "after");
|
||||
|
||||
ASSERT_TRUE(graph[0u].top_level());
|
||||
ASSERT_FALSE(graph[1u].top_level());
|
||||
ASSERT_FALSE(graph[2u].top_level());
|
||||
|
||||
ASSERT_EQ(graph[0u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[1u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[2u].children().size(), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].children()[0u], 1u);
|
||||
ASSERT_EQ(graph[1u].children()[0u], 2u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Organizer, Override) {
|
||||
entt::organizer organizer;
|
||||
|
||||
organizer.emplace<&ro_int_rw_char_double, const char, const double>("t1");
|
||||
organizer.emplace<&ro_char_rw_double, const double>("t2");
|
||||
organizer.emplace<&ro_int_double, double>("t3");
|
||||
|
||||
const auto graph = organizer.graph();
|
||||
|
||||
ASSERT_EQ(graph.size(), 3u);
|
||||
|
||||
ASSERT_STREQ(graph[0u].name(), "t1");
|
||||
ASSERT_STREQ(graph[1u].name(), "t2");
|
||||
ASSERT_STREQ(graph[2u].name(), "t3");
|
||||
|
||||
ASSERT_TRUE(graph[0u].top_level());
|
||||
ASSERT_TRUE(graph[1u].top_level());
|
||||
ASSERT_FALSE(graph[2u].top_level());
|
||||
|
||||
ASSERT_EQ(graph[0u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[1u].children().size(), 1u);
|
||||
ASSERT_EQ(graph[2u].children().size(), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].children()[0u], 2u);
|
||||
ASSERT_EQ(graph[1u].children()[0u], 2u);
|
||||
}
|
||||
|
||||
TEST(Organizer, Prepare) {
|
||||
entt::organizer organizer;
|
||||
entt::registry registry;
|
||||
clazz instance;
|
||||
|
||||
organizer.emplace<&ro_int_double>();
|
||||
organizer.emplace<&clazz::rw_int_char>(instance);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
TEST(Organizer, Dependencies) {
|
||||
entt::organizer organizer;
|
||||
clazz instance;
|
||||
|
||||
organizer.emplace<&ro_int_double>();
|
||||
organizer.emplace<&clazz::rw_int_char>(instance);
|
||||
organizer.emplace<char, const double>(+[](const void *, entt::registry &) {});
|
||||
|
||||
const auto graph = organizer.graph();
|
||||
entt::type_info buffer[5u]{};
|
||||
|
||||
ASSERT_EQ(graph.size(), 3u);
|
||||
|
||||
ASSERT_EQ(graph[0u].ro_count(), 2u);
|
||||
ASSERT_EQ(graph[0u].rw_count(), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].ro_dependency(buffer, 0u), 0u);
|
||||
ASSERT_EQ(graph[0u].rw_dependency(buffer, 2u), 0u);
|
||||
|
||||
ASSERT_EQ(graph[0u].ro_dependency(buffer, 5u), 2u);
|
||||
ASSERT_EQ(buffer[0u], entt::type_id<int>());
|
||||
ASSERT_EQ(buffer[1u], entt::type_id<double>());
|
||||
|
||||
ASSERT_EQ(graph[1u].ro_count(), 0u);
|
||||
ASSERT_EQ(graph[1u].rw_count(), 2u);
|
||||
|
||||
ASSERT_EQ(graph[1u].ro_dependency(buffer, 2u), 0u);
|
||||
ASSERT_EQ(graph[1u].rw_dependency(buffer, 0u), 0u);
|
||||
|
||||
ASSERT_EQ(graph[1u].rw_dependency(buffer, 5u), 2u);
|
||||
ASSERT_EQ(buffer[0u], entt::type_id<int>());
|
||||
ASSERT_EQ(buffer[1u], entt::type_id<char>());
|
||||
|
||||
ASSERT_EQ(graph[2u].ro_count(), 1u);
|
||||
ASSERT_EQ(graph[2u].rw_count(), 1u);
|
||||
|
||||
ASSERT_EQ(graph[2u].ro_dependency(buffer, 2u), 1u);
|
||||
ASSERT_EQ(graph[2u].rw_dependency(buffer, 0u), 0u);
|
||||
|
||||
ASSERT_EQ(graph[2u].ro_dependency(buffer, 5u), 1u);
|
||||
ASSERT_EQ(buffer[0u], entt::type_id<double>());
|
||||
|
||||
ASSERT_EQ(graph[2u].rw_dependency(buffer, 5u), 1u);
|
||||
ASSERT_EQ(buffer[0u], entt::type_id<char>());
|
||||
}
|
||||
|
||||
TEST(Organizer, ToArgsIntegrity) {
|
||||
entt::organizer organizer;
|
||||
entt::registry registry;
|
||||
|
||||
organizer.emplace<&to_args_integrity>();
|
||||
registry.set<std::size_t>(42u);
|
||||
|
||||
auto graph = organizer.graph();
|
||||
graph[0u].callback()(graph[0u].data(), registry);
|
||||
|
||||
ASSERT_EQ(registry.ctx<std::size_t>(), 0u);
|
||||
}
|
||||
Reference in New Issue
Block a user