#pragma once #include "graph.hpp" /** @file task.hpp @brief task include file */ namespace tf { // ---------------------------------------------------------------------------- // Task Types // ---------------------------------------------------------------------------- /** @enum TaskType @brief enumeration of all task types */ enum TaskType { PLACEHOLDER_TASK = Node::PLACEHOLDER_TASK, CUDAFLOW_TASK = Node::CUDAFLOW_TASK, STATIC_TASK = Node::STATIC_TASK, DYNAMIC_TASK = Node::DYNAMIC_TASK, CONDITION_TASK = Node::CONDITION_TASK, MODULE_TASK = Node::MODULE_TASK, ASYNC_TASK = Node::ASYNC_TASK }; /** @brief convert a task type to a human-readable string */ inline const char* task_type_to_string(TaskType type) { const char* val; switch(type) { case PLACEHOLDER_TASK: val = "placeholder"; break; case CUDAFLOW_TASK: val = "cudaflow"; break; case STATIC_TASK: val = "static"; break; case DYNAMIC_TASK: val = "subflow"; break; case CONDITION_TASK: val = "condition"; break; case MODULE_TASK: val = "module"; break; case ASYNC_TASK: val = "async"; break; default: val = "undefined"; break; } return val; } // ---------------------------------------------------------------------------- // Task Traits // ---------------------------------------------------------------------------- /** @brief determines if a callable is a static task A static task is a callable object constructible from std::function. */ template constexpr bool is_static_task_v = std::is_invocable_r_v && !std::is_invocable_r_v; /** @brief determines if a callable is a dynamic task A dynamic task is a callable object constructible from std::function. */ template constexpr bool is_dynamic_task_v = std::is_invocable_r_v; /** @brief determines if a callable is a condition task A condition task is a callable object constructible from std::function. */ template constexpr bool is_condition_task_v = std::is_invocable_r_v; /** @brief determines if a callable is a cudaflow task A cudaFlow task is a callable object constructible from std::function or std::function. */ template constexpr bool is_cudaflow_task_v = std::is_invocable_r_v || std::is_invocable_r_v; // ---------------------------------------------------------------------------- // Task // ---------------------------------------------------------------------------- /** @class Task @brief handle to a node in a task dependency graph A Task is handle to manipulate a node in a taskflow graph. It provides a set of methods for users to access and modify the attributes of the associated graph node without directly touching internal node data. */ class Task { friend class FlowBuilder; friend class Taskflow; friend class TaskView; public: /** @brief constructs an empty task */ Task() = default; /** @brief constructs the task with the copy of the other task */ Task(const Task& other); /** @brief replaces the contents with a copy of the other task */ Task& operator = (const Task&); /** @brief replaces the contents with a null pointer */ Task& operator = (std::nullptr_t); /** @brief compares if two tasks are associated with the same graph node */ bool operator == (const Task& rhs) const; /** @brief compares if two tasks are not associated with the same graph node */ bool operator != (const Task& rhs) const; /** @brief queries the name of the task */ const std::string& name() const; /** @brief queries the number of successors of the task */ size_t num_successors() const; /** @brief queries the number of predecessors of the task */ size_t num_dependents() const; /** @brief queries the number of strong dependents of the task */ size_t num_strong_dependents() const; /** @brief queries the number of weak dependents of the task */ size_t num_weak_dependents() const; /** @brief assigns a name to the task @param name a @std_string acceptable string @return @c *this */ Task& name(const std::string& name); /** @brief assigns a callable @tparam C callable type @param callable callable to construct one of the static, dynamic, condition, and cudaFlow tasks @return @c *this */ template Task& work(C&& callable); /** @brief creates a module task from a taskflow @param taskflow a taskflow object for the module @return @c *this */ Task& composed_of(Taskflow& taskflow); /** @brief adds precedence links from this to other tasks @tparam Ts parameter pack @param tasks one or multiple tasks @return @c *this */ template Task& precede(Ts&&... tasks); /** @brief adds precedence links from other tasks to this @tparam Ts parameter pack @param tasks one or multiple tasks @return @c *this */ template Task& succeed(Ts&&... tasks); /** @brief makes the task release this semaphore */ Task& release(Semaphore& semaphore); /** @brief makes the task acquire this semaphore */ Task& acquire(Semaphore& semaphore); /** @brief resets the task handle to null */ void reset(); /** @brief resets the associated work to a placeholder */ void reset_work(); /** @brief queries if the task handle points to a task node */ bool empty() const; /** @brief queries if the task has a work assigned */ bool has_work() const; /** @brief applies an visitor callable to each successor of the task */ template void for_each_successor(V&& visitor) const; /** @brief applies an visitor callable to each dependents of the task */ template void for_each_dependent(V&& visitor) const; /** @brief obtains a hash value of the underlying node */ size_t hash_value() const; /** @brief returns the task type */ TaskType type() const; /** @brief dumps the task through an output stream */ void dump(std::ostream& ostream) const; private: Task(Node*); Node* _node {nullptr}; }; // Constructor inline Task::Task(Node* node) : _node {node} { } // Constructor inline Task::Task(const Task& rhs) : _node {rhs._node} { } // Function: precede template Task& Task::precede(Ts&&... tasks) { (_node->_precede(tasks._node), ...); //_precede(std::forward(tasks)...); return *this; } // Function: succeed template Task& Task::succeed(Ts&&... tasks) { (tasks._node->_precede(_node), ...); //_succeed(std::forward(tasks)...); return *this; } // Function: composed_of inline Task& Task::composed_of(Taskflow& tf) { _node->_handle.emplace(&tf); return *this; } // Operator = inline Task& Task::operator = (const Task& rhs) { _node = rhs._node; return *this; } // Operator = inline Task& Task::operator = (std::nullptr_t ptr) { _node = ptr; return *this; } // Operator == inline bool Task::operator == (const Task& rhs) const { return _node == rhs._node; } // Operator != inline bool Task::operator != (const Task& rhs) const { return _node != rhs._node; } // Function: name inline Task& Task::name(const std::string& name) { _node->_name = name; return *this; } // Function: acquire inline Task& Task::acquire(Semaphore& s) { if(!_node->_semaphores) { _node->_semaphores.emplace(); } _node->_semaphores->to_acquire.push_back(&s); return *this; } // Function: release inline Task& Task::release(Semaphore& s) { if(!_node->_semaphores) { _node->_semaphores.emplace(); } _node->_semaphores->to_release.push_back(&s); return *this; } // Procedure: reset inline void Task::reset() { _node = nullptr; } // Procedure: reset_work inline void Task::reset_work() { _node->_handle.emplace(); } // Function: name inline const std::string& Task::name() const { return _node->_name; } // Function: num_dependents inline size_t Task::num_dependents() const { return _node->num_dependents(); } // Function: num_strong_dependents inline size_t Task::num_strong_dependents() const { return _node->num_strong_dependents(); } // Function: num_weak_dependents inline size_t Task::num_weak_dependents() const { return _node->num_weak_dependents(); } // Function: num_successors inline size_t Task::num_successors() const { return _node->num_successors(); } // Function: empty inline bool Task::empty() const { return _node == nullptr; } // Function: has_work inline bool Task::has_work() const { return _node ? _node->_handle.index() != 0 : false; } // Function: task_type inline TaskType Task::type() const { return static_cast(_node->_handle.index()); } // Function: for_each_successor template void Task::for_each_successor(V&& visitor) const { for(size_t i=0; i<_node->_successors.size(); ++i) { visitor(Task(_node->_successors[i])); } } // Function: for_each_dependent template void Task::for_each_dependent(V&& visitor) const { for(size_t i=0; i<_node->_dependents.size(); ++i) { visitor(Task(_node->_dependents[i])); } } // Function: hash_value inline size_t Task::hash_value() const { return std::hash{}(_node); } // Procedure: dump inline void Task::dump(std::ostream& os) const { os << "task "; if(name().empty()) os << _node; else os << name(); os << " [type=" << task_type_to_string(type()) << ']'; } // Function: work template Task& Task::work(C&& c) { if constexpr(is_static_task_v) { _node->_handle.emplace(std::forward(c)); } else if constexpr(is_dynamic_task_v) { _node->_handle.emplace(std::forward(c)); } else if constexpr(is_condition_task_v) { _node->_handle.emplace(std::forward(c)); } else if constexpr(is_cudaflow_task_v) { _node->_handle.emplace(std::forward(c)); } else { static_assert(dependent_false_v, "invalid task callable"); } return *this; } // ---------------------------------------------------------------------------- // global ostream // ---------------------------------------------------------------------------- /** @brief overload of ostream inserter operator for cudaTask */ inline std::ostream& operator << (std::ostream& os, const Task& task) { task.dump(os); return os; } // ---------------------------------------------------------------------------- /** @class TaskView @brief class to access task information from the observer interface */ class TaskView { friend class Executor; public: /** @brief queries the name of the task */ const std::string& name() const; /** @brief queries the number of successors of the task */ size_t num_successors() const; /** @brief queries the number of predecessors of the task */ size_t num_dependents() const; /** @brief queries the number of strong dependents of the task */ size_t num_strong_dependents() const; /** @brief queries the number of weak dependents of the task */ size_t num_weak_dependents() const; /** @brief applies an visitor callable to each successor of the task */ template void for_each_successor(V&& visitor) const; /** @brief applies an visitor callable to each dependents of the task */ template void for_each_dependent(V&& visitor) const; /** @brief queries the task type */ TaskType type() const; private: TaskView(const Node&); TaskView(const TaskView&) = default; const Node& _node; }; // Constructor inline TaskView::TaskView(const Node& node) : _node {node} { } // Function: name inline const std::string& TaskView::name() const { return _node._name; } // Function: num_dependents inline size_t TaskView::num_dependents() const { return _node.num_dependents(); } // Function: num_strong_dependents inline size_t TaskView::num_strong_dependents() const { return _node.num_strong_dependents(); } // Function: num_weak_dependents inline size_t TaskView::num_weak_dependents() const { return _node.num_weak_dependents(); } // Function: num_successors inline size_t TaskView::num_successors() const { return _node.num_successors(); } // Function: type inline TaskType TaskView::type() const { return static_cast(_node._handle.index()); } // Function: for_each_successor template void TaskView::for_each_successor(V&& visitor) const { for(size_t i=0; i<_node._successors.size(); ++i) { visitor(TaskView(_node._successors[i])); } } // Function: for_each_dependent template void TaskView::for_each_dependent(V&& visitor) const { for(size_t i=0; i<_node._dependents.size(); ++i) { visitor(TaskView(_node._dependents[i])); } } } // end of namespace tf. --------------------------------------------------- namespace std { /** @struct hash @brief hash specialization for std::hash */ template <> struct hash { auto operator() (const tf::Task& task) const noexcept { return task.hash_value(); } }; } // end of namespace std ----------------------------------------------------