Skip to content

Commit

Permalink
feat: add coroutine support
Browse files Browse the repository at this point in the history
  • Loading branch information
OEOTYAN committed Sep 9, 2024
1 parent bfb1e6a commit b51585e
Show file tree
Hide file tree
Showing 27 changed files with 423 additions and 121 deletions.
4 changes: 2 additions & 2 deletions src-server/ll/core/Statistics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#include "ll/api/schedule/Task.h"
#include "ll/api/service/Bedrock.h"
#include "ll/api/service/ServerInfo.h"
#include "ll/api/thread/ServerThreadExecuter.h"
#include "ll/api/thread/ServerThreadExecutor.h"
#include "ll/api/utils/ErrorUtils.h"
#include "ll/api/utils/RandomUtils.h"
#include "ll/api/utils/StringUtils.h"
Expand Down Expand Up @@ -106,7 +106,7 @@ struct Statistics::Impl {
nlohmann::json json;

void submitData() {
thread::ServerThreadExecuter::getDefault()
thread::ServerThreadExecutor::getDefault()
.addPackagedTask([this]() {
nlohmann::json pluginInfo;
pluginInfo["pluginName"] = getSelfModIns()->getName();
Expand Down
43 changes: 31 additions & 12 deletions src-test/common/ExecTest.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
#include "ll/core/LeviLamina.h"

#include "ll/api/chrono/GameChrono.h"
#include "ll/api/thread/ServerThreadExecuter.h"
#include "ll/api/thread/ThreadPoolExecuter.h"
#include "ll/api/coro/CoroTask.h"
#include "ll/api/thread/ServerThreadExecutor.h"
#include "ll/api/thread/ThreadPoolExecutor.h"

size_t i = 0;
using namespace ll;
using namespace coro;

static bool run = [] {
using namespace ll;
using namespace literals;

CoroTask<Expected<int>> coroutine() {
for (size_t i = 0;; i++) {
getLogger().info(
"coroutine: {}, thread: {}",
chrono::GameTickClock::now().time_since_epoch(),
std::this_thread::get_id()
);
co_await 2_tick;

// co_await yield;

auto& exec = thread::ThreadPoolExecuter::getDefault();
if (i > 10) {
break;
// throw std::runtime_error("test coroutine");
}
}
co_return 1234567;
}

static std::function<void()> fn = [&]() {
getLogger().info("now: {}", std::chrono::steady_clock::now().time_since_epoch());

exec.addTaskAfter(fn, chrono::ticks{20});
getLogger().info("erase: {}", exec.removeFromSch(exec.addTaskAfter(fn, chrono::ticks{1})));
};
// exec.addTask(fn);
static bool run = [] {
using namespace ll;

thread::ThreadPoolExecutor::getDefault().addTask([&] {
auto val = coroutine().syncLaunch(thread::ServerThreadExecutor::getDefault());
getLogger().info("coroutine done, {}", val.value());
});

return true;
}();
16 changes: 13 additions & 3 deletions src/ll/api/base/Concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#include "ll/api/base/StdInt.h"
#include "ll/api/base/TypeTraits.h"

namespace ll {
class Error;
}
namespace ll::concepts {

using traits::Require;
Expand Down Expand Up @@ -37,6 +40,10 @@ concept IsExpected = requires(T e) {
requires std::is_same_v<void, typename std::remove_cvref_t<T>::value_type> || requires(T e) { e.value(); };
};

template <class T>
concept IsLeviExpected = IsExpected<T> &&
std::same_as<typename std::remove_cvref_t<T>::error_type, Error>;

template <class T>
concept IsOptional = !IsExpected<T> && requires(T o) {
o.value();
Expand Down Expand Up @@ -90,9 +97,6 @@ concept ArrayLike = Rangeable<T> && !requires { typename std::remove_cvref_t<T>:
template <class T, template <class...> class Z>
concept DerivedFromSpecializes = traits::is_derived_from_specialization_of_v<T, Z>;

template <class T>
concept VirtualCloneable = traits::is_virtual_cloneable_v<T>;

template <class T>
concept Stringable = requires(T t) {
requires requires {
Expand All @@ -102,4 +106,10 @@ concept Stringable = requires(T t) {
};
};

template <class T>
concept VirtualCloneable = traits::is_virtual_cloneable_v<T>;

template <class T>
concept Awaitable = traits::is_awaitable_v<T>;

} // namespace ll::concepts
8 changes: 0 additions & 8 deletions src/ll/api/command/Overload.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,6 @@ class Overload : private OverloadData {
return *this;
}

template <auto Executor>
[[deprecated("deprecated: please put the function as a parameter in the bracket")]] void constexpr execute() {
using E = std::remove_cvref_t<decltype((Executor))>;
setFactory([&]() -> std::unique_ptr<::Command> {
return std::unique_ptr<Command<Params, E>>(new Command<Params, E>{Executor});
});
}

template <class Fn>
void constexpr execute(Fn&& fn) {
using E = std::remove_cvref_t<Fn>;
Expand Down
2 changes: 1 addition & 1 deletion src/ll/api/command/runtime/RuntimeCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace ll::command {
RuntimeCommand::RuntimeCommand(
StringMap<uint64> const& map,
std::vector<std::pair<std::string, ParamKindType>> const& params,
Executor const& executor
Fn const& executor
)
: executor(executor),
paramIndexMap(map),
Expand Down
6 changes: 3 additions & 3 deletions src/ll/api/command/runtime/RuntimeCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ class RuntimeCommand : public ::Command {
friend RuntimeOverload;

public:
using Executor = std::function<void(CommandOrigin const&, CommandOutput&, RuntimeCommand const&)>;
using Fn = std::function<void(CommandOrigin const&, CommandOutput&, RuntimeCommand const&)>;

private:
uint64 placeholder{};
Executor const& executor;
Fn const& executor;
StringMap<uint64> const& paramIndexMap;
size_t paramCount;

LLAPI RuntimeCommand(
StringMap<uint64> const& map,
std::vector<std::pair<std::string, ParamKindType>> const& params,
Executor const& executor
Fn const& executor
);

public:
Expand Down
2 changes: 1 addition & 1 deletion src/ll/api/command/runtime/RuntimeOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ RuntimeOverload& RuntimeOverload::deoption(CommandParameterOption option) {
back().mOptions = (CommandParameterOption)((uchar)(back().mOptions) & (!(uchar)option));
return *this;
}
void RuntimeOverload::execute(RuntimeCommand::Executor fn) {
void RuntimeOverload::execute(RuntimeCommand::Fn fn) {
std::lock_guard l{lock()};

StringMap<uint64> map;
Expand Down
2 changes: 1 addition & 1 deletion src/ll/api/command/runtime/RuntimeOverload.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class RuntimeOverload : private OverloadData {

LLNDAPI RuntimeOverload& deoption(CommandParameterOption option);

LLAPI void execute(RuntimeCommand::Executor fn);
LLAPI void execute(RuntimeCommand::Fn);

LLAPI RuntimeOverload(RuntimeOverload&&);
LLAPI ~RuntimeOverload();
Expand Down
91 changes: 91 additions & 0 deletions src/ll/api/coro/CoroPromise.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#pragma once

#include <coroutine>

#include "ll/api/Expected.h"
#include "ll/api/base/Concepts.h"
#include "ll/api/coro/ForwardAwaiter.h"
#include "ll/api/coro/SleepWaiter.h"
#include "ll/api/coro/YieldAwaiter.h"

namespace ll::coro {

template <class T>
class CoroTask;

struct CoroPromiseBase {
std::coroutine_handle<> handle;
void* local{};
ExecutorRef exec{};

constexpr CoroPromiseBase() noexcept = default;

struct FinalAwaiter {
constexpr bool await_ready() const noexcept { return false; }
template <std::derived_from<CoroPromiseBase> P>
constexpr auto await_suspend(std::coroutine_handle<P> h) noexcept {
return h.promise().handle;
}
constexpr void await_resume() noexcept {}
};
constexpr std::suspend_always initial_suspend() noexcept { return {}; }
constexpr FinalAwaiter final_suspend() noexcept { return {}; }

template <concepts::Awaitable T>
constexpr decltype(auto) await_transform(T&& awaitable) {
if constexpr (requires { awaitable.setExecutor(exec); }) {
awaitable.setExecutor(exec);
}
return std::forward<T>(awaitable);
}

constexpr ForwardAwaiter<thread::TaskExecutor const&> await_transform(CurrentExecutor) { return {exec.get()}; }

constexpr YieldAwaiter await_transform(Yield) { return {exec}; }

template <class R, class P>
constexpr SleepWaiter await_transform(std::chrono::duration<R, P> dur) {
return {dur, exec};
}
template <class C, class D>
constexpr SleepWaiter await_transform(std::chrono::time_point<C, D> time) {
return {time, exec};
}
};
template <class T>
struct CoroPromise : public CoroPromiseBase {
using ExpectedT = std::conditional_t<concepts::IsLeviExpected<T>, T, Expected<T>>;

ExpectedT result{};

constexpr CoroPromise() noexcept = default;

template <class V>
void return_value(V&& value) noexcept(std::is_nothrow_constructible_v<T, V>)
requires(std::is_constructible_v<T, V>)
{
if constexpr (std::is_same_v<T, ExpectedT>) {
result = T{std::forward<V>(value)};
} else {
result.emplace(std::forward<V>(value));
}
}
void unhandled_exception() noexcept { result = makeExceptionError(); }

constexpr CoroTask<T> get_return_object() noexcept;
};
template <>
struct CoroPromise<void> : public CoroPromiseBase {
using ExpectedT = Expected<>;

ExpectedT result{std::in_place};

constexpr CoroPromise() noexcept = default;

void return_void() noexcept {}

void unhandled_exception() noexcept { result = makeExceptionError(); }

constexpr CoroTask<void> get_return_object() noexcept;
};
} // namespace ll::coro
1 change: 1 addition & 0 deletions src/ll/api/coro/CoroTask.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "ll/api/coro/CoroTask.h"
108 changes: 104 additions & 4 deletions src/ll/api/coro/CoroTask.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,108 @@
#pragma once

#include <semaphore>

#include "ll/api/coro/CoroPromise.h"
#include "ll/api/coro/CoroTaskWaiter.h"

namespace ll::coro {
constexpr inline struct Yield {
} yield;
constexpr inline struct CurrentExecutor {
} currentExecutor;

template <class T = void>
class CoroTask {
public:
using promise_type = CoroPromise<T>;
using Handle = std::coroutine_handle<promise_type>;

using ExpectedT = typename CoroPromise<T>::ExpectedT;

friend promise_type;

private:
Handle handle;

constexpr explicit CoroTask(Handle h) noexcept : handle(h) {}

using WaiterBase = CoroTaskWaiter<T>;

public:
struct ExpectedAwaiter : public WaiterBase {
constexpr ExpectedAwaiter(Handle h) : WaiterBase(h) {}
constexpr ExpectedT await_resume() noexcept { return WaiterBase::getResult(); };
};

struct ValueAwaiter : public WaiterBase {
constexpr ValueAwaiter(Handle h) : WaiterBase(h) {}
constexpr T await_resume() {
if constexpr (std::is_same_v<T, ExpectedT>) {
return WaiterBase::getResult();
} else {
return WaiterBase::getResult().value();
}
}
};

CoroTask(CoroTask const&) = delete;
CoroTask& operator=(CoroTask const&) = delete;

CoroTask(CoroTask&& other) noexcept : handle(std::exchange(other.handle, nullptr)) {}

constexpr CoroTask& operator=(CoroTask&& other) noexcept {
std::swap(handle, other.handle);
return *this;
}

constexpr ~CoroTask() {
if (handle) {
std::exchange(handle, nullptr).destroy();
}
};

constexpr void setExecutor(ExecutorRef ex) { handle.promise().exec = ex; }

constexpr ExecutorRef getExecutor() { return handle.promise().exec; }

bool done() const { return !handle || handle.done(); }

auto operator co_await() { return ValueAwaiter(std::exchange(handle, nullptr)); }

auto tryGet() { return ExpectedAwaiter(std::exchange(handle, nullptr)); }

template <class F = decltype([](auto&&) {})>
void launch(Executor const& executor, F&& callback = {})
requires(std::is_invocable_v<F, ExpectedT>)
{
setExecutor(executor);
struct Launcher {
struct promise_type : public CoroPromiseBase {
constexpr std::suspend_never initial_suspend() noexcept { return {}; }
constexpr std::suspend_never final_suspend() noexcept { return {}; }
constexpr void return_void() noexcept {}
constexpr void unhandled_exception() {}
constexpr Launcher get_return_object() noexcept { return {}; }
};
};
[](CoroTask lazy, std::decay_t<F> cb) -> Launcher {
cb(co_await lazy.tryGet());
}(std::move(*this), std::forward<F>(callback));
}

ExpectedT syncLaunch(Executor const& executor) {
std::binary_semaphore cond{0};
ExpectedT value;
launch(executor, [&](ExpectedT&& result) {
value = std::move(result);
cond.release();
});
cond.acquire();
return value;
}
};

template <class T>
constexpr CoroTask<T> CoroPromise<T>::get_return_object() noexcept {
return CoroTask<T>(CoroTask<T>::Handle::from_promise(*this));
}
constexpr CoroTask<void> CoroPromise<void>::get_return_object() noexcept {
return CoroTask<void>(CoroTask<void>::Handle::from_promise(*this));
}
} // namespace ll::coro
Loading

0 comments on commit b51585e

Please sign in to comment.