only pass yield to coroutines, make them movable and swappable, improve examples

master
Daniel Kolesa 2017-03-06 18:37:33 +01:00
parent b9260c8918
commit 64d92743e4
3 changed files with 483 additions and 193 deletions

View File

@ -5,192 +5,286 @@ using namespace ostd;
struct foo {
foo() {
writeln("***foo ctor***");
writeln("<constructing foo>");
}
~foo() {
writeln("***foo dtor***");
writeln("<destroying foo>");
}
};
int main() {
writeln("MAIN START");
writeln("starting main...");
for (int steps: range(1, 10)) {
if (steps != 1) {
/* separate the results */
writeln();
}
writefln("** SCOPE START %d **", steps);
coroutine<int(int)> f = [](auto &coro, int x) {
writefln(" main loop: step %s", steps);
writeln(" coroutine creation");
coroutine<int(int)> f = [](auto yield, int x) {
writefln(" coroutine call, first arg: %s", x);
foo test;
writeln("got: ", x);
for (int i: range(x)) {
writeln("loop: ", i);
writeln("yield: ", coro.yield(i * 10));
writefln(" loop inside coroutine %s", i + 1);
writefln(" yielding %s...", i * 10);
auto yr = yield(i * 10);
writefln(" yielded: %s", yr);
}
writeln("return");
writeln(" return from coroutine (returning 1234)...");
return 1234;
};
writeln("CALL");
writeln(" coroutine call loop");
int val = 5;
for (int i: range(steps)) {
writeln("COROUTINE RETURNED: ", f(val));
writefln("OUT %d (dead: %s)", i, !f);
writeln(" calling into coroutine...");
auto v = f(val);
writefln(" called into coroutine which yielded: %s", v);
writefln(" call loop iteration %s done", i + 1);
writefln(" coroutine dead: %s", !f);
val += 5;
}
writefln("** SCOPE END %d **", steps);
writefln(" main loop iteration %s done", steps);
}
writeln("MAIN END");
writeln("... main has ended");
}
/*
MAIN START
** SCOPE START 1 **
CALL
***foo ctor***
got: 5
loop: 0
COROUTINE RETURNED: 0
OUT 0 (dead: false)
** SCOPE END 1 **
***foo dtor***
starting main...
main loop: step 1
coroutine creation
coroutine call loop
calling into coroutine...
coroutine call, first arg: 5
<constructing foo>
loop inside coroutine 1
yielding 0...
called into coroutine which yielded: 0
call loop iteration 1 done
coroutine dead: false
main loop iteration 1 done
<destroying foo>
** SCOPE START 2 **
CALL
***foo ctor***
got: 5
loop: 0
COROUTINE RETURNED: 0
OUT 0 (dead: false)
yield: 10
loop: 1
COROUTINE RETURNED: 10
OUT 1 (dead: false)
** SCOPE END 2 **
***foo dtor***
main loop: step 2
coroutine creation
coroutine call loop
calling into coroutine...
coroutine call, first arg: 5
<constructing foo>
loop inside coroutine 1
yielding 0...
called into coroutine which yielded: 0
call loop iteration 1 done
coroutine dead: false
calling into coroutine...
yielded: 10
loop inside coroutine 2
yielding 10...
called into coroutine which yielded: 10
call loop iteration 2 done
coroutine dead: false
main loop iteration 2 done
<destroying foo>
** SCOPE START 3 **
CALL
***foo ctor***
got: 5
loop: 0
COROUTINE RETURNED: 0
OUT 0 (dead: false)
yield: 10
loop: 1
COROUTINE RETURNED: 10
OUT 1 (dead: false)
yield: 15
loop: 2
COROUTINE RETURNED: 20
OUT 2 (dead: false)
** SCOPE END 3 **
***foo dtor***
main loop: step 3
coroutine creation
coroutine call loop
calling into coroutine...
coroutine call, first arg: 5
<constructing foo>
loop inside coroutine 1
yielding 0...
called into coroutine which yielded: 0
call loop iteration 1 done
coroutine dead: false
calling into coroutine...
yielded: 10
loop inside coroutine 2
yielding 10...
called into coroutine which yielded: 10
call loop iteration 2 done
coroutine dead: false
calling into coroutine...
yielded: 15
loop inside coroutine 3
yielding 20...
called into coroutine which yielded: 20
call loop iteration 3 done
coroutine dead: false
main loop iteration 3 done
<destroying foo>
** SCOPE START 4 **
CALL
***foo ctor***
got: 5
loop: 0
COROUTINE RETURNED: 0
OUT 0 (dead: false)
yield: 10
loop: 1
COROUTINE RETURNED: 10
OUT 1 (dead: false)
yield: 15
loop: 2
COROUTINE RETURNED: 20
OUT 2 (dead: false)
yield: 20
loop: 3
COROUTINE RETURNED: 30
OUT 3 (dead: false)
** SCOPE END 4 **
***foo dtor***
main loop: step 4
coroutine creation
coroutine call loop
calling into coroutine...
coroutine call, first arg: 5
<constructing foo>
loop inside coroutine 1
yielding 0...
called into coroutine which yielded: 0
call loop iteration 1 done
coroutine dead: false
calling into coroutine...
yielded: 10
loop inside coroutine 2
yielding 10...
called into coroutine which yielded: 10
call loop iteration 2 done
coroutine dead: false
calling into coroutine...
yielded: 15
loop inside coroutine 3
yielding 20...
called into coroutine which yielded: 20
call loop iteration 3 done
coroutine dead: false
calling into coroutine...
yielded: 20
loop inside coroutine 4
yielding 30...
called into coroutine which yielded: 30
call loop iteration 4 done
coroutine dead: false
main loop iteration 4 done
<destroying foo>
** SCOPE START 5 **
CALL
***foo ctor***
got: 5
loop: 0
COROUTINE RETURNED: 0
OUT 0 (dead: false)
yield: 10
loop: 1
COROUTINE RETURNED: 10
OUT 1 (dead: false)
yield: 15
loop: 2
COROUTINE RETURNED: 20
OUT 2 (dead: false)
yield: 20
loop: 3
COROUTINE RETURNED: 30
OUT 3 (dead: false)
yield: 25
loop: 4
COROUTINE RETURNED: 40
OUT 4 (dead: false)
** SCOPE END 5 **
***foo dtor***
main loop: step 5
coroutine creation
coroutine call loop
calling into coroutine...
coroutine call, first arg: 5
<constructing foo>
loop inside coroutine 1
yielding 0...
called into coroutine which yielded: 0
call loop iteration 1 done
coroutine dead: false
calling into coroutine...
yielded: 10
loop inside coroutine 2
yielding 10...
called into coroutine which yielded: 10
call loop iteration 2 done
coroutine dead: false
calling into coroutine...
yielded: 15
loop inside coroutine 3
yielding 20...
called into coroutine which yielded: 20
call loop iteration 3 done
coroutine dead: false
calling into coroutine...
yielded: 20
loop inside coroutine 4
yielding 30...
called into coroutine which yielded: 30
call loop iteration 4 done
coroutine dead: false
calling into coroutine...
yielded: 25
loop inside coroutine 5
yielding 40...
called into coroutine which yielded: 40
call loop iteration 5 done
coroutine dead: false
main loop iteration 5 done
<destroying foo>
** SCOPE START 6 **
CALL
***foo ctor***
got: 5
loop: 0
COROUTINE RETURNED: 0
OUT 0 (dead: false)
yield: 10
loop: 1
COROUTINE RETURNED: 10
OUT 1 (dead: false)
yield: 15
loop: 2
COROUTINE RETURNED: 20
OUT 2 (dead: false)
yield: 20
loop: 3
COROUTINE RETURNED: 30
OUT 3 (dead: false)
yield: 25
loop: 4
COROUTINE RETURNED: 40
OUT 4 (dead: false)
yield: 30
return
***foo dtor***
COROUTINE RETURNED: 1234
OUT 5 (dead: true)
** SCOPE END 6 **
main loop: step 6
coroutine creation
coroutine call loop
calling into coroutine...
coroutine call, first arg: 5
<constructing foo>
loop inside coroutine 1
yielding 0...
called into coroutine which yielded: 0
call loop iteration 1 done
coroutine dead: false
calling into coroutine...
yielded: 10
loop inside coroutine 2
yielding 10...
called into coroutine which yielded: 10
call loop iteration 2 done
coroutine dead: false
calling into coroutine...
yielded: 15
loop inside coroutine 3
yielding 20...
called into coroutine which yielded: 20
call loop iteration 3 done
coroutine dead: false
calling into coroutine...
yielded: 20
loop inside coroutine 4
yielding 30...
called into coroutine which yielded: 30
call loop iteration 4 done
coroutine dead: false
calling into coroutine...
yielded: 25
loop inside coroutine 5
yielding 40...
called into coroutine which yielded: 40
call loop iteration 5 done
coroutine dead: false
calling into coroutine...
yielded: 30
return from coroutine (returning 1234)...
<destroying foo>
called into coroutine which yielded: 1234
call loop iteration 6 done
coroutine dead: true
main loop iteration 6 done
** SCOPE START 7 **
CALL
***foo ctor***
got: 5
loop: 0
COROUTINE RETURNED: 0
OUT 0 (dead: false)
yield: 10
loop: 1
COROUTINE RETURNED: 10
OUT 1 (dead: false)
yield: 15
loop: 2
COROUTINE RETURNED: 20
OUT 2 (dead: false)
yield: 20
loop: 3
COROUTINE RETURNED: 30
OUT 3 (dead: false)
yield: 25
loop: 4
COROUTINE RETURNED: 40
OUT 4 (dead: false)
yield: 30
return
***foo dtor***
COROUTINE RETURNED: 1234
OUT 5 (dead: true)
main loop: step 7
coroutine creation
coroutine call loop
calling into coroutine...
coroutine call, first arg: 5
<constructing foo>
loop inside coroutine 1
yielding 0...
called into coroutine which yielded: 0
call loop iteration 1 done
coroutine dead: false
calling into coroutine...
yielded: 10
loop inside coroutine 2
yielding 10...
called into coroutine which yielded: 10
call loop iteration 2 done
coroutine dead: false
calling into coroutine...
yielded: 15
loop inside coroutine 3
yielding 20...
called into coroutine which yielded: 20
call loop iteration 3 done
coroutine dead: false
calling into coroutine...
yielded: 20
loop inside coroutine 4
yielding 30...
called into coroutine which yielded: 30
call loop iteration 4 done
coroutine dead: false
calling into coroutine...
yielded: 25
loop inside coroutine 5
yielding 40...
called into coroutine which yielded: 40
call loop iteration 5 done
coroutine dead: false
calling into coroutine...
yielded: 30
return from coroutine (returning 1234)...
<destroying foo>
called into coroutine which yielded: 1234
call loop iteration 6 done
coroutine dead: true
calling into coroutine...
terminating with uncaught exception of type ostd::coroutine_error: dead coroutine
zsh: abort ./coro
*/

View File

@ -4,11 +4,11 @@
using namespace ostd;
int main() {
generator<int> g = [](auto &coro) {
coro.yield(5);
coro.yield(10);
coro.yield(15);
coro.yield(20);
generator<int> g = [](auto yield) {
yield(5);
yield(10);
yield(15);
yield(20);
return 25;
};

View File

@ -80,11 +80,32 @@ struct coroutine_context {
p_coro = detail::ostd_make_fcontext(p_stack.get() + ss, ss, context_call);
}
coroutine_context(coroutine_context const &) = delete;
coroutine_context(coroutine_context &&c):
p_stack(std::move(c.p_stack)), p_coro(c.p_coro), p_orig(c.p_orig),
p_except(std::move(c.p_except)), p_callp(c.p_callp), p_data(c.p_data),
p_finished(c.p_finished)
{
c.p_coro = c.p_orig = nullptr;
c.p_data = nullptr;
c.p_callp = nullptr;
/* make sure it's not unwound */
c.p_finished = true;
}
coroutine_context &operator=(coroutine_context const &) = delete;
coroutine_context &operator=(coroutine_context &&c) {
swap(c);
/* make sure it's not unwound */
c.p_finished = true;
return *this;
}
void call() {
if (p_finished) {
throw coroutine_error{"dead coroutine"};
}
p_coro = detail::ostd_jump_fcontext(p_coro, this).ctx;
coro_jump();
if (p_except) {
std::rethrow_exception(std::move(p_except));
}
@ -102,6 +123,10 @@ struct coroutine_context {
);
}
void coro_jump() {
p_coro = detail::ostd_jump_fcontext(p_coro, this).ctx;
}
void yield_jump() {
p_orig = detail::ostd_jump_fcontext(p_orig, nullptr).ctx;
}
@ -110,6 +135,20 @@ struct coroutine_context {
return p_finished;
}
void set_data(void *data) {
p_data = data;
}
void swap(coroutine_context &other) noexcept {
std::swap(p_stack, other.p_stack);
std::swap(p_coro, other.p_coro);
std::swap(p_orig, other.p_orig);
std::swap(p_except, other.p_except);
std::swap(p_callp, other.p_callp);
std::swap(p_data, other.p_data);
std::swap(p_finished, other.p_finished);
}
private:
static void context_call(detail::transfer_t t) {
auto &self = *(static_cast<coroutine_context *>(t.data));
@ -135,6 +174,10 @@ private:
bool p_finished = false;
};
inline void swap(coroutine_context &a, coroutine_context &b) {
a.swap(b);
}
template<typename T>
struct coroutine;
@ -152,6 +195,10 @@ namespace detail {
return std::move(p_arg);
}
void swap(arg_wrapper &other) {
std::swap(p_arg, other.p_arg);
}
private:
T p_arg = T{};
};
@ -168,6 +215,10 @@ namespace detail {
return *p_arg;
}
void swap(arg_wrapper &other) {
std::swap(p_arg, other.p_arg);
}
private:
T *p_arg = nullptr;
};
@ -184,10 +235,19 @@ namespace detail {
return *p_arg;
}
void swap(arg_wrapper &other) {
std::swap(p_arg, other.p_arg);
}
private:
T *p_arg = nullptr;
};
template<typename T>
inline void swap(arg_wrapper<T> &a, arg_wrapper<T> &b) {
a.swap(b);
}
template<typename ...A>
struct coro_types {
using yield_type = std::tuple<A...>;
@ -222,21 +282,46 @@ namespace detail {
/* default case, yield returns args and takes a value */
template<typename R, typename ...A>
struct coro_base {
protected:
coro_base(void (*callp)(void *), size_t ss):
p_ctx(ss, callp, this)
{}
coro_args<A...> yield(R &&ret) {
p_result = std::forward<R>(ret);
p_ctx.yield_jump();
return yield_ret(p_args, std::make_index_sequence<sizeof...(A)>{});
coro_base(coro_base const &) = delete;
coro_base(coro_base &&c):
p_args(std::move(c.p_args)), p_result(std::move(c.p_result)),
p_ctx(std::move(c.p_ctx))
{
p_ctx.set_data(this);
}
protected:
template<typename F, typename C, size_t ...I>
void call_helper(F &func, C &coro, std::index_sequence<I...>) {
coro_base &operator=(coro_base const &) = delete;
coro_base &operator=(coro_base &&c) {
std::swap(p_args, c.p_args);
std::swap(p_result, c.p_result);
std::swap(p_ctx, c.p_ctx);
p_ctx.set_data(this);
return *this;
}
struct yielder {
yielder(coro_base<R, A...> &coro): p_coro(coro) {}
coro_args<A...> operator()(R &&ret) {
p_coro.p_result = std::forward<R>(ret);
p_coro.p_ctx.yield_jump();
return yield_ret(
p_coro.p_args, std::make_index_sequence<sizeof...(A)>{}
);
}
private:
coro_base<R, A...> &p_coro;
};
template<typename F, size_t ...I>
void call_helper(F &func, std::index_sequence<I...>) {
p_result = std::forward<R>(
func(coro, std::forward<A>(std::get<I>(p_args))...)
func(yielder{*this}, std::forward<A>(std::get<I>(p_args))...)
);
}
@ -246,6 +331,12 @@ namespace detail {
return std::forward<R>(p_result);
}
void swap(coro_base &other) {
std::swap(p_args, other.p_args);
std::swap(p_result, other.p_result);
std::swap(p_ctx, other.p_ctx);
}
std::tuple<arg_wrapper<A>...> p_args;
arg_wrapper<R> p_result;
coroutine_context p_ctx;
@ -254,19 +345,41 @@ namespace detail {
/* yield takes a value but doesn't return any args */
template<typename R>
struct coro_base<R> {
protected:
coro_base(void (*callp)(void *), size_t ss):
p_ctx(ss, callp, this)
{}
void yield(R &&ret) {
p_result = std::forward<R>(ret);
p_ctx.yield_jump();
coro_base(coro_base const &) = delete;
coro_base(coro_base &&c):
p_result(std::move(c.p_result)),
p_ctx(std::move(c.p_ctx))
{
p_ctx.set_data(this);
}
protected:
template<typename F, typename C, size_t ...I>
void call_helper(F &func, C &coro, std::index_sequence<I...>) {
p_result = std::forward<R>(func(coro));
coro_base &operator=(coro_base const &) = delete;
coro_base &operator=(coro_base &&c) {
std::swap(p_result, c.p_result);
std::swap(p_ctx, c.p_ctx);
p_ctx.set_data(this);
return *this;
}
struct yielder {
yielder(coro_base<R> &coro): p_coro(coro) {}
void operator()(R &&ret) {
p_coro.p_result = std::forward<R>(ret);
p_coro.p_ctx.yield_jump();
}
private:
coro_base<R> &p_coro;
};
template<typename F, size_t ...I>
void call_helper(F &func, std::index_sequence<I...>) {
p_result = std::forward<R>(func(yielder{*this}));
}
R call() {
@ -274,6 +387,11 @@ namespace detail {
return std::forward<R>(this->p_result);
}
void swap(coro_base &other) {
std::swap(p_result, other.p_result);
std::swap(p_ctx, other.p_ctx);
}
arg_wrapper<R> p_result;
coroutine_context p_ctx;
};
@ -281,19 +399,43 @@ namespace detail {
/* yield doesn't take a value and returns args */
template<typename ...A>
struct coro_base<void, A...> {
protected:
coro_base(void (*callp)(void *), size_t ss):
p_ctx(ss, callp, this)
{}
coro_args<A...> yield() {
p_ctx.yield_jump();
return yield_ret(p_args, std::make_index_sequence<sizeof...(A)>{});
coro_base(coro_base const &) = delete;
coro_base(coro_base &&c):
p_args(std::move(c.p_args)),
p_ctx(std::move(c.p_ctx))
{
p_ctx.set_data(this);
}
protected:
template<typename F, typename C, size_t ...I>
void call_helper(F &func, C &coro, std::index_sequence<I...>) {
func(coro, std::forward<A>(std::get<I>(p_args))...);
coro_base &operator=(coro_base const &) = delete;
coro_base &operator=(coro_base &&c) {
std::swap(p_args, c.p_args);
std::swap(p_ctx, c.p_ctx);
p_ctx.set_data(this);
return *this;
}
struct yielder {
yielder(coro_base<void, A...> &coro): p_coro(coro) {}
coro_args<A...> operator()() {
p_coro.p_ctx.yield_jump();
return yield_ret(
p_coro.p_args, std::make_index_sequence<sizeof...(A)>{}
);
}
private:
coro_base<void, A...> &p_coro;
};
template<typename F, size_t ...I>
void call_helper(F &func, std::index_sequence<I...>) {
func(yielder{*this}, std::forward<A>(std::get<I>(p_args))...);
}
void call(A ...args) {
@ -301,6 +443,11 @@ namespace detail {
p_ctx.call();
}
void swap(coro_base &other) {
std::swap(p_args, other.p_args);
std::swap(p_ctx, other.p_ctx);
}
std::tuple<arg_wrapper<A>...> p_args;
coroutine_context p_ctx;
};
@ -308,35 +455,69 @@ namespace detail {
/* yield doesn't take a value or return any args */
template<>
struct coro_base<void> {
protected:
coro_base(void (*callp)(void *), size_t ss):
p_ctx(ss, callp, this)
{}
void yield() {
p_ctx.yield_jump();
coro_base(coro_base const &) = delete;
coro_base(coro_base &&c): p_ctx(std::move(c.p_ctx)) {
p_ctx.set_data(this);
}
protected:
template<typename F, typename C, size_t ...I>
void call_helper(F &func, C &coro, std::index_sequence<I...>) {
func(coro);
coro_base &operator=(coro_base const &) = delete;
coro_base &operator=(coro_base &&c) {
std::swap(p_ctx, c.p_ctx);
p_ctx.set_data(this);
return *this;
}
struct yielder {
yielder(coro_base<void> &coro): p_coro(coro) {}
void operator()() {
p_coro.p_ctx.yield_jump();
}
private:
coro_base<void> &p_coro;
};
template<typename F, size_t ...I>
void call_helper(F &func, std::index_sequence<I...>) {
func(yielder{*this});
}
void call() {
p_ctx.call();
}
void swap(coro_base &other) {
std::swap(p_ctx, other.p_ctx);
}
coroutine_context p_ctx;
};
} /* namespace detail */
template<typename R, typename ...A>
struct coroutine<R(A...)>: detail::coro_base<R, A...> {
private:
using base_t = detail::coro_base<R, A...>;
public:
using yield_type = typename detail::coro_base<R, A...>::yielder;
template<typename F>
coroutine(F func, size_t ss = COROUTINE_DEFAULT_STACK_SIZE):
detail::coro_base<R, A...>(&context_call, ss), p_func(std::move(func))
{}
coroutine(coroutine const &) = delete;
coroutine(coroutine &&) = default;
coroutine &operator=(coroutine const &) = delete;
coroutine &operator=(coroutine &&) = default;
~coroutine() {
this->p_ctx.unwind();
}
@ -345,18 +526,33 @@ struct coroutine<R(A...)>: detail::coro_base<R, A...> {
return !this->p_ctx.is_done();
}
R resume(A ...args) {
return this->call(std::forward<A>(args)...);
}
R operator()(A ...args) {
return this->call(std::forward<A>(args)...);
}
void swap(coroutine &other) {
std::swap(p_func, other.p_func);
base_t::swap(other);
}
private:
static void context_call(void *data) {
coroutine &self = *(static_cast<coroutine *>(data));
self.call_helper(self.p_func, self, std::index_sequence_for<A...>{});
self.call_helper(self.p_func, std::index_sequence_for<A...>{});
}
std::function<R(coroutine<R(A...)> &, A...)> p_func;
std::function<R(yield_type, A...)> p_func;
};
template<typename R, typename ...A>
inline void swap(coroutine<R(A...)> &a, coroutine<R(A...)> &b) {
a.swap(b);
}
template<typename T>
struct generator: input_range<generator<T>> {
using range_category = input_range_tag;