Skip to content

Instantly share code, notes, and snippets.

@detri
Last active September 13, 2025 13:35
Show Gist options
  • Select an option

  • Save detri/230031d7f54505d22be8eaa2b644555f to your computer and use it in GitHub Desktop.

Select an option

Save detri/230031d7f54505d22be8eaa2b644555f to your computer and use it in GitHub Desktop.
Entitask Executor (EnTT & Taskflow)
#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], &registry](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