-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcorofork.h
3334 lines (2881 loc) · 136 KB
/
corofork.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/** @file corofork.h
@brief Use coroutines inline, integrate existing asynchronous API easy.
Simple way to turn callback oriented API into awaitable API.
Each "callback based" API (including those, that require "C style" callbacks)
can be used inside C++20 coroutines with that simple trick.
Easy solution to "callback hell", to make code linear and easy to read.
Sample usage (see also more concrete samples below):
@code
// Some code
corofork(=){ //here = means "new coroutine captures by value"
// Some code that still executes in the same context
await AnyAwaitableThing; //some await operation
// Some code that is executed in the context of resumer
// optionally more awaits can happen here,
// one can use captured variables, etc here
};
//Code that executes independently once we started awaiting
@endcode
There is a way to make any "asynchronous" API to operate in
synchronous style with the help of introduced helpers.
There are invert_function and invert_callback API to generate
std::function of "C style" callbacks respectively, and turn
any asynchronous API into awaitable.
Those "awaitables" are suitable for waiting from any coroutine.
For example one can turn asynchronous communication to linear in this way:
@code
// inside some coroutine, imagine we need a sequence of async calls
// First we need to do AsyncRequestCustomerNames
auto customerNames = co_await invert_function(
[](
std::function<
void(std::vector<std::string>) //expected signature to receive customers
> onReady
){
//Pass generated callback to your async API
AsyncRequestCustomerNames(onReady);
}
);
//coroutine executes here once onReady is called
//the customerNames variable is what was received by onReady callback,
//that is how onReady function was "inverted" (turns into awaitable)
...
auto [age, address] = co_await invert_function(
[&](
std::function<
void(unsigned age, std::string address)
> onReady
){
//Pass generated callback to your async API
AsyncRequestAgeAndAddressFor(customerNames, onReady);
}
);
//here age is unsigned and address is std::string
//(for multiple callback parameters std::tuple is returned)
... //etc
@endcode
The main rule of such "inversion" is to repeat the signature,
as it is expected by the asynchronous API and then those arguments will be
received as a result of the await operation.
The "inverting" lambda can capture both by value[=] and by reference[&]
inverting lambda always executes together with co_await expression.
NOTE: remember invert_function and invert_callback will work with
any coroutine that is able to do co_await operation,
you can use them without corofork macro.
also please remember to await result of invert_* immediately
while the "full expression" being inverted still exists
You can pass your generated callback to any API, assuming signature fits.
For example here is artificial sample doing the same thing as
co_await winrt::resume_background();
but fully C++ standard compliant
@code
corofork(){
co_await invert_function([](std::function<void()> resumer){
// Note: resumer will continue execution of the coroutine in thread
std::thread(resumer).detach();
});
//from now coroutine executes in thread
... //doing some in parallel with main
};
... //continue execution independently of corofork content above))
@endcode
RETURN sample
Windows API sample
@code
@endcode
Copyright (c) 2023-2024, Pavlo M, https://github.com/olvap80
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of corofork nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef COROFORK_H_INCLUDED
#define COROFORK_H_INCLUDED
//based on standard C++20 coroutines
#include <coroutine>
//can use std::function as callbacks
#include <functional>
//interfaces are declared with concepts
#include <concepts>
//for case when callback accepts multiple arguments
#include <tuple>
//allow immediate return and handle short path when result is known
#include <optional>
//internally tracks lifetime automatically
#include <memory>
//std::move, std::swap and friends))
#include <utility>
//allow allocation for pregenerated trampolines from different threads
#include <mutex>
//for Sync to be used in corosync
#include <condition_variable>
//for rethrowing exceptions from "other threads" in corosync
#include <exception>
//runtime_error throws when trampoline allocation fails
#include <stdexcept>
//placement new is used for placing lambda to trampoline
#include <new>
//for std::nullptr_t
#include <cstddef>
//Used by invert_subscription to accumulate events (calls) happened so far
#include <queue>
//______________________________________________________________________________
// The macro to start coroutine inline (see samples above)
/// Start a coroutine "inline" (body follows in {})
/** Macro creates a new coroutine optionally capturing parameters
* The way how variables are captured is determined by the macro parameters
* one can provide =, &, or named captures as well.
* "Forked" coroutine starts execution immediately in current thread,
* but can be suspended and resumed in other threqad later.
* The "fork" suffix means coroutine lifetime and execution
* is independent from the caller thread (caller thread is not blocked).
* Be aware: mutexes shall not be crossed by co_await, because
* one can resune corourine from different thread, but mutex
* is sill owned by previous thread! */
#define corofork(...) \
CoroFork_detail::tagTieWithOverloadedOperator ->* \
/* Note: lambda is not called below, operator->* does the stuff */ \
[__VA_ARGS__]()-> \
CoroFork_detail::CoroFork /* Lambda body follows here in {} */
/// Run a coroutine "inline" (synchronously), blocks until coroutine completes
/** The same as above, but now caller thread will not continue independently,
* and will block until coroutine completes (probablu in other threads),
* this means coroutine executes in "their" threads, but finally control
* return to this one onve coroutine completed.
* The "sync" suffix means coroutine lifetime and execution
* is tied to the caller thread (initial caller thread is blocked).
*
* One can do
* co_await back_to_thread;
* to force this coroutine execution flow to continue in the initial thread
* that called the corosync marco.
* This is especially useful for the case if long computation
* is needed "here" but "their" callback must return immediately,
* in this way we can reuse existing thread for running the computation
* in the coroutine, see samples above.
*
* Exceptions are forwarded from "other thread" to this one
* (so corosync will rethrow all exceptions being trown in
* any "other thread" driving the coroutine to the caller thread)
* Be aware: mutexes shall not be crossed by co_await, because
* one can resune corourine from different thread, but mutex
* is sill owned by previous thread! */
#define corosync(...) \
CoroFork_detail::tagTieWithSyncOverloadedOperator ->* \
/* Note: lambda is not called below, operator->* does the stuff */ \
[__VA_ARGS__]( \
[[maybe_unused]] \
CoroFork_detail::CoroSync::promise_type:: \
SharedState::BackToThreadAwaitable \
back_to_thread \
)-> \
CoroFork_detail::CoroSync /* Lambda body follows here in {} */
//______________________________________________________________________________
// Some "must have" internal stuff before declaring API (just skip that!!!))
namespace CoroFork_detail{
/// Meta function to extract types from LambdaType::operator()
/** This is "pre declaration", implementation is in details below */
template<class MethodSignature>
struct FromOperator;
//__________________________________________________________________________
// The ExtractFromLambda meta function LambdaType -> related data
/// General meta function to extract signatures for generated API
template<class LambdaType>
using ExtractFromLambda = FromOperator<decltype(&LambdaType::operator())>;
//__________________________________________________________________________
// Forward declare awaitables and holders
template<class Decorator, class CoResult, class SetupLambdaType>
class AwaitableSimpleCB;
template<class Decorator, class CoResult,
class SetupLambdaType, class GeneratedCallbackResultType>
class AwaitableGeneratedCBReturnsValue;
template<class Decorator, class CoResult,
class SetupLambdaType, class CallbackToCallFromGeneratedCallbackType>
class AwaitableGeneratedCBReturnsResultOfCall;
/// Internal class to hold subscription as long as instance exists
template<class Decorator, class AwaitedType>
class InvertSubscriptionHolder;
//__________________________________________________________________________
// Helpers for callback_from_lambda
/// Meta function to extract types and code from LambdaType::operator()
template<class MethodSignature>
struct ConvertSignature;
/// Signature for trampoline obtained directly from lambda
template<class LambdaType>
using SimpleSignatureFromLambda = typename
ConvertSignature<decltype(&LambdaType::operator())>::SimpleSignature;
//__________________________________________________________________________
// Utility stuff
/// Default customization for generated callback (no custom wrapping)
struct TheSame;
/// Customization that turns std::function to "plain C" callback
template<
std::size_t maxCallbacksCount,
class AdditionalOptionalTag
>
class ToPlainC;
/// Prevent co_await from "saved value" (forces await on full expression only!)
template<class RealAwaitable>
class AwaitAsTemporaryOnly{
public:
//non copyable (takes advantage of RVO)
AwaitAsTemporaryOnly(const AwaitAsTemporaryOnly&) = delete;
AwaitAsTemporaryOnly& operator=(const AwaitAsTemporaryOnly&) = delete;
/// Moves awaitable only from the temporary
AwaitAsTemporaryOnly(RealAwaitable&& realAwaitable)
: awaitable(std::move(realAwaitable)){}
/// It is possible to issue co_await only on "full expression" in coroutine
/** Thus "saving/moving to some other location and awaiting later" is explicitly banned,
to prevent referencing those components of the "full expression" that were destroyed */
auto operator co_await() && -> RealAwaitable& {
return awaitable;
}
/// Intentionally prevent co_await on stored values
std::suspend_always operator co_await() & = delete;
private:
/// The item being really awaited
RealAwaitable awaitable;
};
} //namespace CoroFork_detail
//______________________________________________________________________________
//Utility API useful on it's own (but one can also skip that)
///Turn lambda to function pointer by storing lambda in a block pool
/** Create "single shot" trampoline by default
(trampoline deallocates automatically after first call,
thus freeing one item in reservedCount,
NOTE: never issued callback will never free!
use scoped_callback to be able to release without call)
For usage sample see explanations at beginning of the file.
NOTE: AdditionalOptionalTag can be used for "nonunique" types,
when "something else" than lambda is passed to CallbackFrom
in several different places.
In your usual case you pass only reservedCount to CallbackFrom
See also https://en.cppreference.com/w/cpp/language/function_template
section Template argument deduction */
template<
unsigned reservedCount, ///< how many callbacks coexist simultaneously for
///< that (AdditionalOptionalTag, UniqueLambdaType)
///< unique combination
class AdditionalOptionalTag = void, ///< tweak for case UniqueLambdaType
///< is not unique
class UniqueLambdaType ///< Type for actual callable to be wrapped
>
auto callback_from_lambda(UniqueLambdaType&& lambda) ->
CoroFork_detail::SimpleSignatureFromLambda<
UniqueLambdaType
>*;
/// Extra parameter to lambda change default allocation behavior in callback_from_lambda
/** By default trampolines are "single shot", they "deallocate" as soon
as are called, so it is possible to subscribe only to those API that call lambda
For usage sample see explanations at beginning of the file.
See callback_from_lambda */
class callback_extend_lifetime{
public:
/// No way to copy (is passed by reference to lambda)
callback_extend_lifetime(const callback_extend_lifetime&) = delete;
/// No way to copy (is passed by reference to lambda)
callback_extend_lifetime& operator=(const callback_extend_lifetime&) = delete;
/// Cause trampoline API to be freed
/** Call from inside of lambda to cause "this lambda" to untie from callback,
(this means resource limited by reservedCount can be reused again,
and this also means calling lambda must make sure that the old caller
will not issue the same trampoline any more) */
void dispose();
///Test corresponding trampoline is disposed (freed)
bool is_disposed() const;
protected:
/// Created by corresponding apply
callback_extend_lifetime(){}
private:
/// True if lambda wands to dispose (free trampoline)
bool isDisposed = false;
};
/// RAII to hold callback allocation as long as needed
/** Use scoped_callback below as convenience API */
template<
unsigned reservedCount, ///< how many callbacks coexist simultaneously for
///< that (AdditionalOptionalTag, UniqueLambdaType)
///< unique combination
class AdditionalOptionalTag, ///< tweak for case UniqueLambdaType
///< is not unique
class UniqueLambdaType ///< Type for actual callable to be wrapped
>
class scoped_callback_holder{
public:
/// Corresponding simple API signature
using Signature = CoroFork_detail::SimpleSignatureFromLambda<UniqueLambdaType>;
/// Create instance from lambda
scoped_callback_holder(UniqueLambdaType&& lambda);
/// Cleanup the stuff once instance goes out of scope
~scoped_callback_holder();
/// Assign callback after scoped_callback_holder was created
scoped_callback_holder& operator=(UniqueLambdaType&& lambda);
// can move, but cannot copy
scoped_callback_holder(const scoped_callback_holder&&);
scoped_callback_holder& operator=(const scoped_callback_holder&&);
scoped_callback_holder(const scoped_callback_holder&) = delete;
scoped_callback_holder& operator=(const scoped_callback_holder&) = delete;
/// Obtain pointer. Do not use that after scoped_callback_holder destructs.
Signature* Callback() const;
private:
/// "Type erased" info for deallocation
void* owningBlock = nullptr;
};
/// Generate callback around lambda, that exists as long as scope
template<
unsigned reservedCount, ///< how many callbacks coexist simultaneously for
///< that (AdditionalOptionalTag, UniqueLambdaType)
///< unique combination
class AdditionalOptionalTag = void, ///< tweak for case UniqueLambdaType
///< is not unique
class UniqueLambdaType ///< Type for actual callable to be wrapped
>
auto scoped_callback(UniqueLambdaType&& lambda) ->
scoped_callback_holder<
reservedCount, AdditionalOptionalTag, UniqueLambdaType
>;
//______________________________________________________________________________
// Describe kinds of lambda used for setup (it is better to look into samples))
/// Define kind of lambda to be used for function setup
template<class SetupLambdaType>
concept CoroForkLambdaToSetupFunction = requires(
SetupLambdaType setupLambda,
typename CoroFork_detail::ExtractFromLambda<
SetupLambdaType
>::FunctionType generatedFunction
){
//shall have operator(), or else there is no way to extract parameter
&SetupLambdaType::operator();
//shall be callable with that function as parameter
setupLambda(generatedFunction);
};
/// Define kind of lambda to be used for callback setup
template<class SetupLambdaType>
concept CoroForkLambdaToSetupCallback = requires(
SetupLambdaType setupLambda,
typename CoroFork_detail::ExtractFromLambda<
SetupLambdaType
>::RawPointerCallbackType generatedRawPointerCallback
){
/* Shall have operator(),
this is a naive test for argument to be anonymous lambda,
trick with maxCallbacksCount below will not work if we pass
"something else" than unique lambda type.
Unique lambda guaranties we have maxCallbacksCount for each
call site (place of call) is being used individually,
and will not reuse other (independent) call sites
(see also https://en.cppreference.com/w/cpp/language/lambda) */
&SetupLambdaType::operator();
/* shall be a lambda accepting a raw "plain C" stype pointer!
use invert_function instead of invert_callback when
some std::function has to be passed,
this will avoid the need to guess on maxCallbacksCount */
setupLambda(generatedRawPointerCallback);
};
//______________________________________________________________________________
// invert_function API to turn std::function based API into awaitable
/// Turn std::function callback based API into coroutine based
/** Provided lambda shall receive desired callback with corresponding signature
* as a parameter. Such callback can be passed to asynchronous API
* to be called later by "something else" single time.
* Once that generated function callback is called it will
* resume the coroutine awaiting for the result of invert_function,
* and callback arguments will go to caller.
* Remember to await result of invert_function immediately.
* See samples above for illustration */
template<CoroForkLambdaToSetupFunction LambdaToSetupFunction>
auto invert_function(
const LambdaToSetupFunction& setupFunction ///< receives generated callback
){
using Awaitable = CoroFork_detail::AwaitableSimpleCB<
CoroFork_detail::TheSame,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::CoResultType,
LambdaToSetupFunction
>;
return CoroFork_detail::AwaitAsTemporaryOnly<Awaitable>(
Awaitable(setupFunction)
);
}
/// Same as above, but generated function callback has a return value
/** In addition to the above logic, here the return value for
* the generated function callback can be provided.
* Remember to await result of invert_function immediately.
* See samples above for illustration. */
template<
CoroForkLambdaToSetupFunction LambdaToSetupFunction,
class ResultForCallbackFunctionAsValue
>
requires
std::convertible_to<
std::remove_cvref_t<ResultForCallbackFunctionAsValue>,
std::remove_cvref_t<
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::FunctionResultType
>
>
auto invert_function(
const LambdaToSetupFunction& setupFunction, ///< Your setup
ResultForCallbackFunctionAsValue&& resultForCallbackFunction
){
using Awaitable = CoroFork_detail::AwaitableGeneratedCBReturnsValue<
CoroFork_detail::TheSame,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::CoResultType,
LambdaToSetupFunction,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::FunctionResultType
>;
return CoroFork_detail::AwaitAsTemporaryOnly<Awaitable>(
Awaitable(
setupFunction,
std::forward<ResultForCallbackFunctionAsValue>(resultForCallbackFunction)
)
);
}
/// Same as above, but generated callback returns value returned by functionToObtainResult
/** Remember here coroutine is resumed directly from the callback, thus
* functionToObtainResultForCallback is issued once we await for "somenting else" */
template<
CoroForkLambdaToSetupFunction LambdaToSetupFunction, ///< Your setup
std::invocable FunctionToObtainResult
>
requires
std::same_as<
std::remove_cvref_t<
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::FunctionResultType
>,
std::remove_cvref_t<
std::invoke_result_t< std::remove_cvref_t<FunctionToObtainResult> >
>
>
auto invert_function(
const LambdaToSetupFunction& setupFunction,
FunctionToObtainResult&& functionToObtainResultForCallbackFunction
){
using Awaitable = CoroFork_detail::AwaitableGeneratedCBReturnsResultOfCall<
CoroFork_detail::TheSame,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::CoResultType,
LambdaToSetupFunction,
FunctionToObtainResult
>;
return CoroFork_detail::AwaitAsTemporaryOnly<Awaitable>(
Awaitable(
setupFunction, functionToObtainResultForCallbackFunction
)
);
}
//______________________________________________________________________________
// invert_callback API for generating "pain C" style callbacks
/// Turn "plain C style" callback based API into coroutine based
template<
std::size_t maxCallbacksCount, ///< Specify callback count per lambda
CoroForkLambdaToSetupCallback LambdaToSetupCallback ///< Your setup
>
auto invert_callback(
const LambdaToSetupCallback& setupCallback
){
using Awaitable = CoroFork_detail::AwaitableSimpleCB<
CoroFork_detail::ToPlainC<
maxCallbacksCount, LambdaToSetupCallback
>,
typename CoroFork_detail::ExtractFromLambda<
LambdaToSetupCallback
>::CoResultType,
LambdaToSetupCallback
>;
return CoroFork_detail::AwaitAsTemporaryOnly<Awaitable>(
Awaitable(setupCallback)
);
}
/// Same as above, but generated callback has a return value
/** In addition to the above logic Here the return value for
* the generated callback can be provided.
* See samples above for illustration.
* Remember here coroutine is resumed directly from the callback */
template<
std::size_t maxCallbacksCount, ///< Specify callback count per lambda
CoroForkLambdaToSetupCallback LambdaToSetupCallback, ///< Your setup
class ResultForCallbackFunctionAsValue
>
requires
std::convertible_to<
std::remove_cvref_t<ResultForCallbackFunctionAsValue>,
std::remove_cvref_t<
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupCallback>::FunctionResultType
>
>
auto invert_callback(
const LambdaToSetupCallback& setupCallback,
ResultForCallbackFunctionAsValue&& resultForCallback
){
using Awaitable = CoroFork_detail::AwaitableGeneratedCBReturnsValue<
CoroFork_detail::ToPlainC<
maxCallbacksCount, LambdaToSetupCallback
>,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupCallback>::CoResultType,
LambdaToSetupCallback,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupCallback>::FunctionResultType
>;
return CoroFork_detail::AwaitAsTemporaryOnly<Awaitable>(
Awaitable(
setupCallback,
std::forward<ResultForCallbackFunctionAsValue>(resultForCallback)
)
);
}
/// Same as above, but generated callback returns value returned by functionToObtainResultForCallback
/** Remember here coroutine is resumed directly from the callback, thus
* functionToObtainResultForCallback is issued once we await for "somenting else" */
template<
std::size_t maxCallbacksCount, ///< Specify callback count per lambda
CoroForkLambdaToSetupCallback LambdaToSetupCallback, ///< Your setup
std::invocable FunctionToObtainResult
>
requires
std::same_as<
std::remove_cvref_t<
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupCallback>::FunctionResultType
>,
std::remove_cvref_t<
std::invoke_result_t< std::remove_cvref_t<FunctionToObtainResult> >
>
>
auto invert_callback(
const LambdaToSetupCallback& setupCallback,
FunctionToObtainResult&& functionToObtainResultForCallback
){
using Awaitable = CoroFork_detail::AwaitableGeneratedCBReturnsResultOfCall<
CoroFork_detail::ToPlainC<
maxCallbacksCount, LambdaToSetupCallback
>,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupCallback>::CoResultType,
LambdaToSetupCallback,
FunctionToObtainResult
>;
return CoroFork_detail::AwaitAsTemporaryOnly<Awaitable>(
Awaitable(
setupCallback, functionToObtainResultForCallback
)
);
}
//______________________________________________________________________________
// Turning subscription to awaitable for function
namespace CoroFork_detail{
} //namespace CoroFork_detail
/// Turn std::function subscription based API into coroutine based
/** Provided lambda shall receive desired callback with corresponding signature
* as a parameter. Such callback can be passed to asynchronous subscription API
* to be called later (likely multiple times) by "something else".
* Each time once called such generated function callback
* will resume the coroutine awaiting for invert_subscription_holder
* being created by the invert_subscription call,
* and callback arguments will go to the awaiter.
* The setupFunction can optionally return "the unsubscription API",
* to be automatically invoked once corresponding invert_subscription_holder
* goes out of scope.
* See samples above for illustration.
* (Remember here "inverted" subscription callback returns result immediately once issued
* and not when subscription is actually awaited) */
template<CoroForkLambdaToSetupFunction LambdaToSetupFunction>
auto invert_subscription(
const LambdaToSetupFunction& setupFunction ///< receives generated callback
){
//takes advantage of RVO here
return CoroFork_detail::InvertSubscriptionHolder<
typename CoroFork_detail::TheSame,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::CoResultType
>(setupFunction);
}
/// Same as above but now there is a return value of generated callback
template<
CoroForkLambdaToSetupFunction LambdaToSetupFunction,
class ResultForCallbackFunctionAsValue
>
requires
std::convertible_to<
std::remove_cvref_t<
ResultForCallbackFunctionAsValue
>,
std::remove_cvref_t<
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::FunctionResultType
>
>
auto invert_subscription(
const LambdaToSetupFunction& setupFunction, ///< Your setup
ResultForCallbackFunctionAsValue&& resultForCallbackFunction
){
//takes advantage of RVO here
return CoroFork_detail::InvertSubscriptionHolder<
typename CoroFork_detail::TheSame,
typename CoroFork_detail::ExtractFromLambda<LambdaToSetupFunction>::CoResultType
>(
setupFunction,
std::forward<ResultForCallbackFunctionAsValue>(resultForCallbackFunction)
);
}
//______________________________________________________________________________
//##############################################################################
/*==============================================================================
* More implementation details follow *
*=============================================================================*/
//##############################################################################
namespace CoroFork_detail{
//__________________________________________________________________________
// General corofork macro support
/// Actual implementation of corofork/corosync macro
class CoroFork{
public:
//Uncopyable, unmovable (take advantage of RVO)
//NOTE: CoroFork dissapears once full expression starting the coroutine is exited
// this means promise_type can outlive CoroFork instance
CoroFork(const CoroFork&) = delete;
CoroFork& operator=(const CoroFork&) = delete;
/// Define coroutine behavior
struct promise_type;
///Start with lambda that creates coroutine instance
/** Ensures lambda is alive as long as coroutine is running */
template<class Lambda>
static void Start(Lambda&& lambda);
private:
/// Tie with promise once created
CoroFork(promise_type* createdFrom): myPromise(createdFrom) {}
/// Corresponding promise (may not be alive as coroutine exits)
promise_type* myPromise;
/// Common base for stored lambda (to be stored in promise)
class LambdaStorageBase;
/// Store that lambda (with captures) as long as coroutine is running
template<class Lambda>
class LambdaStorage;
};
struct CoroFork::promise_type{
/// Default constructor (just do nothing)
promise_type() = default;
//uncopyable, unmovable (take advantage of RVO)
promise_type(const promise_type&) = delete;
promise_type& operator=(const promise_type&) = delete;
/// How actually CoroFork is obtained from the coroutine
CoroFork get_return_object() { return CoroFork(this); }
/// Shall be suspended (prevent deallocation until the lambda is safe)
std::suspend_always initial_suspend() const noexcept { return {}; }
/// Just do nothing
void return_void() const noexcept {}
/// Once exception happens, let it propagate
void unhandled_exception() const { throw; }
/// Shall let coroutine to be destroyed by reaching end
/** let it cleanup self in natural way, as coroutine execution reaches end
https://stackoverflow.com/questions/68352718/is-it-necessary-to-call-destroy-on-a-stdcoroutine-handle
*/
std::suspend_never final_suspend() const noexcept {
// do nothing (lambda will fade away together with the promise)
return {}; //let coroutine destruct
}
/// The way to make sure all lambda captures are alive as long coroutine is running
std::unique_ptr<LambdaStorageBase> capturingFunction;
};
struct CoroFork::LambdaStorageBase{
public:
/// Allow cleanup via base class (correct destruction of derived)
virtual ~LambdaStorageBase(){}
/// Actually starts coroutine and obtains coroutine
virtual CoroFork RunStoredLambda() = 0;
};
///Store lambda (with captures!) as long as coroutine is running
template<class Lambda>
class CoroFork::LambdaStorage: public CoroFork::LambdaStorageBase{
public:
///Create RAII wrapper around Lambda
/** Using Lambda directly, optimizer takes case due to [&]() in front */
constexpr LambdaStorage(Lambda initialLambda)
: lambda(initialLambda) {}
//ensure copy changes go only through move constructor
LambdaStorage(const LambdaStorage& other) = delete;
LambdaStorage& operator=(const LambdaStorage& other) = delete;
/// Actually starts coroutine (and created promise)
virtual CoroFork RunStoredLambda() override{
return lambda();
}
private:
Lambda lambda; ///< Hold lambda, avoid slow std::function here
};
template<class Lambda>
void CoroFork::Start(Lambda&& lambda){
/* captured lambda here will have fixed memory location,
While std::unique_ptr can be later paced into the promise,
and live as long as coroutine is running for sure */
auto capture = std::unique_ptr<LambdaStorageBase>(
new LambdaStorage< std::remove_reference_t<Lambda> >(
std::forward<Lambda>(lambda)
)
);
//this will create corresponding promise (in suspended state)
CoroFork started = capture->RunStoredLambda();
/*capture will exist as long as the coroutine is running
(it is stored in the promise) */
started.myPromise->capturingFunction = std::move(capture);
/* Here coroutine is resumed from the current thread,
all actions will happen immediately in this thread */
std::coroutine_handle<promise_type>::from_promise(*started.myPromise).resume();
/*once we are here this means coroutine eiter suspended or executes
in some other thread, or it is already finished */
/* Here we intentionally do not destroy coroutine here,
(final_suspend returs std::suspend_never, so coroutine will be destroyed
once it reaches the end, and lambda will be destroyed together with the promise)
https://stackoverflow.com/questions/68352718/is-it-necessary-to-call-destroy-on-a-stdcoroutine-handle
*/
}
///Helper type to trigger operator ->* for corofork macro
struct TagTieWithOverloadedOperator{
constexpr TagTieWithOverloadedOperator() = default;
};
///Use this "instance" to trigger overloaded operator ->* for corofork macro
/** The trick with TagTieWithOverloadedOperator is needed
to infer type of the lambda */
constexpr TagTieWithOverloadedOperator tagTieWithOverloadedOperator;
///Helper operator to easy catch lambda for corofork macro
/** Use template to avoid slow std::function
(raw lambda is copied/stored here) */
template<class Lambda>
constexpr void operator ->* (TagTieWithOverloadedOperator, Lambda&& lambda){
CoroFork::Start(std::move(lambda));
}
//__________________________________________________________________________
// General corosync macro support
/// Actual implementation of corofork/corosync macro
class CoroSync{
public:
//Uncopyable, unmovable (take advantage of RVO)
//NOTE: CoroSync dissapears once full expression starting the coroutine is exited
// this means promise_type can outlive CoroSync instance
CoroSync(const CoroSync&) = delete;
CoroSync& operator=(const CoroSync&) = delete;
/// Define coroutine behavior
struct promise_type;
///Start with lambda that creates coroutine instance
/** Ensures lambda is alive as long as coroutine is running */
template<class Lambda>
static void Start(Lambda&& lambda);
private:
/// Tie with promise once created
CoroSync(promise_type* createdFrom): myPromise(createdFrom) {}
/// Corresponding promise (may not be alive as coroutine exits)
promise_type* myPromise;
/// Common base for stored lambda (to be stored in promise)
class LambdaStorageBase;
/// Store that lambda (with captures) as long as coroutine is running
template<class Lambda>
class LambdaStorage;
};
struct CoroSync::promise_type{
/// Default constructor (just do nothing)
promise_type() = default;
//uncopyable, unmovable (take advantage of RVO)
promise_type(const promise_type&) = delete;
promise_type& operator=(const promise_type&) = delete;
/// How actually Coro is obtained from the coroutine
CoroSync get_return_object() { return CoroSync(this); }
/// Shall be suspended (prevent deallocation until the lambda is safe)
std::suspend_always initial_suspend() const noexcept { return {}; }
/// Part to outlive the coroutine lifetime
struct SharedState{
/// Protect promise from multiple access
std::mutex protectPromise;
/// Used for waiting for coroutine to finish
std::condition_variable waitForPromise;
enum class State{
Running,
FinishedNormally,
FinishedWithException,
GoBackToThread
} state = State::Running;
class BackToThreadAwaitable{
public:
BackToThreadAwaitable(SharedState* sharedStateToUse)
: sharedState(sharedStateToUse) {}
/// Always cause coroutine to suspend
bool await_ready() const noexcept{ return false; }
/// Cause execution to continue in the initial thread
void await_suspend(std::coroutine_handle<> handle){
std::lock_guard lock(sharedState->protectPromise);
sharedState->state = State::GoBackToThread;
sharedState->waitForPromise.notify_one();
}
void await_resume() const noexcept{}
private:
SharedState* const sharedState;
};
/// Exception to be propagated from corosync
std::exception_ptr exception;
/// Mark coroutine as finished (will not resume)
void return_void() noexcept {
std::lock_guard lock(protectPromise);
state = State::FinishedNormally;