├── examples ├── Tupfile └── ping_pong.cpp ├── Tupfile ├── .gitmodules ├── cycle.cpp ├── cycle.hpp ├── .travis.yml ├── Tuprules.tup ├── task_node.hpp ├── task_node.cpp ├── task.hpp ├── future.hpp ├── abstract_task.cpp ├── scheduler.cpp ├── scheduler.hpp ├── abstract_task.hpp ├── README.md └── toposort.hpp /examples/Tupfile: -------------------------------------------------------------------------------- 1 | include_rules 2 | 3 | : foreach *.cpp | ../ |> $(CC) $(CFLAGS) %f -o %o -pthread -L .. -l cosche |> %B -------------------------------------------------------------------------------- /Tupfile: -------------------------------------------------------------------------------- 1 | include_rules 2 | 3 | : foreach *.cpp | *.hpp |> !cc |> %B.o 4 | : $(OBJ) | ./context/ |> ar crs %o %f |> libcosche.a 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "context"] 2 | path = context 3 | url = https://github.com/matovitch/context 4 | [submodule "RobinHoodHashtable"] 5 | path = RobinHoodHashtable 6 | url = https://github.com/matovitch/RobinHoodHashtable 7 | -------------------------------------------------------------------------------- /cycle.cpp: -------------------------------------------------------------------------------- 1 | #include "cycle.hpp" 2 | 3 | #include "abstract_task.hpp" 4 | 5 | #include 6 | 7 | namespace cosche 8 | { 9 | 10 | Cycle::Cycle(const std::vector& cycle) : _cycle(cycle) {} 11 | 12 | void Cycle::operator()() const 13 | { 14 | for (auto& node : _cycle) 15 | { 16 | node._task->onCycle(); 17 | } 18 | } 19 | 20 | } // end cosche namespace -------------------------------------------------------------------------------- /cycle.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CYCLE_H__ 2 | #define __CYCLE_H__ 3 | 4 | #include "task_node.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace cosche 10 | { 11 | 12 | struct Cycle : std::exception 13 | { 14 | Cycle(const std::vector& cycle); 15 | 16 | void operator()() const; 17 | 18 | std::vector _cycle; 19 | }; 20 | 21 | } // end cosche namespace 22 | 23 | #endif // __CYCLE_H__ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c++ 2 | compiler: gcc 3 | os: linux 4 | 5 | sudo: required 6 | 7 | before_install: 8 | - sudo apt-add-repository 'deb http://ppa.launchpad.net/anatol/tup/ubuntu precise main' 9 | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y 10 | - sudo add-apt-repository ppa:boost-latest/ppa -y 11 | - sudo apt-get update 12 | - sudo apt-get install tup 13 | - sudo apt-get install g++-5 14 | - sudo apt-get install libboost1.55-all-dev 15 | 16 | install: tup init 17 | 18 | script: tup -------------------------------------------------------------------------------- /Tuprules.tup: -------------------------------------------------------------------------------- 1 | ROOT = $(TUP_CWD) 2 | CC = clang++ 3 | 4 | CFLAGS = 5 | CFLAGS += -Wall 6 | CFLAGS += -pedantic 7 | CFLAGS += -std=c++14 8 | 9 | ifeq (@(TUP_DEBUG),y) 10 | CFLAGS += -g 11 | else 12 | CFLAGS += -g 13 | endif 14 | 15 | CFLAGS += -I $(ROOT) 16 | CFLAGS += -I $(ROOT)/context/include 17 | CFLAGS += -I $(ROOT)/RobinHoodHashtable 18 | 19 | !cc = |> $(CC) $(CFLAGS) -c %f -o %o |> %B.o 20 | 21 | OBJ = $(ROOT)/*.o 22 | OBJ += $(ROOT)/context/src/*.o 23 | OBJ += $(ROOT)/context/src/asm/*.o 24 | OBJ += $(ROOT)/context/src/posix/*.o 25 | -------------------------------------------------------------------------------- /task_node.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_NODE_H__ 2 | #define __TASK_NODE_H__ 3 | 4 | #include 5 | 6 | namespace cosche 7 | { 8 | 9 | class AbstractTask; 10 | 11 | struct TaskNode 12 | { 13 | TaskNode(AbstractTask* task); 14 | 15 | operator AbstractTask*() const; 16 | 17 | AbstractTask* _task; 18 | }; 19 | 20 | bool operator==(const TaskNode& lhs, 21 | const TaskNode& rhs); 22 | 23 | struct TaskNodeHasher 24 | { 25 | std::size_t operator()(const TaskNode& n) const; 26 | }; 27 | 28 | } // end cosche namespace 29 | 30 | #endif // __TASK_NODE_H__ -------------------------------------------------------------------------------- /task_node.cpp: -------------------------------------------------------------------------------- 1 | #include "task_node.hpp" 2 | 3 | #include "abstract_task.hpp" 4 | 5 | namespace cosche 6 | { 7 | 8 | TaskNode::TaskNode(AbstractTask* task) : _task(task) {} 9 | 10 | TaskNode::operator AbstractTask*() const { return _task; } 11 | 12 | bool operator==(const TaskNode& lhs, 13 | const TaskNode& rhs) 14 | { 15 | return (lhs._task->id() == rhs._task->id()); 16 | } 17 | 18 | std::size_t TaskNodeHasher::operator()(const TaskNode& n) const 19 | { 20 | const std::size_t id = n._task->id(); 21 | 22 | const std::size_t base = 0x00000100000001b3; 23 | std::size_t hash = 0xcbf29ce484222325; 24 | 25 | hash = (hash ^ ((id << 0x00) + (id >> 0x20))) * base; 26 | hash = (hash ^ ((id << 0x08) + (id >> 0x18))) * base; 27 | hash = (hash ^ ((id << 0x10) + (id >> 0x10))) * base; 28 | hash = (hash ^ ((id << 0x18) + (id >> 0x08))) * base; 29 | 30 | return hash; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /task.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __TASK_H__ 2 | #define __TASK_H__ 3 | 4 | #include "abstract_task.hpp" 5 | #include "scheduler.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace cosche 11 | { 12 | 13 | template 14 | class Task : public AbstractTask 15 | { 16 | 17 | public: 18 | 19 | Task(Scheduler& scheduler) : 20 | AbstractTask(scheduler), 21 | _task(std::make_shared>()) 22 | {} 23 | 24 | template 25 | void operator()(Fn&& fn, Args&&... args) 26 | { 27 | *_task = std::packaged_task(std::bind(fn, args...)); 28 | } 29 | 30 | void run() 31 | { 32 | if (_task->valid()) 33 | { 34 | _task->operator()(); 35 | } 36 | } 37 | 38 | std::size_t id() const 39 | { 40 | return reinterpret_cast(_task.get()); 41 | } 42 | 43 | void recycle() 44 | { 45 | scheduler().recycle(this); 46 | } 47 | 48 | auto getFuture() { return _task->get_future(); } 49 | 50 | private: 51 | 52 | std::shared_ptr> _task; 53 | }; 54 | 55 | } // end cosche namespace 56 | 57 | #endif // __TASK_H__ -------------------------------------------------------------------------------- /future.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __ABSTRACT_FUTURE_H__ 2 | #define __ABSTRACT_FUTURE_H__ 3 | 4 | #include 5 | #include 6 | 7 | namespace cosche 8 | { 9 | 10 | struct AbstractFuture 11 | { 12 | virtual bool ready() const = 0; 13 | }; 14 | 15 | template 16 | struct Future : std::future, AbstractFuture 17 | { 18 | Future(std::future&& future) : std::future(std::move(future)) {} 19 | 20 | bool ready() const 21 | { 22 | return std::future::wait_for(std::chrono::seconds(0)) == 23 | std::future_status::ready; 24 | } 25 | }; 26 | 27 | template 28 | struct ScopedFuture : std::future, AbstractFuture 29 | { 30 | template 31 | ScopedFuture(std::future&& future, 32 | const std::chrono::duration& timeoutDuration) : 33 | std::future(std::move(future)), 34 | _timeout(std::chrono::steady_clock::now() + timeoutDuration) {} 35 | 36 | bool ready() const 37 | { 38 | return std::future::wait_until(_timeout) != std::future_status::deferred; 39 | } 40 | 41 | const std::chrono::steady_clock::time_point _timeout; 42 | }; 43 | 44 | } // end cosche namespace 45 | 46 | #endif -------------------------------------------------------------------------------- /abstract_task.cpp: -------------------------------------------------------------------------------- 1 | #include "abstract_task.hpp" 2 | 3 | #include "scheduler.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace cosche 11 | { 12 | 13 | AbstractTask::AbstractTask(Scheduler& scheduler) : 14 | _scheduler(scheduler), 15 | _context(std::make_shared(start)), 16 | _onCycle(std::make_shared> 17 | ( 18 | [&]() 19 | { 20 | std::cerr << "The task " << id() << " belongs to a cycle." << std::endl; 21 | } 22 | )) 23 | {} 24 | 25 | Context AbstractTask::start(Context context, AbstractTask* task) 26 | { 27 | *(task->_context) = std::move(context); 28 | 29 | task->run(); 30 | 31 | task->_scheduler.erase(task); 32 | 33 | task->recycle(); 34 | 35 | context = std::move(*(task->_context)); 36 | 37 | return context; 38 | } 39 | 40 | void AbstractTask::attach(AbstractTask& task) 41 | { 42 | _scheduler.attach(this, &task); 43 | 44 | if (_scheduler.running()) 45 | { 46 | *_context = std::get<0>((*_context)(this)); 47 | } 48 | } 49 | 50 | void AbstractTask::detach(AbstractTask& task) 51 | { 52 | _scheduler.detach(this, &task); 53 | } 54 | 55 | void AbstractTask::release() 56 | { 57 | _scheduler.release(this); 58 | } 59 | 60 | void AbstractTask::onCycle() 61 | { 62 | if (_onCycle->valid()) 63 | { 64 | _onCycle->operator()(); 65 | } 66 | } 67 | 68 | Scheduler& AbstractTask::scheduler() const { return _scheduler; } 69 | 70 | } // end cosche namespace 71 | -------------------------------------------------------------------------------- /scheduler.cpp: -------------------------------------------------------------------------------- 1 | #include "scheduler.hpp" 2 | 3 | #include "abstract_task.hpp" 4 | #include "future.hpp" 5 | #include "cycle.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace cosche 12 | { 13 | 14 | Scheduler::Scheduler() : _running(false) {} 15 | 16 | void Scheduler::run() 17 | { 18 | _running = true; 19 | 20 | AbstractTask* task; 21 | 22 | while (!cyclic() && (!empty() || waiting()) && !_throwing) 23 | { 24 | if (!empty()) 25 | { 26 | task = top()._task; 27 | *(task->_context) = std::get<0>((*(task->_context))(task)); 28 | } 29 | 30 | checkFutures(); 31 | } 32 | 33 | if (cyclic()) 34 | { 35 | throw Cycle(cycle()); 36 | } 37 | 38 | if (_throwing) 39 | { 40 | std::rethrow_exception(_throwing); 41 | } 42 | 43 | _running = false; 44 | } 45 | 46 | bool Scheduler::running() const { return _running; } 47 | 48 | void Scheduler::checkFutures() 49 | { 50 | auto it = _futures.begin(); 51 | 52 | while (it != _futures.end()) 53 | { 54 | if (it->first->ready()) 55 | { 56 | wake(it->second); 57 | it->second->_future = it->first; 58 | it = _futures.erase(it); 59 | } 60 | else 61 | { 62 | it++; 63 | } 64 | } 65 | } 66 | 67 | void Scheduler::haltWaitingFuture(std::shared_ptr&& future, 68 | AbstractTask* task) 69 | { 70 | halt(task); 71 | _futures[std::move(future)] = task; 72 | } 73 | 74 | } // end cosche namespace 75 | -------------------------------------------------------------------------------- /examples/ping_pong.cpp: -------------------------------------------------------------------------------- 1 | #include "scheduler.hpp" 2 | #include "cycle.hpp" 3 | #include "task.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int main() 11 | { 12 | cosche::Scheduler scheduler; 13 | 14 | //scheduler.reserveTasks(2); 15 | 16 | auto ping = scheduler.getNewTask(); 17 | auto pong = scheduler.getNewTask(); 18 | 19 | ping 20 | ( 21 | [&]() 22 | { 23 | std::cout << "ping" << std::endl; 24 | pong.detach(ping); 25 | ping.attach(pong); 26 | std::cout << "ping" << std::endl; 27 | ping.release(); 28 | 29 | // DO NOT use as it does not register 30 | // an edge in the dependency graph 31 | ping.wait(pong.getFuture()); 32 | 33 | ping.waitFor(std::chrono::seconds(1), 34 | std::async(std::launch::async, 35 | []() 36 | { 37 | std::this_thread::sleep_for(std::chrono::seconds(2)); 38 | std::cout << "task" << std::endl; 39 | } 40 | ) 41 | ); 42 | 43 | std::cout << "ping" << std::endl; 44 | } 45 | ); 46 | 47 | pong 48 | ( 49 | [&]() 50 | { 51 | std::cout << "pong" << std::endl; 52 | //pong.throwing(std::runtime_error("throw !")); 53 | ping.detach(pong); 54 | pong.attach(ping); 55 | 56 | std::cout << "pong" << std::endl; 57 | } 58 | ); 59 | 60 | ping.onCycle([]() { std::cerr << "ping belongs to a cycle" << std::endl; }); 61 | pong.onCycle([]() { std::cerr << "pong belongs to a cycle" << std::endl; }); 62 | 63 | pong.attach(ping); 64 | 65 | try 66 | { 67 | scheduler.run(); 68 | } 69 | catch (const cosche::Cycle& cycle) 70 | { 71 | std::cerr << "The scheduler ended on a cycle !" << std::endl; 72 | 73 | cycle(); 74 | 75 | exit(EXIT_FAILURE); 76 | } 77 | 78 | return EXIT_SUCCESS; 79 | } 80 | -------------------------------------------------------------------------------- /scheduler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __SCHEDULER_H__ 2 | #define __SCHEDULER_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "task_node.hpp" 10 | #include "toposort.hpp" 11 | 12 | namespace cosche 13 | { 14 | 15 | class AbstractTask; 16 | struct AbstractFuture; 17 | 18 | template 19 | class Task; 20 | 21 | class Scheduler : Toposort 22 | { 23 | 24 | friend AbstractTask; 25 | 26 | template 27 | friend class Task; 28 | 29 | public: 30 | 31 | Scheduler(); 32 | 33 | template 34 | Task& getNewTask() 35 | { 36 | std::vector& recycleds = _recycleds[std::type_index(typeid(Rt))]; 37 | 38 | if (recycleds.empty()) 39 | { 40 | _tasks.emplace_back(std::make_shared>(*this)); 41 | 42 | push(_tasks.back().get()); 43 | 44 | return static_cast&>(*(_tasks.back())); 45 | } 46 | else 47 | { 48 | Task& task = static_cast&>(*(recycleds.back())); 49 | use(recycleds.back()); 50 | recycleds.pop_back(); 51 | return task; 52 | } 53 | } 54 | 55 | template 56 | void reserveTasks(std::size_t nTasks) 57 | { 58 | for (std::size_t i = 0; i < nTasks; i++) 59 | { 60 | _tasks.emplace_back(std::make_shared>(*this)); 61 | 62 | plan(_tasks.back().get()); 63 | 64 | _recycleds[std::type_index(typeid(Rt))].push_back(_tasks.back().get()); 65 | } 66 | } 67 | 68 | void run(); 69 | 70 | private: 71 | 72 | bool running() const; 73 | 74 | void checkFutures(); 75 | 76 | void haltWaitingFuture(std::shared_ptr&& future, 77 | AbstractTask* task); 78 | 79 | template 80 | void recycle(Task* task) 81 | { 82 | _recycleds[std::type_index(typeid(Rt))].push_back(task); 83 | } 84 | 85 | bool _running; 86 | std::exception_ptr _throwing; 87 | std::vector> _tasks; 88 | std::unordered_map> _recycleds; 89 | std::unordered_map, AbstractTask*> _futures; 90 | }; 91 | 92 | } // end cosche namespace 93 | 94 | #endif // __SCHEDULER_H__ -------------------------------------------------------------------------------- /abstract_task.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __ABSTRACT_TASK_H__ 2 | #define __ABSTRACT_TASK_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "scheduler.hpp" 10 | #include "future.hpp" 11 | 12 | namespace cosche 13 | { 14 | 15 | class AbstractTask; 16 | 17 | typedef boost::context::execution_context Context; 18 | 19 | class AbstractTask 20 | { 21 | 22 | friend Scheduler; 23 | 24 | public: 25 | 26 | AbstractTask(Scheduler& scheduler); 27 | 28 | AbstractTask(Scheduler&& scheduler) = delete; 29 | 30 | virtual void run() = 0; 31 | 32 | virtual void recycle() = 0; 33 | 34 | virtual std::size_t id() const = 0; 35 | 36 | void attach(AbstractTask& task); 37 | 38 | void detach(AbstractTask& task); 39 | 40 | void release(); 41 | 42 | void onCycle(); 43 | 44 | Scheduler& scheduler() const; 45 | 46 | template 47 | void throwing(const E& e) 48 | { 49 | try 50 | { 51 | throw e; 52 | } 53 | catch (...) 54 | { 55 | _scheduler._throwing = std::current_exception(); 56 | } 57 | } 58 | 59 | template 60 | void onCycle(Fn&& fn, Args&&... args) 61 | { 62 | *_onCycle = std::packaged_task(std::bind(fn, args...)); 63 | } 64 | 65 | template 66 | T wait(std::future&& future) 67 | { 68 | if (_scheduler.running()) 69 | { 70 | _scheduler.haltWaitingFuture( 71 | std::make_shared>(std::move(future)), 72 | this); 73 | 74 | *_context = std::get<0>((*_context)(this)); 75 | } 76 | 77 | return static_cast&>(*_future).get(); 78 | } 79 | 80 | template 81 | std::future& waitFor(const std::chrono::duration& timeoutDuration, 82 | std::future&& future) 83 | { 84 | if (_scheduler.running()) 85 | { 86 | _scheduler.haltWaitingFuture( 87 | std::make_shared>(std::move(future), 88 | timeoutDuration), 89 | this); 90 | 91 | *_context = std::get<0>((*_context)(this)); 92 | } 93 | 94 | return static_cast&>( 95 | static_cast&>(*_future) 96 | ); 97 | } 98 | 99 | static Context start(Context context, AbstractTask* task); 100 | 101 | private: 102 | 103 | Scheduler& _scheduler; 104 | std::shared_ptr _context; 105 | std::shared_ptr _future; 106 | std::shared_ptr> _onCycle; 107 | }; 108 | 109 | } // end cosche namespace 110 | 111 | #endif // __ABSTRACT_TASK_H__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cosche [![Build Status](https://travis-ci.org/matovitch/cosche.svg?branch=master)](https://travis-ci.org/matovitch/cosche) 2 | 3 | >*Disclaimer:* This is an omega version using a greek modulo versioning (the next iteration may be an alpha ;-)) ! I wouldn't recommend using it for anything else than a toy project. Furthermore, this is my first project in this realm of concurrency so I have probably done some huge mistakes and will gladly accept any reviews and feedbacks. 4 | 5 | ## What is this ? 6 | 7 | *Cosche* is a small dependency-based coroutine scheduler able to register ```std::future(s)``` in its main loop to interact with threads and other asynchronous I/O libraries. It is based on [boost::context](https://github.com/boostorg/context) written by Oliver Kowalke and I got the idea of building this by watching [an amazing conference](https://www.youtube.com/watch?v=AhR4PSExnqk) from Amaury Sechet on the [Stupid D Compiler](https://github.com/SDC-Developers/SDC). So thanks to both of you ! :) 8 | 9 | ##Features and limitations 10 | 11 |   ➕ very small and easy to use 12 |   ➕ threads and async IO interactions via futures 13 |   ➕ support arbitrary dependency graph and detect cycles 14 | 15 |   ➖ not optimized for speed 16 |   ➖ each scheduler is bound to a unique thread 17 | 18 | ## Show me some code ! 19 | 20 | ```c++ 21 | cosche::Scheduler scheduler; 22 | 23 | //scheduler.reserveTasks(2); 24 | 25 | auto ping = scheduler.getNewTask(); 26 | auto pong = scheduler.getNewTask(); 27 | 28 | ping 29 | ( 30 | [&]() 31 | { 32 | std::cout << "ping" << std::endl; 33 | pong.detach(ping); 34 | ping.attach(pong); 35 | std::cout << "ping" << std::endl; 36 | ping.release(); 37 | 38 | // DO NOT use as it does not register 39 | // an edge in the dependency graph 40 | ping.wait(pong.getFuture()); 41 | 42 | ping.waitFor(std::chrono::seconds(1), 43 | std::async(std::launch::async, 44 | []() 45 | { 46 | std::this_thread::sleep_for(std::chrono::seconds(2)); 47 | std::cout << "task" << std::endl; 48 | } 49 | ) 50 | ); 51 | 52 | std::cout << "ping" << std::endl; 53 | } 54 | ); 55 | 56 | pong 57 | ( 58 | [&]() 59 | { 60 | std::cout << "pong" << std::endl; 61 | //pong.throwing(std::runtime_error("throw !")); 62 | ping.detach(pong); 63 | pong.attach(ping); 64 | 65 | std::cout << "pong" << std::endl; 66 | } 67 | ); 68 | 69 | ping.onCycle([]() { std::cerr << "ping belongs to a cycle" << std::endl; }); 70 | pong.onCycle([]() { std::cerr << "pong belongs to a cycle" << std::endl; }); 71 | 72 | pong.attach(ping); 73 | 74 | try 75 | { 76 | scheduler.run(); 77 | } 78 | catch (const cosche::Cycle& cycle) 79 | { 80 | std::cerr << "The scheduler ended on a cycle !" << std::endl; 81 | 82 | cycle(); 83 | } 84 | ``` 85 | 86 | The output of the above program will be 87 | ``` 88 | ping 89 | pong 90 | ping 91 | pong 92 | [wait for 1 second] 93 | ping 94 | [wait for 1 second] 95 | task 96 | ``` 97 | 98 | ## Overview 99 | 100 | ```c++ 101 | namespace cosche 102 | { 103 | struct Scheduler 104 | { 105 | template Task& Scheduler::getNewTask(); 106 | template void reserveTasks(std::size_t); 107 | void Scheduler::run(); 108 | }; 109 | } 110 | ``` 111 | 112 | The scheduler allows to create a task with a given return type (```getNewTask```) reserve them for faster creation (```reserveTasks```) and launch the execution of registered tasks (```run```). 113 | 114 | ```c++ 115 | namespace cosche 116 | { 117 | template 118 | struct Task 119 | { 120 | virtual std::size_t id() const; 121 | Scheduler& scheduler() const; 122 | template void operator()(Fn&& fn, Args&&... args); 123 | void attach(AbstractTask& task); 124 | void detach(AbstractTask& task); 125 | void release(); 126 | template T wait(std::future&& future); 127 | template std::future& waitFor(const std::chrono::duration& timeoutDuration, std::future&& future); 128 | template void onCycle(Fn&& fn, Args&&... args); 129 | template void throwing(const E& e); 130 | std::future getFuture() { return _task->get_future(); } 131 | }; 132 | } 133 | ``` 134 | For a given task you can get an ```id``` and the ```scheduler``` it was registered in. The content of a task is defined with ```operator()```. You can ```attach``` and ```detach``` them to other tasks in the graph of their *common* scheduler. A task can detach all the tasks that attached it via ```release```. A task can ```wait``` or ```waitFor``` a given time on a future. You can defined a task to be executed in case it is part of a cycle (```onCycle```). And throw an exception outside of the scheduler run (```throwing```). You can catch normal ```throw``` with ```getFuture``` or synchronize with tasks defined within an other scheduler. 135 | 136 | - ####TODOs 137 | 138 | - add exemples/tests/banchmarks 139 | - think about what to add 140 | - fill this README 141 | -------------------------------------------------------------------------------- /toposort.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __TOPOSORT_H__ 2 | #define __TOPOSORT_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "RobinHoodHashtable.hpp" 10 | 11 | namespace cosche 12 | { 13 | 14 | template 15 | struct NodePtrHasher 16 | { 17 | std::size_t operator()(const N& node) const 18 | { 19 | return H()(node->_t); 20 | } 21 | }; 22 | 23 | template > 24 | using NodePtrSet = RobinHoodHashtable, E>; 25 | 26 | template 27 | struct Node 28 | { 29 | Node(const T& t) : _t(t) {} 30 | 31 | T _t; 32 | 33 | NodePtrSet*, H> _ins; 34 | NodePtrSet*, H> _outs; 35 | }; 36 | 37 | template 38 | struct NodePtrEquals 39 | { 40 | bool operator() (const std::unique_ptr>& lhs, 41 | const std::unique_ptr>& rhs) const 42 | { 43 | return lhs->_t == rhs->_t; 44 | } 45 | }; 46 | 47 | template > 48 | struct Toposort 49 | { 50 | 51 | Toposort() { clear(); } 52 | 53 | void clear() 54 | { 55 | _heap.clear(); 56 | _pendings.clear(); 57 | _blockeds.clear(); 58 | _waitings.clear(); 59 | _planneds.clear(); 60 | } 61 | 62 | void push(const T& t) 63 | { 64 | std::unique_ptr> node(std::make_unique>(T(t))); 65 | _pendings.insert(&(*node)); 66 | _heap.insert(std::move(node)); 67 | } 68 | 69 | void plan(const T& t) 70 | { 71 | std::unique_ptr> node(std::make_unique>(T(t))); 72 | _planneds.insert(&(*node)); 73 | _heap.insert(std::move(node)); 74 | } 75 | 76 | void use(const T& t) 77 | { 78 | auto fit = _heap.find(std::make_unique>(t)); 79 | 80 | if (fit != _heap.end()) 81 | { 82 | Node* const tNode = &(*(*fit)); 83 | 84 | if (_planneds.find(tNode) != _planneds.end()) 85 | { 86 | _planneds.erase(tNode); 87 | _pendings.insert(tNode); 88 | } 89 | } 90 | } 91 | 92 | void detach(const T& lhs, 93 | const T& rhs) 94 | { 95 | auto lfit = _heap.find(std::make_unique>(lhs)); 96 | auto rfit = _heap.find(std::make_unique>(rhs)); 97 | 98 | if (lfit != _heap.end() && 99 | rfit != _heap.end()) 100 | { 101 | Node* const lhsNode = &(*(*lfit)); 102 | Node* const rhsNode = &(*(*rfit)); 103 | 104 | lhsNode->_ins.erase(rhsNode); 105 | rhsNode->_outs.erase(lhsNode); 106 | 107 | if (lhsNode->_ins.empty() && 108 | _waitings.find(lhsNode) == _waitings.end()) 109 | { 110 | _blockeds.erase(lhsNode); 111 | _pendings.insert(lhsNode); 112 | } 113 | } 114 | } 115 | 116 | void attach(const T& lhs, 117 | const T& rhs) 118 | { 119 | auto lfit = _heap.find(std::make_unique>(lhs)); 120 | auto rfit = _heap.find(std::make_unique>(rhs)); 121 | 122 | if (lfit != _heap.end() && 123 | rfit != _heap.end()) 124 | { 125 | Node* const lhsNode = &(*(*lfit)); 126 | Node* const rhsNode = &(*(*rfit)); 127 | 128 | if (lhsNode->_ins.empty() && 129 | _waitings.find(lhsNode) == _waitings.end()) 130 | { 131 | _blockeds.insert(lhsNode); 132 | _pendings.erase(lhsNode); 133 | } 134 | 135 | lhsNode->_ins.insert(rhsNode); 136 | rhsNode->_outs.insert(lhsNode); 137 | } 138 | } 139 | 140 | void erase(const T& t) 141 | { 142 | auto fit = _heap.find(std::make_unique>(t)); 143 | 144 | if (fit != _heap.end()) 145 | { 146 | Node* const tNode = &(*(*fit)); 147 | 148 | for (Node* const out : tNode->_outs) 149 | { 150 | out->_ins.erase(tNode); 151 | 152 | if (out->_ins.empty()) 153 | { 154 | _pendings.insert(out); 155 | _blockeds.erase(out); 156 | } 157 | } 158 | 159 | _pendings.erase(tNode); 160 | _blockeds.erase(tNode); 161 | _planneds.insert(tNode); 162 | } 163 | } 164 | 165 | void release(const T& t) 166 | { 167 | auto fit = _heap.find(std::make_unique>(t)); 168 | 169 | if (fit != _heap.end()) 170 | { 171 | Node* const tNode = &(*(*fit)); 172 | 173 | for (Node* const out : tNode->_outs) 174 | { 175 | out->_ins.erase(tNode); 176 | 177 | if (out->_ins.empty() && 178 | _waitings.find(out) == _waitings.end()) 179 | { 180 | _blockeds.erase(out); 181 | _pendings.insert(out); 182 | } 183 | } 184 | 185 | tNode->_outs.clear(); 186 | } 187 | } 188 | 189 | void halt(const T& t) 190 | { 191 | auto fit = _heap.find(std::make_unique>(t)); 192 | 193 | if (fit != _heap.end()) 194 | { 195 | Node* const tNode = &(*(*fit)); 196 | 197 | _waitings.insert(tNode); 198 | 199 | _blockeds.erase(tNode); 200 | _pendings.erase(tNode); 201 | } 202 | } 203 | 204 | void wake(const T& t) 205 | { 206 | auto fit = _heap.find(std::make_unique>(t)); 207 | 208 | if (fit != _heap.end()) 209 | { 210 | Node* const tNode = &(*(*fit)); 211 | 212 | _waitings.erase(tNode); 213 | 214 | if (tNode->_ins.empty()) 215 | { 216 | _pendings.insert(tNode); 217 | } 218 | else 219 | { 220 | _blockeds.insert(tNode); 221 | } 222 | } 223 | } 224 | 225 | T& top() { return (*(_pendings.begin()))->_t; } 226 | const T& ctop() const { return (*(_pendings.begin()))->_t; } 227 | 228 | bool empty() const { return _pendings.empty(); } 229 | bool waiting() const { return !_waitings.empty(); } 230 | 231 | bool cyclic() const 232 | { 233 | return empty() && !waiting() && !_blockeds.empty(); 234 | } 235 | 236 | const std::vector& cycle() 237 | { 238 | _cycle.clear(); 239 | 240 | if (cyclic()) 241 | { 242 | Node* node_1 = *(_blockeds.begin()); 243 | NodePtrSet*, H> visiteds; 244 | 245 | while (visiteds.find(node_1) == visiteds.end()) 246 | { 247 | visiteds.insert(node_1); 248 | node_1 = *(node_1->_ins.begin()); 249 | } 250 | 251 | Node* node_2 = node_1; 252 | 253 | do 254 | { 255 | _cycle.push_back(node_2->_t); 256 | node_2 = *(node_2->_ins.begin()); 257 | } while (node_2 != node_1); 258 | } 259 | 260 | return _cycle; 261 | } 262 | 263 | NodePtrSet*, H> _pendings; 264 | NodePtrSet*, H> _blockeds; 265 | NodePtrSet*, H> _waitings; 266 | NodePtrSet*, H> _planneds; 267 | 268 | NodePtrSet>, H, NodePtrEquals> _heap; 269 | 270 | std::vector _cycle; 271 | }; 272 | 273 | } // end cosche namespace 274 | 275 | #endif // end __TOPOSORT_H__ 276 | --------------------------------------------------------------------------------