|
| 1 | +#ifndef CMCPP_RUNTIME_HPP |
| 2 | +#define CMCPP_RUNTIME_HPP |
| 3 | + |
| 4 | +#include <algorithm> |
| 5 | +#include <any> |
| 6 | +#include <functional> |
| 7 | +#include <memory> |
| 8 | +#include <mutex> |
| 9 | +#include <optional> |
| 10 | +#include <utility> |
| 11 | +#include <vector> |
| 12 | + |
| 13 | +namespace cmcpp |
| 14 | +{ |
| 15 | + class Store; |
| 16 | + |
| 17 | + struct Supertask; |
| 18 | + using SupertaskPtr = std::shared_ptr<Supertask>; |
| 19 | + |
| 20 | + using OnStart = std::function<std::vector<std::any>()>; |
| 21 | + using OnResolve = std::function<void(std::optional<std::vector<std::any>>)>; |
| 22 | + |
| 23 | + class Thread : public std::enable_shared_from_this<Thread> |
| 24 | + { |
| 25 | + public: |
| 26 | + using ReadyFn = std::function<bool()>; |
| 27 | + using ResumeFn = std::function<bool(bool)>; |
| 28 | + using CancelFn = std::function<void()>; |
| 29 | + |
| 30 | + static std::shared_ptr<Thread> create(Store &store, ReadyFn ready, ResumeFn resume, bool cancellable = false, CancelFn on_cancel = {}); |
| 31 | + |
| 32 | + Thread(Store &store, ReadyFn ready, ResumeFn resume, bool cancellable, CancelFn on_cancel); |
| 33 | + bool ready() const; |
| 34 | + void resume(); |
| 35 | + void request_cancellation(); |
| 36 | + bool cancellable() const; |
| 37 | + bool cancelled() const; |
| 38 | + bool completed() const; |
| 39 | + |
| 40 | + private: |
| 41 | + enum class State |
| 42 | + { |
| 43 | + Pending, |
| 44 | + Running, |
| 45 | + Completed |
| 46 | + }; |
| 47 | + |
| 48 | + void set_pending(bool pending_again, const std::shared_ptr<Thread> &self); |
| 49 | + |
| 50 | + Store *store_; |
| 51 | + ReadyFn ready_; |
| 52 | + ResumeFn resume_; |
| 53 | + CancelFn on_cancel_; |
| 54 | + bool cancellable_; |
| 55 | + bool cancelled_; |
| 56 | + mutable std::mutex mutex_; |
| 57 | + State state_; |
| 58 | + }; |
| 59 | + |
| 60 | + class Call |
| 61 | + { |
| 62 | + public: |
| 63 | + using CancelRequest = std::function<void()>; |
| 64 | + |
| 65 | + Call() = default; |
| 66 | + explicit Call(CancelRequest cancel) : request_cancellation_(std::move(cancel)) {} |
| 67 | + |
| 68 | + void request_cancellation() const |
| 69 | + { |
| 70 | + if (request_cancellation_) |
| 71 | + { |
| 72 | + request_cancellation_(); |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + static Call from_thread(const std::shared_ptr<Thread> &thread) |
| 77 | + { |
| 78 | + if (!thread) |
| 79 | + { |
| 80 | + return Call(); |
| 81 | + } |
| 82 | + std::weak_ptr<Thread> weak = thread; |
| 83 | + return Call([weak]() |
| 84 | + { |
| 85 | + if (auto locked = weak.lock()) |
| 86 | + { |
| 87 | + locked->request_cancellation(); |
| 88 | + } }); |
| 89 | + } |
| 90 | + |
| 91 | + private: |
| 92 | + CancelRequest request_cancellation_; |
| 93 | + }; |
| 94 | + |
| 95 | + struct Supertask |
| 96 | + { |
| 97 | + SupertaskPtr parent; |
| 98 | + }; |
| 99 | + |
| 100 | + using FuncInst = std::function<Call(Store &, SupertaskPtr, OnStart, OnResolve)>; |
| 101 | + |
| 102 | + class Store |
| 103 | + { |
| 104 | + public: |
| 105 | + Call invoke(const FuncInst &func, SupertaskPtr caller, OnStart on_start, OnResolve on_resolve); |
| 106 | + void tick(); |
| 107 | + void schedule(const std::shared_ptr<Thread> &thread); |
| 108 | + std::size_t pending_size() const; |
| 109 | + |
| 110 | + private: |
| 111 | + friend class Thread; |
| 112 | + mutable std::mutex mutex_; |
| 113 | + std::vector<std::shared_ptr<Thread>> pending_; |
| 114 | + }; |
| 115 | + |
| 116 | + inline std::shared_ptr<Thread> Thread::create(Store &store, ReadyFn ready, ResumeFn resume, bool cancellable, CancelFn on_cancel) |
| 117 | + { |
| 118 | + auto thread = std::shared_ptr<Thread>(new Thread(store, std::move(ready), std::move(resume), cancellable, std::move(on_cancel))); |
| 119 | + store.schedule(thread); |
| 120 | + return thread; |
| 121 | + } |
| 122 | + |
| 123 | + inline Thread::Thread(Store &store, ReadyFn ready, ResumeFn resume, bool cancellable, CancelFn on_cancel) |
| 124 | + : store_(&store), ready_(std::move(ready)), resume_(std::move(resume)), on_cancel_(std::move(on_cancel)), cancellable_(cancellable), cancelled_(false), state_(State::Pending) |
| 125 | + { |
| 126 | + } |
| 127 | + |
| 128 | + inline bool Thread::ready() const |
| 129 | + { |
| 130 | + std::lock_guard lock(mutex_); |
| 131 | + if (state_ != State::Pending) |
| 132 | + { |
| 133 | + return false; |
| 134 | + } |
| 135 | + if (!ready_) |
| 136 | + { |
| 137 | + return true; |
| 138 | + } |
| 139 | + return ready_(); |
| 140 | + } |
| 141 | + |
| 142 | + inline void Thread::resume() |
| 143 | + { |
| 144 | + auto self = shared_from_this(); |
| 145 | + ResumeFn resume; |
| 146 | + bool was_cancelled = false; |
| 147 | + { |
| 148 | + std::lock_guard lock(mutex_); |
| 149 | + if (state_ != State::Pending) |
| 150 | + { |
| 151 | + return; |
| 152 | + } |
| 153 | + state_ = State::Running; |
| 154 | + resume = resume_; |
| 155 | + was_cancelled = cancelled_; |
| 156 | + } |
| 157 | + |
| 158 | + bool keep_pending = false; |
| 159 | + if (resume) |
| 160 | + { |
| 161 | + keep_pending = resume(was_cancelled); |
| 162 | + } |
| 163 | + |
| 164 | + set_pending(keep_pending, self); |
| 165 | + } |
| 166 | + |
| 167 | + inline void Thread::request_cancellation() |
| 168 | + { |
| 169 | + CancelFn cancel; |
| 170 | + { |
| 171 | + std::lock_guard lock(mutex_); |
| 172 | + if (cancelled_ || !cancellable_) |
| 173 | + { |
| 174 | + return; |
| 175 | + } |
| 176 | + cancelled_ = true; |
| 177 | + cancel = on_cancel_; |
| 178 | + } |
| 179 | + if (cancel) |
| 180 | + { |
| 181 | + cancel(); |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + inline bool Thread::cancellable() const |
| 186 | + { |
| 187 | + std::lock_guard lock(mutex_); |
| 188 | + return cancellable_; |
| 189 | + } |
| 190 | + |
| 191 | + inline bool Thread::cancelled() const |
| 192 | + { |
| 193 | + std::lock_guard lock(mutex_); |
| 194 | + return cancelled_; |
| 195 | + } |
| 196 | + |
| 197 | + inline bool Thread::completed() const |
| 198 | + { |
| 199 | + std::lock_guard lock(mutex_); |
| 200 | + return state_ == State::Completed; |
| 201 | + } |
| 202 | + |
| 203 | + inline void Thread::set_pending(bool pending_again, const std::shared_ptr<Thread> &self) |
| 204 | + { |
| 205 | + { |
| 206 | + std::lock_guard lock(mutex_); |
| 207 | + state_ = pending_again ? State::Pending : State::Completed; |
| 208 | + } |
| 209 | + if (pending_again) |
| 210 | + { |
| 211 | + store_->schedule(self); |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + inline Call Store::invoke(const FuncInst &func, SupertaskPtr caller, OnStart on_start, OnResolve on_resolve) |
| 216 | + { |
| 217 | + if (!func) |
| 218 | + { |
| 219 | + return Call(); |
| 220 | + } |
| 221 | + return func(*this, std::move(caller), std::move(on_start), std::move(on_resolve)); |
| 222 | + } |
| 223 | + |
| 224 | + inline void Store::tick() |
| 225 | + { |
| 226 | + std::shared_ptr<Thread> selected; |
| 227 | + { |
| 228 | + std::lock_guard lock(mutex_); |
| 229 | + auto it = std::find_if(pending_.begin(), pending_.end(), [](const std::shared_ptr<Thread> &thread) |
| 230 | + { return thread && thread->ready(); }); |
| 231 | + if (it == pending_.end()) |
| 232 | + { |
| 233 | + return; |
| 234 | + } |
| 235 | + selected = *it; |
| 236 | + pending_.erase(it); |
| 237 | + } |
| 238 | + if (selected) |
| 239 | + { |
| 240 | + selected->resume(); |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + inline void Store::schedule(const std::shared_ptr<Thread> &thread) |
| 245 | + { |
| 246 | + if (!thread) |
| 247 | + { |
| 248 | + return; |
| 249 | + } |
| 250 | + std::lock_guard lock(mutex_); |
| 251 | + pending_.push_back(thread); |
| 252 | + } |
| 253 | + |
| 254 | + inline std::size_t Store::pending_size() const |
| 255 | + { |
| 256 | + std::lock_guard lock(mutex_); |
| 257 | + return pending_.size(); |
| 258 | + } |
| 259 | +} |
| 260 | + |
| 261 | +#endif |
0 commit comments