diff --git a/docs/md/entity.md b/docs/md/entity.md
index 66738e2b8..7b4f6517f 100644
--- a/docs/md/entity.md
+++ b/docs/md/entity.md
@@ -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.
+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.
+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 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
diff --git a/src/entt/entity/fwd.hpp b/src/entt/entity/fwd.hpp
index 625f3daed..906f245a8 100644
--- a/src/entt/entity/fwd.hpp
+++ b/src/entt/entity/fwd.hpp
@@ -8,7 +8,7 @@
namespace entt {
-template
+template
class basic_registry;
@@ -28,6 +28,10 @@ template
class basic_observer;
+template
+class basic_organizer;
+
+
template
struct basic_actor;
@@ -60,6 +64,10 @@ using registry = basic_registry;
using observer = basic_observer;
+/*! @brief Alias declaration for the most common use case. */
+using organizer = basic_organizer;
+
+
/*! @brief Alias declaration for the most common use case. */
using handle = basic_handle;
diff --git a/src/entt/entity/organizer.hpp b/src/entt/entity/organizer.hpp
new file mode 100644
index 000000000..347553f52
--- /dev/null
+++ b/src/entt/entity/organizer.hpp
@@ -0,0 +1,506 @@
+#ifndef ENTT_ENTITY_ORGANIZER_HPP
+#define ENTT_ENTITY_ORGANIZER_HPP
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#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
+struct is_view: std::false_type {};
+
+template
+struct is_view, Component...>>: std::true_type {};
+
+template
+inline constexpr bool is_view_v = is_view::value;
+
+
+template
+struct unpack_type {
+ using ro = std::conditional_t<
+ type_list_contains_v> || (std::is_const_v && !type_list_contains_v>),
+ type_list>,
+ type_list<>
+ >;
+
+ using rw = std::conditional_t<
+ type_list_contains_v> || (!std::is_const_v && !type_list_contains_v>),
+ type_list,
+ type_list<>
+ >;
+};
+
+template
+struct unpack_type, type_list> {
+ using ro = type_list<>;
+ using rw = type_list<>;
+};
+
+template
+struct unpack_type, type_list>
+ : unpack_type, type_list>
+{};
+
+template
+struct unpack_type, Component...>, type_list> {
+ using ro = type_list_cat_t, typename unpack_type>::ro...>;
+ using rw = type_list_cat_t>::rw...>;
+};
+
+template
+struct unpack_type, Component...>, type_list>
+ : unpack_type, Component...>, type_list>
+{};
+
+
+template
+struct resource;
+
+template
+struct resource, type_list> {
+ using args = type_list...>;
+ using ro = type_list_cat_t>::ro..., typename unpack_type>::ro...>;
+ using rw = type_list_cat_t>::rw..., typename unpack_type>::rw...>;
+};
+
+
+template
+resource...>, type_list> to_resource(Ret(*)(Args...));
+
+template
+resource...>, type_list> to_resource(Ret(Class:: *)(Args...));
+
+template
+resource...>, type_list> to_resource(Ret(Class:: *)(Args...) const);
+
+template
+resource, type_list> 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.
+ * 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
+class basic_organizer final {
+ using callback_type = void(const void *, entt::basic_registry &);
+ using prepare_type = void(entt::basic_registry &);
+ 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
+ [[nodiscard]] static decltype(auto) extract(basic_registry ®) {
+ if constexpr(std::is_same_v>) {
+ return reg;
+ } else if constexpr(internal::is_view_v) {
+ return as_view{reg};
+ } else {
+ return reg.template ctx_or_set>();
+ }
+ }
+
+ template
+ [[nodiscard]] static auto to_args(basic_registry ®, type_list) {
+ return std::tuple(reg))...>(extract(reg)...);
+ }
+
+ template
+ static std::size_t fill_dependencies(type_list, type_info *buffer, const std::size_t count) {
+ if constexpr(sizeof...(Type) == 0u) {
+ return {};
+ } else {
+ type_info info[sizeof...(Type)]{type_id()...};
+ const auto length = std::min(count, sizeof...(Type));
+ std::copy_n(info, length, buffer);
+ return length;
+ }
+ }
+
+ template
+ void track_dependencies(std::size_t index, const bool requires_registry, type_list, type_list) {
+ dependencies[type_hash>::value()].emplace_back(index, requires_registry && (sizeof...(RO) + sizeof...(RW) == 0u));
+ (dependencies[type_hash::value()].emplace_back(index, false), ...);
+ (dependencies[type_hash::value()].emplace_back(index, true), ...);
+ }
+
+ [[nodiscard]] std::vector adjacency_matrix() {
+ const auto length = vertices.size();
+ std::vector 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 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 & 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 ®) const {
+ node.prepare ? node.prepare(reg) : void();
+ }
+
+ private:
+ bool is_top_level;
+ vertex_data node;
+ std::vector 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
+ void emplace(const char *name = nullptr) {
+ using resource_type = decltype(internal::to_resource(Candidate));
+ constexpr auto requires_registry = type_list_contains_v>;
+
+ callback_type *callback = +[](const void *, basic_registry ®) {
+ 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,
+ type_list_size_v,
+ 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 ®) { void(to_args(reg, typename resource_type::args{})); },
+ type_id>()
+ });
+ }
+
+ /**
+ * @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
+ void emplace(Type &value_or_instance, const char *name = nullptr) {
+ using resource_type = decltype(internal::to_resource(Candidate));
+ constexpr auto requires_registry = type_list_contains_v>;
+
+ callback_type *callback = +[](const void *payload, basic_registry ®) {
+ Type *curr = static_cast(const_cast, 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,
+ type_list_size_v,
+ 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 ®) {
+ void(to_args(reg, typename resource_type::args{}));
+ },
+ type_id>()
+ });
+ }
+
+ /**
+ * @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
+ void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) {
+ using resource_type = decltype(internal::to_resource());
+ track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{});
+
+ vertices.push_back({
+ type_list_size_v,
+ type_list_size_v,
+ 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 graph() {
+ const auto edges = adjacency_matrix();
+
+ // creates the adjacency list
+ std::vector adjacency_list{};
+ adjacency_list.reserve(vertices.size());
+
+ for(std::size_t col{}, length = vertices.size(); col < length; ++col) {
+ std::vector 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>> dependencies;
+ std::vector vertices;
+};
+
+
+}
+
+
+#endif
diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp
index d1047b532..2f7ea9411 100644
--- a/src/entt/entt.hpp
+++ b/src/entt/entt.hpp
@@ -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"
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 92ecd17ab..d9626312e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -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)
diff --git a/test/entt/entity/organizer.cpp b/test/entt/entity/organizer.cpp
new file mode 100644
index 000000000..00d9e1edf
--- /dev/null
+++ b/test/entt/entity/organizer.cpp
@@ -0,0 +1,348 @@
+#include
+#include
+#include
+
+void ro_int_rw_char_double(entt::view, const int, char>, double &) {}
+void ro_char_rw_int(entt::view, int, const char>) {}
+void ro_char_rw_double(entt::view, const char>, double &) {}
+void ro_int_double(entt::view, const int>, const double &) {}
+void sync_point(entt::registry &) {}
+
+struct clazz {
+ void ro_int_char_double(entt::view, const int, const char>, const double &) {}
+ void rw_int(entt::view, int>) {}
+ void rw_int_char(entt::view, int, char>) {}
+ void rw_int_char_double(entt::view, int, char>, double &) {}
+};
+
+void to_args_integrity(entt::view, 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(); };
+ auto t2 = +[](const void *, entt::registry ®) { reg.clear(); };
+ auto t3 = +[](const void *, entt::registry ®) { reg.clear(); };
+ auto t4 = +[](const void *, entt::registry ®) { reg.clear(); };
+
+ organizer.emplace(t1, nullptr, "t1");
+ organizer.emplace(t2, &instance, "t2");
+ organizer.emplace(t3, nullptr, "t3");
+ organizer.emplace(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(), nullptr);
+ ASSERT_EQ(registry.try_ctx(), nullptr);
+ ASSERT_EQ(registry.try_ctx(), nullptr);
+
+ for(auto &&vertex: graph) {
+ vertex.prepare(registry);
+ }
+
+ ASSERT_EQ(registry.try_ctx(), nullptr);
+ ASSERT_EQ(registry.try_ctx(), nullptr);
+ ASSERT_NE(registry.try_ctx(), nullptr);
+}
+
+TEST(Organizer, Dependencies) {
+ entt::organizer organizer;
+ clazz instance;
+
+ organizer.emplace<&ro_int_double>();
+ organizer.emplace<&clazz::rw_int_char>(instance);
+ organizer.emplace(+[](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());
+ ASSERT_EQ(buffer[1u], entt::type_id());
+
+ 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());
+ ASSERT_EQ(buffer[1u], entt::type_id());
+
+ 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());
+
+ ASSERT_EQ(graph[2u].rw_dependency(buffer, 5u), 1u);
+ ASSERT_EQ(buffer[0u], entt::type_id());
+}
+
+TEST(Organizer, ToArgsIntegrity) {
+ entt::organizer organizer;
+ entt::registry registry;
+
+ organizer.emplace<&to_args_integrity>();
+ registry.set(42u);
+
+ auto graph = organizer.graph();
+ graph[0u].callback()(graph[0u].data(), registry);
+
+ ASSERT_EQ(registry.ctx(), 0u);
+}