2025-01-23 12:46:52 -08:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "connection.hpp"
|
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
#include <chrono>
|
2025-01-23 12:46:52 -08:00
|
|
|
#include <coroutine>
|
2025-01-24 14:48:15 -08:00
|
|
|
#include <initializer_list>
|
|
|
|
#include <utility>
|
2025-01-23 12:46:52 -08:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
struct irc_promise;
|
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
/// A coroutine that can co_await on various IRC events
|
2025-01-25 15:45:31 -08:00
|
|
|
struct irc_coroutine : std::coroutine_handle<irc_promise>
|
|
|
|
{
|
2025-01-23 12:46:52 -08:00
|
|
|
using promise_type = irc_promise;
|
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
/// Start the coroutine and associate it with a specific connection.
|
2025-01-23 12:46:52 -08:00
|
|
|
auto start(Connection &connection) -> void;
|
2025-01-24 14:48:15 -08:00
|
|
|
|
|
|
|
/// Returns true when this coroutine is still waiting on events
|
2025-01-23 12:46:52 -08:00
|
|
|
auto is_running() -> bool;
|
2025-01-24 14:48:15 -08:00
|
|
|
|
|
|
|
/// Returns the exception that terminated this coroutine, if there is one.
|
2025-01-23 12:46:52 -08:00
|
|
|
auto exception() -> std::exception_ptr;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct irc_promise
|
|
|
|
{
|
|
|
|
// Pointer to the connection while running. Cleared on termination.
|
|
|
|
std::shared_ptr<Connection> connection_;
|
|
|
|
|
|
|
|
// Pointer to exception that terminated this coroutine if there is one.
|
|
|
|
std::exception_ptr exception_;
|
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
auto get_return_object() -> irc_coroutine
|
2025-01-23 12:46:52 -08:00
|
|
|
{
|
|
|
|
return {irc_coroutine::from_promise(*this)};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Suspend waiting for start() to initialize connection_
|
2025-01-24 14:48:15 -08:00
|
|
|
auto initial_suspend() noexcept -> std::suspend_always { return {}; }
|
2025-01-23 12:46:52 -08:00
|
|
|
|
|
|
|
// Suspend so that is_running() and exception() work
|
2025-01-24 14:48:15 -08:00
|
|
|
auto final_suspend() noexcept -> std::suspend_always { return {}; }
|
2025-01-23 12:46:52 -08:00
|
|
|
|
|
|
|
// Normal termination
|
2025-01-25 15:45:31 -08:00
|
|
|
auto return_void() -> void
|
|
|
|
{
|
2025-01-23 12:46:52 -08:00
|
|
|
connection_.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Abnormal termination - remember the exception
|
2025-01-25 15:45:31 -08:00
|
|
|
auto unhandled_exception() -> void
|
|
|
|
{
|
2025-01-23 12:46:52 -08:00
|
|
|
connection_.reset();
|
|
|
|
exception_ = std::current_exception();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-01-25 15:45:31 -08:00
|
|
|
template <typename... Ts>
|
2025-01-24 14:48:15 -08:00
|
|
|
class Wait;
|
|
|
|
|
|
|
|
/// Argument to a Wait that expects one or more IRC messages
|
2025-01-25 15:45:31 -08:00
|
|
|
class wait_ircmsg
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
// Vector of commands this wait is expecting. Leave empty to accept all messages.
|
2025-01-23 12:46:52 -08:00
|
|
|
std::vector<IrcCommand> want_cmds_;
|
2025-01-24 14:48:15 -08:00
|
|
|
|
|
|
|
// Slot for the ircmsg event
|
|
|
|
boost::signals2::scoped_connection ircmsg_slot_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
using result_type = std::pair<IrcCommand, const IrcMsg &>;
|
|
|
|
|
2025-01-25 15:45:31 -08:00
|
|
|
wait_ircmsg(std::initializer_list<IrcCommand> want_cmds)
|
|
|
|
: want_cmds_{want_cmds}
|
|
|
|
{
|
|
|
|
}
|
2025-01-24 14:48:15 -08:00
|
|
|
|
2025-01-25 15:45:31 -08:00
|
|
|
template <size_t I, typename... Ts>
|
|
|
|
auto start(Wait<Ts...> &command) -> void;
|
2025-01-28 20:01:51 -08:00
|
|
|
auto stop() -> void { ircmsg_slot_.disconnect(); }
|
|
|
|
};
|
|
|
|
|
|
|
|
class wait_snote
|
|
|
|
{
|
|
|
|
// Vector of tags this wait is expecting. Leave empty to accept all messages.
|
|
|
|
std::vector<SnoteTag> want_tags_;
|
|
|
|
|
|
|
|
// Slot for the snote event
|
|
|
|
boost::signals2::scoped_connection snote_slot_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
using result_type = SnoteMatch;
|
|
|
|
|
|
|
|
wait_snote(std::initializer_list<SnoteTag> want_tags)
|
|
|
|
: want_tags_{want_tags}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
template <size_t I, typename... Ts>
|
|
|
|
auto start(Wait<Ts...> &command) -> void;
|
|
|
|
auto stop() -> void { snote_slot_.disconnect(); }
|
2025-01-23 12:46:52 -08:00
|
|
|
};
|
|
|
|
|
2025-01-25 15:45:31 -08:00
|
|
|
class wait_timeout
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
std::optional<boost::asio::steady_timer> timer_;
|
|
|
|
std::chrono::milliseconds timeout_;
|
|
|
|
|
|
|
|
public:
|
2025-01-25 15:45:31 -08:00
|
|
|
struct result_type
|
|
|
|
{
|
|
|
|
};
|
|
|
|
wait_timeout(std::chrono::milliseconds timeout)
|
|
|
|
: timeout_{timeout}
|
|
|
|
{
|
|
|
|
}
|
2025-01-24 14:48:15 -08:00
|
|
|
|
2025-01-25 15:45:31 -08:00
|
|
|
template <size_t I, typename... Ts>
|
|
|
|
auto start(Wait<Ts...> &command) -> void;
|
2025-01-28 20:01:51 -08:00
|
|
|
auto stop() -> void { timer_->cancel(); }
|
2025-01-23 12:46:52 -08:00
|
|
|
};
|
|
|
|
|
2025-01-25 15:45:31 -08:00
|
|
|
template <typename... Ts>
|
|
|
|
class Wait
|
|
|
|
{
|
2025-01-23 12:46:52 -08:00
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
// State associated with each wait mode
|
|
|
|
std::tuple<Ts...> modes_;
|
|
|
|
|
|
|
|
// Result from any one of the wait modes
|
|
|
|
std::optional<std::variant<typename Ts::result_type...>> result_;
|
|
|
|
|
|
|
|
// Handle of the continuation to be resumed when one of the wait
|
|
|
|
// modes is ready.
|
|
|
|
std::coroutine_handle<irc_promise> handle_;
|
2025-01-23 12:46:52 -08:00
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
// Slot for tearing down the irc_coroutine in case the connection
|
|
|
|
// fails before any wait modes complete.
|
|
|
|
boost::signals2::scoped_connection disconnect_slot_;
|
2025-01-23 12:46:52 -08:00
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
template <size_t I>
|
2025-01-25 15:45:31 -08:00
|
|
|
auto start_mode() -> void
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
std::get<I>(modes_).template start<I, Ts...>(*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <std::size_t... Indices>
|
2025-01-25 15:45:31 -08:00
|
|
|
auto start_modes(std::index_sequence<Indices...>) -> void
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
(start_mode<Indices>(), ...);
|
|
|
|
}
|
2025-01-23 12:46:52 -08:00
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
template <std::size_t... Indices>
|
2025-01-25 15:45:31 -08:00
|
|
|
auto stop_modes(std::index_sequence<Indices...>) -> void
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
(std::get<Indices>(modes_).stop(), ...);
|
2025-01-23 12:46:52 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2025-01-25 15:45:31 -08:00
|
|
|
Wait(Ts &&...modes)
|
|
|
|
: modes_{std::forward<Ts>(modes)...}
|
|
|
|
{
|
|
|
|
}
|
2025-01-23 12:46:52 -08:00
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
// Get the connection that this coroutine was started with.
|
2025-01-25 15:45:31 -08:00
|
|
|
auto get_connection() const -> Connection &
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
return *handle_.promise().connection_;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store a successful result and resume the coroutine
|
|
|
|
template <size_t I, typename... Args>
|
2025-01-25 15:45:31 -08:00
|
|
|
auto complete(Args &&...args) -> void
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
result_.emplace(std::in_place_index<I>, std::forward<Args>(args)...);
|
|
|
|
handle_.resume();
|
|
|
|
}
|
|
|
|
|
|
|
|
// The coroutine always needs to wait for a message. It will never
|
|
|
|
// be ready immediately.
|
|
|
|
auto await_ready() noexcept -> bool { return false; }
|
2025-01-23 12:46:52 -08:00
|
|
|
|
|
|
|
/// Install event handles in the connection that will resume this coroutine.
|
2025-01-24 14:48:15 -08:00
|
|
|
auto await_suspend(std::coroutine_handle<irc_promise> handle) -> void;
|
2025-01-23 12:46:52 -08:00
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
auto await_resume() -> std::variant<typename Ts::result_type...>;
|
2025-01-23 12:46:52 -08:00
|
|
|
};
|
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
template <size_t I, typename... Ts>
|
|
|
|
auto wait_ircmsg::start(Wait<Ts...> &command) -> void
|
|
|
|
{
|
|
|
|
ircmsg_slot_ = command.get_connection().sig_ircmsg.connect([this, &command](auto cmd, auto &msg) {
|
2025-01-25 15:45:31 -08:00
|
|
|
const auto wanted = want_cmds_.empty() || std::find(want_cmds_.begin(), want_cmds_.end(), cmd) != want_cmds_.end();
|
|
|
|
if (wanted)
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
command.template complete<I>(cmd, msg);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-01-28 20:01:51 -08:00
|
|
|
template <size_t I, typename... Ts>
|
|
|
|
auto wait_snote::start(Wait<Ts...> &command) -> void
|
|
|
|
{
|
|
|
|
snote_slot_ = command.get_connection().sig_snote.connect([this, &command](auto &match) {
|
|
|
|
const auto tag = match.get_tag();
|
|
|
|
const auto wanted = want_tags_.empty() || std::find(want_tags_.begin(), want_tags_.end(), tag) != want_tags_.end();
|
|
|
|
if (wanted)
|
|
|
|
{
|
|
|
|
command.template complete<I>(match);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-01-24 14:48:15 -08:00
|
|
|
template <size_t I, typename... Ts>
|
2025-01-25 15:45:31 -08:00
|
|
|
auto wait_timeout::start(Wait<Ts...> &command) -> void
|
2025-01-24 14:48:15 -08:00
|
|
|
{
|
|
|
|
timer_.emplace(command.get_connection().get_executor());
|
|
|
|
timer_->expires_after(timeout_);
|
2025-01-25 15:45:31 -08:00
|
|
|
timer_->async_wait([this, &command](const auto &error) {
|
|
|
|
if (not error)
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
timer_.reset();
|
|
|
|
command.template complete<I>();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename... Ts>
|
|
|
|
auto Wait<Ts...>::await_suspend(std::coroutine_handle<irc_promise> handle) -> void
|
|
|
|
{
|
|
|
|
handle_ = handle;
|
|
|
|
|
2025-01-25 15:45:31 -08:00
|
|
|
const auto tuple_size = std::tuple_size_v<decltype(modes_)>;
|
2025-01-24 14:48:15 -08:00
|
|
|
start_modes(std::make_index_sequence<tuple_size>{});
|
|
|
|
|
|
|
|
disconnect_slot_ = get_connection().sig_disconnect.connect([this]() {
|
|
|
|
handle_.resume();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename... Ts>
|
|
|
|
auto Wait<Ts...>::await_resume() -> std::variant<typename Ts::result_type...>
|
|
|
|
{
|
2025-01-25 15:45:31 -08:00
|
|
|
const auto tuple_size = std::tuple_size_v<decltype(modes_)>;
|
2025-01-24 14:48:15 -08:00
|
|
|
stop_modes(std::make_index_sequence<tuple_size>{});
|
|
|
|
|
|
|
|
disconnect_slot_.disconnect();
|
|
|
|
|
2025-01-25 15:45:31 -08:00
|
|
|
if (result_)
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
return std::move(*result_);
|
2025-01-25 15:45:31 -08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2025-01-24 14:48:15 -08:00
|
|
|
throw std::runtime_error{"connection terminated"};
|
|
|
|
}
|
|
|
|
}
|
2025-01-28 20:01:51 -08:00
|
|
|
|
|
|
|
/// Start the coroutine and associate it with a specific connection.
|
|
|
|
inline auto irc_coroutine::start(Connection &connection) -> void
|
|
|
|
{
|
|
|
|
promise().connection_ = connection.shared_from_this();
|
|
|
|
resume();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns true when this coroutine is still waiting on events
|
|
|
|
inline auto irc_coroutine::is_running() -> bool
|
|
|
|
{
|
|
|
|
return promise().connection_ != nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline auto irc_coroutine::exception() -> std::exception_ptr
|
|
|
|
{
|
|
|
|
return promise().exception_;
|
|
|
|
}
|