#pragma once #include #include #include "Task.h" #include "Event.h" namespace clice::async { struct none {}; template ::value_type> using task_value_t = std::conditional_t, none, V>; template auto gather(Tasks&&... tasks) -> Task...>> { constexpr static std::size_t count = sizeof...(Tasks); Event event; std::size_t finished = 0; auto run_task = [&](auto& task) -> Task> { using V = typename std::remove_cvref_t::value_type; if constexpr(std::is_void_v) { co_await task; /// Check if all tasks are finished. If so, set the event to /// resume the gather handle. finished += 1; if(finished == count) { event.set(); } co_return none{}; } else { auto result = co_await task; finished += 1; if(finished == count) { event.set(); } co_return std::move(result); } }; auto schedule_task = [&](auto& task) { auto core = run_task(task); core.schedule(); return core; }; std::tuple all = {schedule_task(tasks)...}; /// Wait for all tasks to finish. co_await event; /// Return the results of all tasks. co_return [&](std::index_sequence) { return std::make_tuple(std::get(all).result()...); }(std::make_index_sequence{}); } /// Run the tasks in parallel and return the results. template auto run(Tasks&&... tasks) { auto core = gather(std::forward(tasks)...); core.schedule(); async::run(); assert(core.done() && "run: not done"); return core.result(); } template requires requires(Coroutine coroutine, ranges::range_value_t value) { { coroutine(value) } -> std::same_as>; } Task gather(Range&& range, Coroutine&& coroutine, std::size_t concurrency = std::thread::hardware_concurrency()) { std::vector> tasks; tasks.reserve(concurrency); auto iter = ranges::begin(range); auto end = ranges::end(range); Event event; std::size_t finished = 0; bool cancelled = false; auto run_task = [&](auto& value) -> async::Task<> { /// Execute the first task. auto task = coroutine(value); /// If any task fails, cancel all tasks and return false. if(auto result = co_await task; !result) { for(auto& task: tasks) { task.cancel(); task.dispose(); } cancelled = true; event.set(); co_return; } finished += 1; /// Check if still have tasks to run. If so, run the next task. while(iter != end) { auto task = coroutine(*iter); iter++; finished -= 1; if(auto result = co_await task; !result) { for(auto& task: tasks) { task.cancel(); task.dispose(); } cancelled = true; event.set(); co_return; } finished += 1; } /// Check if all tasks are finished. If so, set the event to /// resume the gather handle. if(finished == tasks.size()) { event.set(); } }; /// Fill tasks. while(iter != end && tasks.size() < concurrency) { tasks.emplace_back(run_task(*iter)); tasks.back().schedule(); iter++; } co_await event; co_return !cancelled; } } // namespace clice::async