.. _module-pw_async2:

=============
pw_async2
=============
.. pigweed-module::
   :name: pw_async2

   - **Simple Ownership**: Say goodbye to that jumble of callbacks and shared
     state! Complex tasks with many concurrent elements can be expressed by
     simply combining smaller tasks.
   - **Efficient**: No dynamic memory allocation required.
   - **Pluggable**: Your existing event loop, work queue, or task scheduler
     can run the ``Dispatcher`` without any extra threads.
   - **Coroutine-capable**: C++20 coroutines and Rust ``async fn`` work just
     like other tasks, and can easily plug into an existing ``pw_async2``
     systems.

:cpp:class:`pw::async2::Task` is Pigweed's async primitive. ``Task`` objects
are cooperatively-scheduled "threads" which yield to the
:cpp:class:`pw::async2::Dispatcher` when waiting. When the ``Task`` is able to make
progress, the ``Dispatcher`` will run it again. For example:

.. code-block:: cpp

   #include "pw_async2/dispatcher.h"
   #include "pw_async2/poll.h"

   #include "pw_result/result.h"

   using ::pw::async2::Context;
   using ::pw::async2::Poll;
   using ::pw::async2::Ready;
   using ::pw::async2::Pending;
   using ::pw::async2::Task;

   class ReceiveAndSend: public Task {
    public:
     ReceiveAndSend(Receiver receiver, Sender sender):
       receiver_(receiver), sender_(sender) {}

     Poll<> Pend(Context& cx) {
       if (!send_future_) {
         // ``PendReceive`` checks for available data or errors.
         //
         // If no data is available, it will grab a ``Waker`` from
         // ``cx.Waker()`` and return ``Pending``. When data arrives,
         // it will call ``waker.Wake()`` which tells the ``Dispatcher`` to
         // ``Pend`` this ``Task`` again.
         Poll<pw::Result<Data>> new_data = receiver_.PendReceive(cx);
         if (new_data.is_pending()) {
           // The ``Task`` is still waiting on data. Return ``Pending``,
           // yielding to the dispatcher. ``Pend`` will be called again when
           // data becomes available.
           return Pending();
         }
         if (!new_data->ok()) {
           PW_LOG_ERROR("Receiving failed: %s", data->status().str());
           // The ``Task`` completed;
           return Ready();
         }
         Data& data = **new_data;
         send_future_ = sender_.Send(std::move(data));
       }
       // ``PendSend`` attempts to send ``data_``, returning ``Pending`` if
       // ``sender_`` was not yet able to accept ``data_``.
       Poll<pw::Status> sent = send_future_.Pend(cx);
       if (sent.is_pending()) {
         return Pending();
       }
       if (!sent->ok()) {
         PW_LOG_ERROR("Sending failed: %s", sent->str());
       }
       return Ready();
     }
    private:
     Receiver receiver_;
     Sender sender_;

     // ``SendFuture`` is some type returned by `Sender::Send` that offers a
     // ``Pend`` method similar to the one on ``Task``.
     std::optional<SendFuture> send_future_ = std::nullopt;
   };

Tasks can then be run on a :cpp:class:`pw::async2::Dispatcher` using the
:cpp:func:`pw::async2::Dispatcher::Post` method:

.. code-block:: cpp

   #include "pw_async2/dispatcher.h"

   int main() {
     ReceiveAndSendTask task(SomeMakeReceiverFn(), SomeMakeSenderFn());
     Dispatcher dispatcher;
     dispatcher.Post(task);
     dispatcher.RunUntilComplete(task);
     return 0;
   }

.. _module-pw_async2-coroutines:

----------
Coroutines
----------
C++20 users can also define tasks using coroutines!

.. literalinclude:: examples/coro.cc
   :language: cpp
   :linenos:
   :start-after: [pw_async2-examples-coro-injection]
   :end-before: [pw_async2-examples-coro-injection]

Any value with a ``Poll<T> Pend(Context&)`` method can be passed to
``co_await``, which will return with a ``T`` when the result is ready.

To return from a coroutine, ``co_return <expression>`` must be used instead of
the usual ``return <expression>`` syntax. Because of this, the
:c:macro:`PW_TRY` and :c:macro:`PW_TRY_ASSIGN` macros are not usable within
coroutines. :c:macro:`PW_CO_TRY` and :c:macro:`PW_CO_TRY_ASSIGN` should be
used instead.

For a more detailed explanation of Pigweed's coroutine support, see the
documentation on the :cpp:class:`pw::async2::Coro<T>` type.

-----------------
C++ API reference
-----------------
.. doxygenclass:: pw::async2::Task
  :members:

.. doxygenclass:: pw::async2::Poll
  :members:

.. doxygenfunction:: pw::async2::Ready()

.. doxygenfunction:: pw::async2::Ready(std::in_place_t, Args&&... args)

.. doxygenfunction:: pw::async2::Ready(T&& value)

.. doxygenfunction:: pw::async2::Pending()

.. doxygenclass:: pw::async2::Context
  :members:

.. doxygenclass:: pw::async2::Waker
  :members:

.. doxygenclass:: pw::async2::Dispatcher
  :members:

.. doxygenclass:: pw::async2::Coro
  :members:

.. doxygenclass:: pw::async2::CoroContext
  :members:

-------------
C++ Utilities
-------------
.. doxygenfunction:: pw::async2::AllocateTask(pw::allocator::Allocator& allocator, Pendable&& pendable)

.. doxygenfunction:: pw::async2::AllocateTask(pw::allocator::Allocator& allocator, Args&&... args)

.. doxygenclass:: pw::async2::PendFuncTask
  :members:

.. doxygenclass:: pw::async2::PendableAsTask
  :members:

.. toctree::
   :hidden:
   :maxdepth: 1

   Backends <backends>
