From dec0f28a2b8600c5a8e831603f3e6cbe73bd56dc Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Fri, 2 Oct 2020 10:41:56 +0200 Subject: [PATCH] organizer: added new type to entt/entity --- docs/md/entity.md | 135 +++++++++ src/entt/entity/fwd.hpp | 10 +- src/entt/entity/organizer.hpp | 506 +++++++++++++++++++++++++++++++++ src/entt/entt.hpp | 1 + test/CMakeLists.txt | 1 + test/entt/entity/organizer.cpp | 348 +++++++++++++++++++++++ 6 files changed, 1000 insertions(+), 1 deletion(-) create mode 100644 src/entt/entity/organizer.hpp create mode 100644 test/entt/entity/organizer.cpp 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); +}