Last active
September 13, 2025 13:35
-
-
Save detri/230031d7f54505d22be8eaa2b644555f to your computer and use it in GitHub Desktop.
Entitask Executor (EnTT & Taskflow)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include "entt/entt.hpp" | |
| #include "taskflow/taskflow.hpp" | |
| namespace entitask | |
| { | |
| /** | |
| * Basic executor class template. | |
| * Sets up an EnTT organizer's graph to be used as a tf::Taskflow | |
| * and executed with a tf::Executor. | |
| * @tparam Registry Type of EnTT registry to use. Default: entt::registry | |
| */ | |
| template<typename Registry = entt::registry> | |
| class basic_executor | |
| { | |
| public: | |
| using organizer_type = entt::basic_organizer<Registry>; | |
| using vertex = typename organizer_type::vertex; | |
| private: | |
| std::vector<vertex> vertices; | |
| tf::Taskflow tf; | |
| tf::Executor tf_executor; | |
| public: | |
| /** | |
| * Construct a basic executor using a registry and a configured organizer. | |
| * Example: | |
| * @code{c++} | |
| * entt::registry registry; | |
| * entt::organizer organizer; | |
| * organizer.emplace<&my_system>(); | |
| * entitask::executor executor{registry, organizer}; | |
| * // each frame: | |
| * executor.run(); | |
| * @endcode | |
| * @param registry EnTT registry instance | |
| * @param organizer EnTT organizer (registry-typed) | |
| */ | |
| explicit basic_executor(Registry& registry, organizer_type& organizer) : | |
| vertices{organizer.graph()}, | |
| tf{build_taskflow(registry)} | |
| { | |
| } | |
| basic_executor() = delete; | |
| basic_executor(const basic_executor&) = delete; | |
| basic_executor(basic_executor&&) = delete; | |
| basic_executor& operator=(const basic_executor&) = delete; | |
| basic_executor& operator=(basic_executor&&) = delete; | |
| /** | |
| * Run the executor using the generated taskflow. | |
| */ | |
| void run() | |
| { | |
| tf_executor.run(tf).wait(); | |
| } | |
| private: | |
| struct task_handle | |
| { | |
| const vertex* vertex; | |
| tf::Task* task; | |
| }; | |
| tf::Taskflow build_taskflow(Registry& registry) | |
| { | |
| tf::Taskflow taskflow; | |
| std::vector<task_handle> handles; | |
| handles.reserve(vertices.size()); | |
| // Prepare vertices and create tasks for them. | |
| // Store a handle to the vertex and task | |
| for (size_t i = 0; i < vertices.size(); ++i) | |
| { | |
| vertices[i].prepare(registry); | |
| tf::Task task = taskflow.emplace([vtx_ptr = &vertices[i], ®istry](tf::Subflow&) | |
| { | |
| vtx_ptr->callback()(vtx_ptr->data(), registry); | |
| }); | |
| handles.push_back({&vertices[i], &task}); | |
| } | |
| for (const auto& handle : handles) | |
| { | |
| // Find dependencies from each vertex's out edges | |
| for (const auto edge_idx : handle.vertex->out_edges()) | |
| { | |
| if (auto* dependent = find_handle_by_vertex_index(handles, edge_idx)) | |
| { | |
| handle.task->precede(*dependent->task); | |
| } | |
| } | |
| } | |
| return taskflow; | |
| } | |
| task_handle* find_handle_by_vertex_index(const std::vector<task_handle>& handles, size_t vertex_idx) | |
| { | |
| auto it = std::ranges::find_if(handles, [this, vertex_idx](const task_handle& h) { | |
| return h.vertex == &vertices[vertex_idx]; | |
| }); | |
| return it != handles.end() ? const_cast<task_handle*>(&*it) : nullptr; | |
| } | |
| }; | |
| /** | |
| * Executor which uses a default registry (and thus organizer). | |
| */ | |
| using executor = basic_executor<>; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment