Integrating External Awaitables#
Default Behavior - It Just Works#
When awaiting an awaitable that is unknown to TMC, by default, the awaitable will be wrapped into
a tmc::task
that it restores the original awaiting task back to its original executor
and priority when it is resumed. This also provides full compatibility with all TMC functions (such as the spawn() customizer functions)
for any awaitable type, but it does have a small performance overhead.
Fully Integrating#
If you want to remove the overhead of the wrapper task, and allow your awaitable to be configured directly,
you can implement a specialization of the tmc::detail::awaitable_traits
struct. However, when implementing this,
you must implement not only the code contract laid out in this document, but also the following behaviors:
When your awaitable completes, by default, the awaiting TMC task must be resumed on its original executor, at its original priority.
If you choose to implement a mode other than WRAPPER, you must implement shared atomic synchronization and awaiting coroutine resumption behavior that is compatible with the existing code.
namespace tmc::detail {
template <typename Awaitable> struct awaitable_traits;
}
The members of this struct are defined below:
/** REQUIRED **/
// Define the result type of `co_await YourAwaitable;`
// This should be a value type, not a reference type.
using result_type = your_result_type;
These members control the behavior when awaited directly using co_await
from within a tmc::task:
/** OPTIONAL **/
// Tells the tmc::task await_transform how to get the awaitable type for this.
// If unimplemented, the default wrapper will be used.
// You should consider whether you want your awaitable to be one-shot or multi-shot,
// which informs whether you should expose lvalue or rvalue overloads of this.
// Some sample implementations are provided below:
static awaiter_type get_awaiter(self_type& Awaitable) noexcept {
return awaiter_type(Awaitable);
}
static awaiter_type get_awaiter(self_type&& Awaitable) noexcept {
return static_cast<self_type&&>(awaitable).operator co_await();
}
These members control the behavior when awaited as part of a group using spawn()
, spawn_many()
or spawn_tuple()
:
/** REQUIRED **/
static constexpr configure_mode mode = /* mode value */;
- The mode options are:
WRAPPER : the default behavior will be used, which is to wrap the awaitable in a task for configuration.
COROUTINE : When initiating the async process, the awaitable will be submitted to the TMC executor to be resumed. It may also be resumed directly using symmetric transfer.
requires {std::coroutine_handle<> c = declval<YourAwaitable>();}
ASYNC_INITIATE : When initiating the async process, the async_initiate() function will be called.
If the mode is not WRAPPER, you must define the following functions. These functions expect your awaitable to conform to the behavior of other TMC awaitables, including shared atomic synchronization of simultaneous completion.
// Will provide a pointer to the storage for a result before an awaitable
// is initiated. If result_type is default-constructible, a pointer to result_type will be provided.
// If result_type is not default-constructible, a pointer to std::optional<result_type> will be provided.
static void set_result_ptr(
Awaitable& YourAwaitable,
tmc::detail::result_storage_t<result_type>* ResultPtr
);
// Will provide std::coroutine_handle<> or std::coroutine_handle<>* depending on the value of done_count.
static void set_continuation(Awaitable& YourAwaitable, void* Continuation);
// May provide a tmc::ex_any* or tmc::ex_any** depending on the value of done_count.
static void set_continuation_executor(Awaitable& YourAwaitable, void* ContExec);
// May provide a std::atomic<uint64_t>* or std::atomic<int64_t>* depending on the value of flags.
static void set_done_count(Awaitable& YourAwaitable, void* DoneCount);
// See the implementation of tmc::detail::awaitable_customizer_base.resume_continuation()
static void set_flags(Awaitable& YourAwaitable, uint64_t Flags);
Additionally, if the mode is ASYNC_INITIATE, you must define the following function. It will be called to initiate the async operation. The current TMC executor and priority will be passed in, but they are not required to be used.
static void async_initiate(
Awaitable&& YourAwaitable,
tmc::ex_any* Executor,
size_t Priority
) {}
Example Implementation#
An example of a complete implementation is here: tmc::aw_asio. It embeds a tmc::detail::awaitable_customizer and delegates the atomic synchronization and awaiting coroutine resumption to that. This is the simplest way to implement the complete integration.