organizer: added new type to entt/entity

This commit is contained in:
Michele Caini
2020-10-02 10:41:56 +02:00
parent de7d79f1f4
commit dec0f28a2b
6 changed files with 1000 additions and 1 deletions
+135
View File
@@ -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
+9 -1
View File
@@ -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>;
+506
View File
@@ -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> &reg) {
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> &reg, 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> &reg) 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> &reg) {
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> &reg) { 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> &reg) {
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> &reg) {
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
+1
View File
@@ -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"
+1
View File
@@ -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)
+348
View File
@@ -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 &registry) {
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) { reg.clear<int>(); };
auto t2 = +[](const void *, entt::registry &reg) { reg.clear<char>(); };
auto t3 = +[](const void *, entt::registry &reg) { reg.clear<double>(); };
auto t4 = +[](const void *, entt::registry &reg) { 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);
}