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