[/
 / Copyright (c) 2021-2023 Klemens D. Morgenstern
 /                         (klemens dot morgenstern at gmx dot net)
 /
 / Distributed under the Boost Software License, Version 1.0. (See accompanying
 / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 /]

[section:coro Resumable C++ 20 Coroutines]

[note This is an experimental feature.]

The [link boost_asio.reference.experimental__coro `experimental::coro`] class provides
support for a universal C++20 coroutine. These coroutines can be used as tasks,
generators and transfomers, depending on their signature.

  coro<std::string_view> line_reader(tcp::socket stream)
  {
     while (stream.is_open())
     {
       std::array<char, 4096> buf;

       auto read = co_await stream.async_read_some(
           boost::asio::buffer(buf), deferred);

       if (read == 0u)
         continue;

       co_yield std::string_view { buf.data(), read };
     }
  }

  coro<void, std::size_t> line_logger(tcp::socket stream)
  {
    std::size_t lines_read = 0u;
    auto reader = line_reader(std::move(stream));
    while (auto l = co_await reader)
    {
      std::cout << "Read: '" << *l << "'" << std::endl;
      lines_read++;
    }
    co_return lines_read;
  }

  void read_lines(tcp::socket sock)
  {
    co_spawn(line_logger(std::move(sock),
        [](std::exception_ptr, std::size_t lines)
        {
          std::clog << "Read " << lines << " lines" << std::endl;
        }));
  }

A [link boost_asio.reference.experimental__coro `coro`] is highly configurable, so
that it can cover a set of different use cases.

  template<
      typename Yield,
      typename Return = void,
      typename Executor = any_io_executor>
  struct coro;

[heading Yield]

The `Yield` parameter designates how a `co_yield` statement behaves. It can
either be a type, like `int` or a signature with zero or one types:

  coro<void> // A coroutine with no yield
  coro<int> // A coroutine that can yield int

  coro<void()> // A coroutine with no yield
  coro<int()> // A coroutine that can yield int

  coro<int(double)> // A coroutine that can yield int and receive double

Receiving a value means that the `co_yield` statement returns a value.

  coro<int(int)> my_sum(any_io_executor)
  {
    int value = 0;
    while (true)
      value += co_yield value; //sum up all values
  }


Putting values into a coroutine can be done it two ways: either by direct
resumption (from another coro) or through async_resume. The first value gets
ignored because the coroutines are lazy.

  coro<void> c(any_io_executor exec)
  {
    auto sum = my_sum(exec);
    assert(0  == co_await sum(-1));
    assert(0  == co_await sum(10));
    assert(10 == co_await sum(15));
    assert(25 == co_await sum(0));
  }

  awaitable<void> a()
  {
    auto sum = my_sum(co_await this_coro::executor);
    assert(0  == co_await sum.async_resume(-1, use_awaitable));
    assert(0  == co_await sum.async_resume(10, use_awaitable));
    assert(10 == co_await sum.async_resume(15, use_awaitable));
    assert(25 == co_await sum.async_resume(0, use_awaitable));
  }

[heading `noexcept`]

A coro may be noexcept:

  coro<void() noexcept> c;
  coro<int() noexcept> c;
  coro<int(double) noexcept> c;

This will change its @c async_resume signature, from `void(std::exception_ptr)`
to `void()` or `void(std::exception_ptr, T)` to `void(T)`. A noexcept coro that
ends with an exception will cause `std::terminate` to be called.

Furthermore, calls of `async_resume` and `co_await` of an expired noexcept coro
will cause undefined behaviour.

[heading Return]

A coro can also define a type that can be used with `co_return`:

  coro<void() noexcept, int> c(any_io_executor)
  {
    co_return 42;
  }

A coro can have both a `Yield` and `Return` that are non void at the same time.

[heading Result]

The result type of a coroutine is dermined by both `Yield` and `Return`. Note
that in the follwing table only the yield output value is considered, i.e.
`T(U)` means `T`.

[table:result_type Result type deduction
    [[Yield] [Return] [`noexcept`] [`result_type`] [`completion_signature`]]
    [[`T`] [`U`] [`false`] [`variant<T, U>`] [`void(std::exception_ptr, variant<T, U>)`]]
    [[`T`] [`U`] [`true`]  [`variant<T, U>`] [`void(variant<T, U>)`]]
    [[`T`] [`void`] [`false`] [`optional<T>`] [`void(std::exception_ptr, optional<T>)`]]
    [[`T`] [`void`] [`true`]  [`optional<T>`] [`void(optional<T>)`]]
    [[`void`] [`void`] [`false`] [`optional<T>`] [`void(std::exception_ptr)`]]
    [[`void`] [`void`] [`true`]  [`optional<T>`] [`void()`]]
    [[`void`] [`T`] [`false`] [`optional<T>`] [`void(std::exception_ptr, T)`]]
    [[`void`] [`T`] [`true`]  [`optional<T>`] [`void(T)`]]
]

[heading Executor]

Every coroutine needs to have its own executor. Since the coroutine gets called
multiple times, it cannot take the executor from the caller like an
`awaitable`. Therefore a `coro` requires to get an executor or an
execution_context passed in as the first parameter.

  coro<int> with_executor(any_io_executor);
  coro<int> with_context(io_context &);

It is to note, that an execution_context is defined as loosely as possible. An
execution context is any object that has a `get_executor()` function, which
returns an executor that can be transformed into the executor_type of the
coroutine. This allows most io_objects to be used as the source of the
executor:

  coro<int> with_socket(tcp::socket);

Additionally, a `coro` that is a member function will check the `this` pointer
as well, either if it's an executor or an execution context:

  struct my_io_object
  {
    any_io_executor get_executor();

    coro<int> my_coro();
  };

Finally, a member coro can be given an explicit executor or execution
context, to override the one of the object:

  struct my_io_object
  {
    any_io_executor get_executor();

    coro<int> my_coro(any_io_executor exec); // it will use exec
  };

[heading `co_await`]

The @c co_await within a `coro` is not the same as `async_resume(deferred)`,
unless both coros use different executors. If they use the same, the `coro`
will direclty suspend and resume the executor, without any usage of the
executor.

`co_await this_coro::` behaves the same as coroutines that use
@c boost::asio::awaitable.

[heading Integrating with awaitable]

As the `coro` member function `async_resume` is an asynchronous operation, it
may also be used in conjunction with `awaitable` coroutines in a single control
flow. For example:

  #include <asio.hpp>
  #include <boost/asio/experimental/coro.hpp>

  using boost::asio::ip::tcp;

  boost::asio::experimental::coro<std::string> reader(tcp::socket& sock)
  {
    std::string buf;
    while (sock.is_open())
    {
      std::size_t n = co_await boost::asio::async_read_until(
          sock, boost::asio::dynamic_buffer(buf), '\n',
          boost::asio::deferred);
      co_yield buf.substr(0, n);
      buf.erase(0, n);
    }
  }

  boost::asio::awaitable<void> consumer(tcp::socket sock)
  {
    auto r = reader(sock);
    auto msg1 = co_await r.async_resume(boost::asio::use_awaitable);
    std::cout << "Message 1: " << msg1.value_or("\n");
    auto msg2 = co_await r.async_resume(boost::asio::use_awaitable);
    std::cout << "Message 2: " << msg2.value_or("\n");
  }

  boost::asio::awaitable<void> listen(tcp::acceptor& acceptor)
  {
    for (;;)
    {
      co_spawn(
          acceptor.get_executor(),
          consumer(co_await acceptor.async_accept(boost::asio::use_awaitable)),
          boost::asio::detached);
    }
  }

  int main()
  {
    boost::asio::io_context ctx;
    tcp::acceptor acceptor(ctx, {tcp::v4(), 54321});
    co_spawn(ctx, listen(acceptor), boost::asio::detached);
    ctx.run();
  }

[heading See Also]

[link boost_asio.reference.co_spawn co_spawn],
[link boost_asio.reference.experimental__coro experimental::coro],
[link boost_asio.overview.composition.cpp20_coroutines C++20 Coroutines],
[link boost_asio.overview.composition.spawn Stackful Coroutines],
[link boost_asio.overview.composition.coroutine Stackless Coroutines].

[endsect]
