flow.hpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. #ifndef ENTT_GRAPH_FLOW_HPP
  2. #define ENTT_GRAPH_FLOW_HPP
  3. #include <algorithm>
  4. #include <concepts>
  5. #include <cstddef>
  6. #include <functional>
  7. #include <iterator>
  8. #include <memory>
  9. #include <type_traits>
  10. #include <utility>
  11. #include "../config/config.h"
  12. #include "../container/dense_map.hpp"
  13. #include "../container/dense_set.hpp"
  14. #include "../core/compressed_pair.hpp"
  15. #include "../core/fwd.hpp"
  16. #include "../core/iterator.hpp"
  17. #include "../stl/functional.hpp"
  18. #include "../stl/iterator.hpp"
  19. #include "../stl/vector.hpp"
  20. #include "adjacency_matrix.hpp"
  21. #include "fwd.hpp"
  22. namespace entt {
  23. /**
  24. * @brief Utility class for creating task graphs.
  25. * @tparam Allocator Type of allocator used to manage memory and elements.
  26. */
  27. template<typename Allocator>
  28. class basic_flow {
  29. using alloc_traits = std::allocator_traits<Allocator>;
  30. static_assert(std::is_same_v<typename alloc_traits::value_type, id_type>, "Invalid value type");
  31. using task_container_type = dense_set<id_type, stl::identity, std::equal_to<>, typename alloc_traits::template rebind_alloc<id_type>>;
  32. using ro_rw_container_type = stl::vector<std::pair<std::size_t, bool>, typename alloc_traits::template rebind_alloc<std::pair<std::size_t, bool>>>;
  33. using deps_container_type = dense_map<id_type, ro_rw_container_type, stl::identity, std::equal_to<>, typename alloc_traits::template rebind_alloc<std::pair<const id_type, ro_rw_container_type>>>;
  34. using adjacency_matrix_type = adjacency_matrix<directed_tag, typename alloc_traits::template rebind_alloc<std::size_t>>;
  35. void emplace(const id_type res, const bool is_rw) {
  36. ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
  37. if(!deps.contains(res) && sync_on != vertices.size()) {
  38. deps[res].emplace_back(sync_on, true);
  39. }
  40. deps[res].emplace_back(index.first(), is_rw);
  41. }
  42. void setup_graph(adjacency_matrix_type &matrix) const {
  43. for(const auto &elem: deps) {
  44. const auto last = elem.second.cend();
  45. auto it = elem.second.cbegin();
  46. while(it != last) {
  47. if(it->second) {
  48. // rw item
  49. if(auto curr = it++; it != last) {
  50. if(it->second) {
  51. matrix.insert(curr->first, it->first);
  52. } else if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
  53. for(; it != next; ++it) {
  54. matrix.insert(curr->first, it->first);
  55. matrix.insert(it->first, next->first);
  56. }
  57. } else {
  58. for(; it != next; ++it) {
  59. matrix.insert(curr->first, it->first);
  60. }
  61. }
  62. }
  63. } else {
  64. // ro item (first iteration only)
  65. if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
  66. for(; it != next; ++it) {
  67. matrix.insert(it->first, next->first);
  68. }
  69. } else {
  70. it = last;
  71. }
  72. }
  73. }
  74. }
  75. }
  76. void transitive_closure(adjacency_matrix_type &matrix) const {
  77. const auto length = matrix.size();
  78. for(std::size_t vk{}; vk < length; ++vk) {
  79. for(std::size_t vi{}; vi < length; ++vi) {
  80. for(std::size_t vj{}; vj < length; ++vj) {
  81. if(matrix.contains(vi, vk) && matrix.contains(vk, vj)) {
  82. matrix.insert(vi, vj);
  83. }
  84. }
  85. }
  86. }
  87. }
  88. void transitive_reduction(adjacency_matrix_type &matrix) const {
  89. const auto length = matrix.size();
  90. for(std::size_t vert{}; vert < length; ++vert) {
  91. matrix.erase(vert, vert);
  92. }
  93. for(std::size_t vj{}; vj < length; ++vj) {
  94. for(std::size_t vi{}; vi < length; ++vi) {
  95. if(matrix.contains(vi, vj)) {
  96. for(std::size_t vk{}; vk < length; ++vk) {
  97. if(matrix.contains(vj, vk)) {
  98. matrix.erase(vi, vk);
  99. }
  100. }
  101. }
  102. }
  103. }
  104. }
  105. public:
  106. /*! @brief Allocator type. */
  107. using allocator_type = Allocator;
  108. /*! @brief Unsigned integer type. */
  109. using size_type = std::size_t;
  110. /*! @brief Iterable task list. */
  111. using iterable = iterable_adaptor<typename task_container_type::const_iterator>;
  112. /*! @brief Adjacency matrix type. */
  113. using graph_type = adjacency_matrix_type;
  114. /*! @brief Default constructor. */
  115. basic_flow()
  116. : basic_flow{allocator_type{}} {}
  117. /**
  118. * @brief Constructs a flow builder with a given allocator.
  119. * @param allocator The allocator to use.
  120. */
  121. explicit basic_flow(const allocator_type &allocator)
  122. : index{0u, allocator},
  123. vertices{allocator},
  124. deps{allocator} {}
  125. /*! @brief Default copy constructor. */
  126. basic_flow(const basic_flow &) = default;
  127. /**
  128. * @brief Allocator-extended copy constructor.
  129. * @param other The instance to copy from.
  130. * @param allocator The allocator to use.
  131. */
  132. basic_flow(const basic_flow &other, const allocator_type &allocator)
  133. : index{other.index.first(), allocator},
  134. vertices{other.vertices, allocator},
  135. deps{other.deps, allocator},
  136. sync_on{other.sync_on} {}
  137. /*! @brief Default move constructor. */
  138. basic_flow(basic_flow &&) noexcept = default;
  139. /**
  140. * @brief Allocator-extended move constructor.
  141. * @param other The instance to move from.
  142. * @param allocator The allocator to use.
  143. */
  144. basic_flow(basic_flow &&other, const allocator_type &allocator)
  145. : index{other.index.first(), allocator},
  146. vertices{std::move(other.vertices), allocator},
  147. deps{std::move(other.deps), allocator},
  148. sync_on{other.sync_on} {}
  149. /*! @brief Default destructor. */
  150. ~basic_flow() = default;
  151. /**
  152. * @brief Default copy assignment operator.
  153. * @return This flow builder.
  154. */
  155. basic_flow &operator=(const basic_flow &) = default;
  156. /**
  157. * @brief Default move assignment operator.
  158. * @return This flow builder.
  159. */
  160. basic_flow &operator=(basic_flow &&) noexcept = default;
  161. /**
  162. * @brief Exchanges the contents with those of a given flow builder.
  163. * @param other Flow builder to exchange the content with.
  164. */
  165. void swap(basic_flow &other) noexcept {
  166. using std::swap;
  167. std::swap(index, other.index);
  168. std::swap(vertices, other.vertices);
  169. std::swap(deps, other.deps);
  170. std::swap(sync_on, other.sync_on);
  171. }
  172. /**
  173. * @brief Returns the associated allocator.
  174. * @return The associated allocator.
  175. */
  176. [[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
  177. return allocator_type{index.second()};
  178. }
  179. /**
  180. * @brief Returns the identifier at specified location.
  181. * @param pos Position of the identifier to return.
  182. * @return The requested identifier.
  183. */
  184. [[nodiscard]] id_type operator[](const size_type pos) const {
  185. return vertices.cbegin()[static_cast<task_container_type::difference_type>(pos)];
  186. }
  187. /*! @brief Clears the flow builder. */
  188. void clear() noexcept {
  189. index.first() = {};
  190. vertices.clear();
  191. deps.clear();
  192. sync_on = {};
  193. }
  194. /**
  195. * @brief Returns true if a flow builder contains no tasks, false otherwise.
  196. * @return True if the flow builder contains no tasks, false otherwise.
  197. */
  198. [[nodiscard]] bool empty() const noexcept {
  199. return vertices.empty();
  200. }
  201. /**
  202. * @brief Returns the number of tasks.
  203. * @return The number of tasks.
  204. */
  205. [[nodiscard]] size_type size() const noexcept {
  206. return vertices.size();
  207. }
  208. /**
  209. * @brief Binds a task to a flow builder.
  210. * @param value Task identifier.
  211. * @return This flow builder.
  212. */
  213. basic_flow &bind(const id_type value) {
  214. sync_on += (sync_on == vertices.size());
  215. const auto it = vertices.emplace(value).first;
  216. index.first() = size_type(it - vertices.begin());
  217. return *this;
  218. }
  219. /**
  220. * @brief Turns the current task into a sync point.
  221. * @return This flow builder.
  222. */
  223. basic_flow &sync() {
  224. ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
  225. sync_on = index.first();
  226. for(const auto &elem: deps) {
  227. elem.second.emplace_back(sync_on, true);
  228. }
  229. return *this;
  230. }
  231. /**
  232. * @brief Assigns a resource to the current task with a given access mode.
  233. * @param res Resource identifier.
  234. * @param is_rw Access mode.
  235. * @return This flow builder.
  236. */
  237. basic_flow &set(const id_type res, bool is_rw = false) {
  238. emplace(res, is_rw);
  239. return *this;
  240. }
  241. /**
  242. * @brief Assigns a read-only resource to the current task.
  243. * @param res Resource identifier.
  244. * @return This flow builder.
  245. */
  246. basic_flow &ro(const id_type res) {
  247. emplace(res, false);
  248. return *this;
  249. }
  250. /**
  251. * @brief Assigns a range of read-only resources to the current task.
  252. * @param first An iterator to the first element of the range of elements.
  253. * @param last An iterator past the last element of the range of elements.
  254. * @return This flow builder.
  255. */
  256. basic_flow &ro(stl::input_iterator auto first, stl::input_iterator auto last) {
  257. for(; first != last; ++first) {
  258. emplace(*first, false);
  259. }
  260. return *this;
  261. }
  262. /**
  263. * @brief Assigns a writable resource to the current task.
  264. * @param res Resource identifier.
  265. * @return This flow builder.
  266. */
  267. basic_flow &rw(const id_type res) {
  268. emplace(res, true);
  269. return *this;
  270. }
  271. /**
  272. * @brief Assigns a range of writable resources to the current task.
  273. * @param first An iterator to the first element of the range of elements.
  274. * @param last An iterator past the last element of the range of elements.
  275. * @return This flow builder.
  276. */
  277. basic_flow &rw(stl::input_iterator auto first, stl::input_iterator auto last) {
  278. for(; first != last; ++first) {
  279. emplace(*first, true);
  280. }
  281. return *this;
  282. }
  283. /**
  284. * @brief Generates a task graph for the current content.
  285. * @return The adjacency matrix of the task graph.
  286. */
  287. [[nodiscard]] graph_type graph() const {
  288. graph_type matrix{vertices.size(), get_allocator()};
  289. setup_graph(matrix);
  290. transitive_closure(matrix);
  291. transitive_reduction(matrix);
  292. return matrix;
  293. }
  294. private:
  295. compressed_pair<size_type, allocator_type> index;
  296. task_container_type vertices;
  297. deps_container_type deps;
  298. size_type sync_on{};
  299. };
  300. } // namespace entt
  301. #endif