YACLib
C++ library for concurrent tasks execution
Loading...
Searching...
No Matches
core.hpp
Go to the documentation of this file.
1#pragma once
2
7#include <yaclib/config.hpp>
12
13namespace yaclib::detail {
14
15InlineCore& MakeDrop() noexcept;
16
18 public:
20 }
21
22 template <typename T>
23 void Store(T&&) noexcept {
24 }
25
29
30 template <bool SymmetricTransfer>
32 return BaseCore::SetResultImpl<SymmetricTransfer, false>();
33 }
34
36};
37
38enum class CoreType : unsigned char {
39 Run = 0,
40 Then = 1,
41 Detach = 2,
42};
43
44template <CoreType Type, typename V, typename E>
45using ResultCoreT = std::conditional_t<Type == CoreType::Detach, NoResultCore, UniqueCore<V, E>>;
46
49 while (head->next != nullptr) {
50 auto* next = static_cast<BaseCore*>(head->next);
51 head->next = nullptr;
52 head = next;
53 }
54 return head;
55}
56
57enum class AsyncType {
58 None,
59 Unique,
60 Shared,
61};
62
63template <typename Ret, typename Arg, typename E, typename Func, CoreType Type, AsyncType kAsync, bool kIsCall,
64 bool kFromShared>
65class Core : public ResultCoreT<Type, Ret, E>, public FuncCore<Func> {
66 using F = FuncCore<Func>;
67 using Storage = typename F::Storage;
68 using Invoke = typename F::Invoke;
69
70 static_assert(!(Type == CoreType::Detach && kAsync != AsyncType::None), "Detach cannot be Async, should be void");
71
72 public:
74
75 explicit Core(Func&& f) : F{std::forward<Func>(f)} {
76 this->_self = {};
77 }
78
79 private:
80 void Call() noexcept final {
81 YACLIB_ASSERT(this->_self.unwrapping == 0);
82 if constexpr (Type == CoreType::Run) {
83 YACLIB_ASSERT(this->_self.caller == nullptr);
84 if constexpr (is_invocable_v<Invoke>) {
85 Loop(this, CallImpl<false>(Unit{})); // optimization
86 } else {
88 }
89 } else {
90 YACLIB_ASSERT(this->_self.caller != this);
91 auto& core = DownCast<ResultCore<Arg, E>>(*this->_self.caller);
92 Loop(this, CallImpl<false>(core.template MoveOrConst<!kFromShared>()));
93 }
94 }
95
96 void Drop() noexcept final {
98 }
99
100 template <bool SymmetricTransfer>
101 [[nodiscard]] YACLIB_INLINE auto Impl([[maybe_unused]] InlineCore& caller) noexcept {
102 auto async_done = [&] {
104 YACLIB_ASSERT(&caller == this || &caller == this->_self.caller);
105 static constexpr bool AsyncShared = kAsync == AsyncType::Shared;
106 auto& core = DownCast<ResultCore<Ret, E>>(*this->_self.caller);
108 };
109 if constexpr (Type == CoreType::Run) {
110 return async_done();
111 } else {
112 if constexpr (kAsync != AsyncType::None) {
113 if (this->_self.unwrapping != 0) {
114 return async_done();
115 }
116 }
117 YACLIB_ASSERT(this->_self.caller == nullptr);
118 this->_self.caller = &caller;
119 if constexpr (kFromShared) {
120 DownCast<BaseCore>(caller).CopyExecutorTo(*this);
121 } else {
122 DownCast<BaseCore>(caller).MoveExecutorTo(*this);
123 }
124 if constexpr (kIsCall) {
125 if constexpr (kFromShared) {
126 // The callback can outlive all SharedFutures
127 // Need to assume ownership
128 caller.IncRef();
129 }
130 this->_executor->Submit(*this);
132 } else {
133 auto& core = DownCast<ResultCore<Arg, E>>(caller);
135 }
136 }
137 }
138 [[nodiscard]] InlineCore* Here(InlineCore& caller) noexcept final {
139 return Impl<false>(caller);
140 }
141#if YACLIB_SYMMETRIC_TRANSFER != 0
142 [[nodiscard]] yaclib_std::coroutine_handle<> Next(InlineCore& caller) noexcept final {
143 return Impl<true>(caller);
144 }
145#endif
146
147 template <bool SymmetricTransfer, typename T>
148 [[nodiscard]] YACLIB_INLINE auto CallImpl(T&& r) noexcept try {
149 if constexpr (std::is_same_v<T, Unit> || is_invocable_v<Invoke, Result<Arg, E>>) {
150 return CallResolveAsync<SymmetricTransfer>(std::forward<T>(r));
151 } else {
152 return CallResolveState<SymmetricTransfer>(std::forward<T>(r));
153 }
154 } catch (...) {
155 return Done<SymmetricTransfer>(std::current_exception());
156 }
157
158 template <bool SymmetricTransfer, bool Async = false, typename T>
159 [[nodiscard]] YACLIB_INLINE auto Done(T&& value) noexcept {
160 // Order defined here is important:
161 // 1. Save caller on stack
162 // 2. Save return value to union where was caller
163 // 3. Destroy and dealloc argument storage
164 // 4. Destroy functor storage
165 // 5. Make current core ready, maybe execute next callback
166 // 3 and 4 can be executed in any order TODO(MBkkt) What order is better here?
167 // Other steps cannot be reordered, examples:
168 // [] (X&& x) -> X&& { touch x, then return it by rvalue reference }
169 // [X x] () -> X&& { touch x, then return it by rvalue reference }
170 auto* caller = this->_self.caller;
171 this->Store(std::forward<T>(value));
172 if constexpr ((Type != CoreType::Run && (!kFromShared || kIsCall)) || Async) {
173 caller->DecRef();
174 }
175 if constexpr (!Async) {
176 this->_func.storage.~Storage();
177 }
178 return this->template SetResult<SymmetricTransfer>();
179 }
180
181 template <bool SymmetricTransfer, typename Result>
182 [[nodiscard]] YACLIB_INLINE auto CallResolveState(Result&& r) {
183 const auto state = r.State();
184 if constexpr (is_invocable_v<Invoke, Arg> || (std::is_void_v<Arg> && is_invocable_v<Invoke, Unit>)) {
185 if (state == ResultState::Value) {
186 return CallResolveAsync<SymmetricTransfer>(std::forward<Result>(r).Value());
187 } else if (state == ResultState::Exception) {
188 return Done<SymmetricTransfer>(std::forward<Result>(r).Exception());
189 } else {
191 return Done<SymmetricTransfer>(std::forward<Result>(r).Error());
192 }
193 } else {
194 /**
195 * TLDR: Before and after the "recovery" callback must have the same value type
196 *
197 * Why can't we use the above strategy for other Invoke?
198 * Because user will not have compile error for this case:
199 * MakeFuture()
200 * // Can't call next recovery callback, because our result is Ok, so we will skip next callback
201 * .ThenInline([](E/std::exception_ptr) -> yaclib::Result/Future<double> {
202 * throw std::runtime_error{""};
203 * })
204 * // Previous callback was skipped, so previous Result type is void (received from MakeFuture)
205 * // But here we need Result<double>, so it leeds error
206 * .ThenInline([](yaclib::Result<double>) {
207 * return 1;
208 * });
209 */
211 constexpr bool kIsError = is_invocable_v<Invoke, E>;
212 static_assert(kIsException ^ kIsError, "Recovery callback should be invokable with std::exception_ptr or E");
214 if (state == kState) {
215 using T = std::conditional_t<kIsException, std::exception_ptr, E>;
216 return CallResolveAsync<SymmetricTransfer>(std::get<T>(std::move(r.Internal())));
217 }
218 return Done<SymmetricTransfer>(std::move(r));
219 }
220 }
221
222 template <bool SymmetricTransfer, typename T>
223 [[nodiscard]] YACLIB_INLINE auto CallResolveAsync(T&& value) {
224 if constexpr (kAsync != AsyncType::None) {
225 auto async = CallResolveVoid(std::forward<T>(value));
226 // In the case of SharedFuture we also release here because the
227 // ownership needs to be 'transferred' into *this
228 auto* core = async.GetCore().Release();
229 if constexpr (Type != CoreType::Run) {
230 this->_self.caller->DecRef();
231 this->_self.unwrapping = 1;
232 }
233 this->_self.caller = core;
234 this->_func.storage.~Storage();
235 if constexpr (is_task_v<decltype(async)>) {
236 core->StoreCallback(*this);
237 return Step<SymmetricTransfer>(*this, *MoveToCaller(core));
238 } else {
239 return core->template SetInline<SymmetricTransfer>(*this);
240 }
241 } else {
242 return Done<SymmetricTransfer>(CallResolveVoid(std::forward<T>(value)));
243 }
244 }
245
246 template <typename T>
247 [[nodiscard]] YACLIB_INLINE auto CallResolveVoid(T&& value) {
248 constexpr bool kArgVoid = is_invocable_v<Invoke>;
249 constexpr bool kRetVoid = std::is_void_v<invoke_t<Invoke, std::conditional_t<kArgVoid, void, T>>>;
250 if constexpr (kRetVoid) {
251 if constexpr (kArgVoid) {
252 std::forward<Invoke>(this->_func.storage)();
253 } else {
254 std::forward<Invoke>(this->_func.storage)(std::forward<T>(value));
255 }
256 return Unit{};
257 } else if constexpr (kArgVoid) {
258 return std::forward<Invoke>(this->_func.storage)();
259 } else {
260 return std::forward<Invoke>(this->_func.storage)(std::forward<T>(value));
261 }
262 }
263};
264
265template <typename V, typename E, typename Func>
266constexpr char Tag() noexcept {
267 if constexpr (is_invocable_v<Func, Result<V, E>>) {
268 return 1;
269 } else if constexpr (is_invocable_v<Func, V>) {
270 return 2;
271 } else if constexpr (is_invocable_v<Func, E>) {
272 return 3;
273 } else if constexpr (is_invocable_v<Func, std::exception_ptr>) {
274 return 4;
275 } else if constexpr (is_invocable_v<Func, Unit>) {
276 return 5;
277 } else {
278 return 0;
279 }
280}
281
283struct Return;
284
285template <typename V, typename E, typename Func>
286struct Return<V, E, Func, 1> final {
288};
289
290template <typename V, typename E, typename Func>
291struct Return<V, E, Func, 2> final {
293};
294
295template <typename V, typename E, typename Func>
296struct Return<V, E, Func, 3> final {
298};
299
300template <typename V, typename E, typename Func>
304
305template <typename V, typename E, typename Func>
306struct Return<V, E, Func, 5> final {
308};
309
310template <CoreType CoreT, bool kIsCall, bool kFromShared, typename Arg, typename E, typename Func>
311auto* MakeCore(Func&& f) {
312 static_assert(CoreT != CoreType::Run || std::is_void_v<Arg>,
313 "It makes no sense to receive some value in first pipeline step");
315 static_assert(CoreT != CoreType::Detach || std::is_void_v<AsyncRet>,
316 "It makes no sense to return some value in Detach, since no one will be able to use it");
318 using Ret = std::conditional_t<std::is_same_v<Ret0, Unit>, void, Ret0>;
319 constexpr AsyncType kAsync = [] {
321 return AsyncType::Unique;
322 } else if constexpr (is_shared_future_v<AsyncRet>) {
323 return AsyncType::Shared;
324 } else {
325 return AsyncType::None;
326 }
327 }();
328 // TODO(MBkkt) Think about inline/detach optimization
330 return MakeUnique<Core>(std::forward<Func>(f)).Release();
331}
332
333enum class CallbackType : unsigned char {
334 Inline = 0,
335 On = 1,
336 InlineOn = 2,
337 LazyInline = 3,
338 LazyOn = 4,
339};
340
341template <CoreType CoreT, CallbackType CallbackT, typename Arg, typename E, typename Func>
343 YACLIB_ASSERT(core);
344 constexpr bool kIsDetach = CoreT == CoreType::Detach;
347 constexpr bool kFromShared = false;
348 auto* callback = MakeCore<CoreT, kIsCall, kFromShared, Arg, E>(std::forward<Func>(f));
349 // TODO(MBkkt) callback, executor, caller should be in ctor
350 if constexpr (kIsDetach) {
351 callback->StoreCallback(MakeDrop());
352 }
353 callback->_executor = executor;
354 auto* caller = core.Release();
355 if constexpr (!kIsLazy) {
356 Loop(caller, caller->template SetInline<false>(*callback));
357 }
358 using ResultCoreT = typename std::remove_reference_t<decltype(*callback)>::Base;
359 if constexpr (kIsLazy) {
360 callback->next = caller;
361 caller->StoreCallback(*callback);
363 } else if constexpr (!kIsDetach) {
364 if constexpr (CallbackT == CallbackType::Inline) {
366 } else {
368 }
369 }
370}
371
372template <CoreType CoreT, CallbackType CallbackT, typename Arg, typename E, typename Func>
374 YACLIB_ASSERT(core);
375 constexpr bool kIsDetach = CoreT == CoreType::Detach;
377 constexpr bool kFromShared = true;
378 auto* callback = MakeCore<CoreT, kIsCall, kFromShared, Arg, E>(std::forward<Func>(f));
379 // TODO(MBkkt) callback, executor, caller should be in ctor
380 callback->_executor = executor;
381 Loop(core.Get(), core->template SetInline<false>(*callback));
382 using ResultCoreT = typename std::remove_reference_t<decltype(*callback)>::Base;
383 if constexpr (!kIsDetach) {
384 if constexpr (CallbackT == CallbackType::Inline) {
386 } else {
388 }
389 }
390}
391
392} // namespace yaclib::detail
Provides a mechanism to access the result of async operations.
Definition future.hpp:236
Provides a mechanism to access the result of async operations.
Definition future.hpp:203
A intrusive pointer to objects with an embedded reference count.
T * Get() const noexcept
Provides a mechanism to schedule the some async operations TODO(MBkkt) add description.
Definition task.hpp:25
void StoreCallbackImpl(InlineCore &callback) noexcept
Definition base_core.hpp:58
Core(Func &&f)
Definition core.hpp:75
ResultCoreT< Type, Ret, E > Base
Definition core.hpp:73
std::decay_t< Func > Storage
Definition func_core.hpp:14
YACLIB_NO_UNIQUE_ADDRESS State _func
Definition func_core.hpp:32
std::conditional_t< std::is_function_v< std::remove_reference_t< Func > >, Storage, Func > Invoke
Definition func_core.hpp:15
void Store(T &&) noexcept
Definition core.hpp:23
Transfer< SymmetricTransfer > SetResult() noexcept
Definition core.hpp:31
void StoreCallback(InlineCore &callback) noexcept
Definition core.hpp:26
#define YACLIB_ASSERT(cond)
Definition log.hpp:85
constexpr char Tag() noexcept
Definition core.hpp:266
YACLIB_INLINE BaseCore * MoveToCaller(BaseCore *head) noexcept
Definition core.hpp:47
InlineCore & MakeDrop() noexcept
Definition drop_core.cpp:27
auto * MakeCore(Func &&f)
Definition core.hpp:311
YACLIB_INLINE void Loop(InlineCore *prev, InlineCore *curr) noexcept
auto SetCallback(UniqueCorePtr< Arg, E > &core, IExecutor *executor, Func &&f)
Definition core.hpp:342
constexpr bool is_task_v
Contract< V, E > MakeContract()
Creates related future and promise.
Definition contract.hpp:25
typename detail::Invoke< Func, Arg... >::Type invoke_t
typename detail::InstantiationTypes< Result, T >::Value result_value_t
invoke_t< Func, Result< V, E > > Type
Definition core.hpp:287
invoke_t< Func, std::exception_ptr > Type
Definition core.hpp:302
invoke_t< Func, Unit > Type
Definition core.hpp:307
YACLIB_NO_UNIQUE_ADDRESS Storage storage
Definition func_core.hpp:24