#pragma once
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <list>
|
|
#include <mutex>
|
|
#include <memory>
|
|
#include <random>
|
|
|
|
namespace rp_piano {
|
|
class coro;
|
|
class abstract_channel;
|
|
|
|
template<typename T>
|
|
struct locked_list;
|
|
}
|
|
|
|
extern "C" {
|
|
void spawn(rp_piano::coro&&);
|
|
}
|
|
|
|
constexpr size_t queue_count = 4;
|
|
|
|
extern std::array<rp_piano::locked_list<rp_piano::coro>, queue_count> sched_queue;
|
|
|
|
namespace rp_piano {
|
|
|
|
template<typename T>
|
|
struct locked_list {
|
|
std::list<T> data;
|
|
std::mutex mtx;
|
|
|
|
std::optional<T> pop() {
|
|
std::lock_guard<std::mutex> _g(mtx);
|
|
if(data.size())
|
|
{
|
|
T ret = std::move(data.front());
|
|
data.pop_front();
|
|
return ret;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
void push(std::list<T>&& in) {
|
|
std::lock_guard<std::mutex> _g(mtx);
|
|
data.splice(std::end(data), in);
|
|
}
|
|
|
|
size_t size() {
|
|
std::lock_guard<std::mutex> _g(mtx);
|
|
return data.size();
|
|
}
|
|
};
|
|
|
|
class abstract_channel {
|
|
public:
|
|
std::shared_ptr<locked_list<coro>> waiters;
|
|
abstract_channel()
|
|
: waiters(std::make_shared<locked_list<coro>>())
|
|
{}
|
|
|
|
std::optional<coro> get_waiter();
|
|
void put_waiter(std::list<coro>);
|
|
|
|
void receive_cb();
|
|
|
|
virtual ~abstract_channel() = default;
|
|
};
|
|
|
|
template<typename T>
|
|
class channel : public abstract_channel {
|
|
locked_list<T> values;
|
|
public:
|
|
void send(T&& v) {
|
|
values.push({std::forward<T>(v)});
|
|
}
|
|
|
|
void send(std::list<T>&& v) {
|
|
values.push(std::forward<T>(v));
|
|
}
|
|
|
|
std::optional<T> receive() {
|
|
receive_cb();
|
|
return values.pop();
|
|
}
|
|
};
|
|
|
|
class coro {
|
|
using corotype = std::function<void(coro&)>;
|
|
std::list<coro> continuations;
|
|
corotype implementation = [](coro&){};
|
|
std::optional<abstract_channel> go_wait;
|
|
bool resched = false;
|
|
public:
|
|
template<typename T>
|
|
coro(T v)
|
|
: implementation(v)
|
|
{}
|
|
|
|
coro() = default;
|
|
|
|
coro& then(coro&& v)
|
|
{
|
|
continuations.emplace_back(std::move(v));
|
|
return continuations.back();
|
|
}
|
|
|
|
void operator() () {
|
|
static thread_local std::default_random_engine eng{};
|
|
if(go_wait)
|
|
{
|
|
go_wait.value().put_waiter({*this});
|
|
go_wait.reset();
|
|
return;
|
|
}
|
|
implementation(*this);
|
|
if(resched)
|
|
{
|
|
resched = false;
|
|
spawn(std::move(*this));
|
|
return;
|
|
}
|
|
if(go_wait)
|
|
{
|
|
go_wait.value().put_waiter({*this});
|
|
go_wait.reset();
|
|
} else {
|
|
sched_queue[eng() & (sched_queue.size() - 1)].push(std::move(continuations));
|
|
}
|
|
return;
|
|
}
|
|
|
|
coro& rewake_with(abstract_channel chan) {
|
|
go_wait = chan;
|
|
return *this;
|
|
}
|
|
|
|
void reschedule() {
|
|
resched = true;
|
|
}
|
|
};
|
|
|
|
inline void abstract_channel::receive_cb() {
|
|
static thread_local std::default_random_engine generator;
|
|
if(waiters)
|
|
{
|
|
while(true)
|
|
{
|
|
auto waitee = waiters->pop();
|
|
if(!waitee)
|
|
{
|
|
return;
|
|
}
|
|
sched_queue[generator() & (sched_queue.size() - 1)].push({waitee.value()});
|
|
}
|
|
}
|
|
}
|
|
}
|