|
| 1 | +/////////////////////////////////////////////////////////////////////////////// |
| 2 | +// Copyright (c) Lewis Baker |
| 3 | +// Licenced under MIT license. See LICENSE.txt for details. |
| 4 | +/////////////////////////////////////////////////////////////////////////////// |
| 5 | +#ifndef CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED |
| 6 | +#define CPPCORO_ROUND_ROBIN_SCHEDULER_HPP_INCLUDED |
| 7 | + |
| 8 | +#include <cppcoro/config.hpp> |
| 9 | + |
| 10 | +#include <experimental/coroutine> |
| 11 | +#include <array> |
| 12 | +#include <cassert> |
| 13 | +#include <algorithm> |
| 14 | +#include <utility> |
| 15 | + |
| 16 | +namespace cppcoro |
| 17 | +{ |
| 18 | +#if CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER |
| 19 | + /// This is a scheduler class that schedules coroutines in a round-robin |
| 20 | + /// fashion once N coroutines have been scheduled to it. |
| 21 | + /// |
| 22 | + /// Only supports access from a single thread at a time so |
| 23 | + /// |
| 24 | + /// This implementation was inspired by Gor Nishanov's CppCon 2018 talk |
| 25 | + /// about nano-coroutines. |
| 26 | + /// |
| 27 | + /// The implementation relies on symmetric transfer and noop_coroutine() |
| 28 | + /// and so only works with a relatively recent version of Clang and does |
| 29 | + /// not yet work with MSVC. |
| 30 | + template<size_t N> |
| 31 | + class round_robin_scheduler |
| 32 | + { |
| 33 | + static_assert( |
| 34 | + N >= 2, |
| 35 | + "Round robin scheduler must be configured to support at least two coroutines"); |
| 36 | + |
| 37 | + class schedule_operation |
| 38 | + { |
| 39 | + public: |
| 40 | + explicit schedule_operation(round_robin_scheduler& s) noexcept : m_scheduler(s) {} |
| 41 | + |
| 42 | + bool await_ready() noexcept |
| 43 | + { |
| 44 | + return false; |
| 45 | + } |
| 46 | + |
| 47 | + std::experimental::coroutine_handle<> await_suspend( |
| 48 | + std::experimental::coroutine_handle<> awaitingCoroutine) noexcept |
| 49 | + { |
| 50 | + return m_scheduler.exchange_next(awaitingCoroutine); |
| 51 | + } |
| 52 | + |
| 53 | + void await_resume() noexcept {} |
| 54 | + |
| 55 | + private: |
| 56 | + round_robin_scheduler& m_scheduler; |
| 57 | + }; |
| 58 | + |
| 59 | + friend class schedule_operation; |
| 60 | + |
| 61 | + public: |
| 62 | + round_robin_scheduler() noexcept |
| 63 | + : m_index(0) |
| 64 | + , m_noop(std::experimental::noop_coroutine()) |
| 65 | + { |
| 66 | + for (size_t i = 0; i < N - 1; ++i) |
| 67 | + { |
| 68 | + m_coroutines[i] = m_noop(); |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + ~round_robin_scheduler() |
| 73 | + { |
| 74 | + // All tasks should have been joined before calling destructor. |
| 75 | + assert(std::all_of( |
| 76 | + m_coroutines.begin(), |
| 77 | + m_coroutines.end(), |
| 78 | + [&](auto h) { return h == m_noop; })); |
| 79 | + } |
| 80 | + |
| 81 | + schedule_operation schedule() noexcept |
| 82 | + { |
| 83 | + return schedule_operation{ *this }; |
| 84 | + } |
| 85 | + |
| 86 | + /// Resume any queued coroutines until there are no more coroutines. |
| 87 | + void drain() noexcept |
| 88 | + { |
| 89 | + size_t countRemaining = N - 1; |
| 90 | + do |
| 91 | + { |
| 92 | + auto nextToResume = exchange_next(m_noop); |
| 93 | + if (nextToResume != m_noop) |
| 94 | + { |
| 95 | + nextToResume.resume(); |
| 96 | + countRemaining = N - 1; |
| 97 | + } |
| 98 | + else |
| 99 | + { |
| 100 | + --countRemaining; |
| 101 | + } |
| 102 | + } while (countRemaining > 0); |
| 103 | + } |
| 104 | + |
| 105 | + private: |
| 106 | + |
| 107 | + std::experimental::coroutine_handle exchange_next( |
| 108 | + std::experimental::coroutine_handle<> coroutine) noexcept |
| 109 | + { |
| 110 | + auto coroutineToResume = std::exchange( |
| 111 | + m_scheduler.m_coroutines[m_scheduler.m_index], |
| 112 | + awaitingCoroutine); |
| 113 | + m_scheduler.m_index = m_scheduler.m_index < (N - 2) ? m_scheduler.m_index + 1 : 0; |
| 114 | + return coroutineToResume; |
| 115 | + } |
| 116 | + |
| 117 | + size_t m_index; |
| 118 | + const std::experimental::coroutine_handle<> m_noop; |
| 119 | + std::array<std::experimental::coroutine_handle<>, N - 1> m_coroutines; |
| 120 | + }; |
| 121 | +#endif |
| 122 | +} |
| 123 | + |
| 124 | +#endif |
0 commit comments