You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

157 lines
2.8 KiB

#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()});
}
}
}
}