replace std::future with nf7::Future

This commit is contained in:
falsycat 2022-06-08 15:38:45 +09:00
parent 1e92ac27f8
commit 58ba3071c2
10 changed files with 452 additions and 311 deletions

View File

@ -48,10 +48,10 @@ target_sources(nf7
common/async_buffer.hh
common/async_buffer_adaptor.hh
common/buffer.hh
common/conditional_queue.hh
common/dir.hh
common/dir_item.hh
common/file_ref.hh
common/future.hh
common/generic_context.hh
common/generic_history.hh
common/generic_memento.hh

View File

@ -2,12 +2,12 @@
#include <cstdint>
#include <exception>
#include <future>
#include <memory>
#include "nf7.hh"
#include "common/buffer.hh"
#include "common/future.hh"
#include "common/lock.hh"
@ -23,13 +23,15 @@ class AsyncBuffer : public nf7::File::Interface, public nf7::Lock::Resource {
AsyncBuffer& operator=(const AsyncBuffer&) = delete;
AsyncBuffer& operator=(AsyncBuffer&&) = delete;
virtual std::future<size_t> Read(size_t offset, uint8_t* buf, size_t size) noexcept = 0;
virtual std::future<size_t> Write(size_t offset, const uint8_t* buf, size_t size) noexcept = 0;
virtual std::future<size_t> Truncate(size_t) noexcept = 0;
virtual nf7::Future<size_t> Read(size_t offset, uint8_t* buf, size_t size) noexcept = 0;
virtual nf7::Future<size_t> Write(size_t offset, const uint8_t* buf, size_t size) noexcept = 0;
virtual nf7::Future<size_t> Truncate(size_t) noexcept = 0;
virtual std::future<size_t> size() const noexcept = 0;
virtual nf7::Future<size_t> size() const noexcept = 0;
virtual Buffer::Flags flags() const noexcept = 0;
virtual std::shared_ptr<AsyncBuffer> self(AsyncBuffer* = nullptr) noexcept = 0;
protected:
using nf7::Lock::Resource::OnLock;
using nf7::Lock::Resource::OnUnlock;

View File

@ -1,7 +1,6 @@
#pragma once
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <utility>
@ -10,96 +9,91 @@
#include "common/async_buffer.hh"
#include "common/buffer.hh"
#include "common/future.hh"
#include "common/queue.hh"
namespace nf7 {
class AsyncBufferAdaptor : public nf7::AsyncBuffer {
class AsyncBufferAdaptor final :
public nf7::AsyncBuffer, public std::enable_shared_from_this<AsyncBufferAdaptor> {
public:
AsyncBufferAdaptor(const std::shared_ptr<nf7::Context>& ctx,
const std::shared_ptr<nf7::Buffer>& buf) noexcept :
data_(std::make_shared<Data>()) {
data_->ctx = ctx;
data_->buf = buf;
ctx_(ctx), buf_(buf) {
}
std::future<size_t> Read(size_t offset, uint8_t* ptr, size_t size) noexcept override {
return ExecWithPromise<size_t>(
[buf = data_->buf, offset, ptr, size]() {
return buf->Read(offset, ptr, size);
});
nf7::Future<size_t> Read(size_t offset, uint8_t* ptr, size_t size) noexcept override {
nf7::Future<size_t>::Promise pro;
Exec([pro, buf = buf_, offset, ptr, size]() mutable {
pro.Wrap([&]() { return buf->Read(offset, ptr, size); });
});
return pro.future();
}
std::future<size_t> Write(size_t offset, const uint8_t* ptr, size_t size) noexcept override {
return ExecWithPromise<size_t>(
[buf = data_->buf, offset, ptr, size]() {
return buf->Write(offset, ptr, size);
});
nf7::Future<size_t> Write(size_t offset, const uint8_t* ptr, size_t size) noexcept override {
nf7::Future<size_t>::Promise pro;
Exec([pro, buf = buf_, offset, ptr, size]() mutable {
pro.Wrap([&]() { return buf->Write(offset, ptr, size); });
});
return pro.future();
}
std::future<size_t> Truncate(size_t size) noexcept override {
return ExecWithPromise<size_t>(
[buf = data_->buf, size]() { return buf->Truncate(size); });
nf7::Future<size_t> Truncate(size_t size) noexcept override {
nf7::Future<size_t>::Promise pro;
Exec([pro, buf = buf_, size]() mutable {
pro.Wrap([&]() { return buf->Truncate(size); });
});
return pro.future();
}
std::future<size_t> size() const noexcept override {
return const_cast<AsyncBufferAdaptor&>(*this).
ExecWithPromise<size_t>(
[buf = data_->buf]() { return buf->size(); });
nf7::Future<size_t> size() const noexcept override {
nf7::Future<size_t>::Promise pro;
const_cast<AsyncBufferAdaptor&>(*this).Exec([pro, buf = buf_]() mutable {
pro.Wrap([&]() { return buf->size(); });
});
return pro.future();
}
Buffer::Flags flags() const noexcept override {
return data_->buf->flags();
return buf_->flags();
}
std::shared_ptr<AsyncBuffer> self(AsyncBuffer*) noexcept override {
return shared_from_this();
}
protected:
void OnLock() noexcept override {
Exec([buf = data_->buf]() { return buf->Lock(); });
Exec([buf = buf_]() { return buf->Lock(); });
}
void OnUnlock() noexcept override {
Exec([buf = data_->buf]() { return buf->Unlock(); });
Exec([buf = buf_]() { return buf->Unlock(); });
}
private:
struct Data {
std::shared_ptr<nf7::Context> ctx;
std::shared_ptr<nf7::Buffer> buf;
std::shared_ptr<nf7::Context> ctx_;
std::shared_ptr<nf7::Buffer> buf_;
std::mutex mtx;
bool working = false;
nf7::Queue<std::function<void()>> q;
};
std::shared_ptr<Data> data_;
std::mutex mtx_;
bool working_ = false;
nf7::Queue<std::function<void()>> q_;
template <typename R>
std::future<R> ExecWithPromise(std::function<R()>&& f) noexcept {
auto pro = std::make_shared<std::promise<R>>();
auto task = [pro, f = std::move(f)]() {
try {
pro->set_value(f());
} catch (...) {
pro->set_exception(std::current_exception());
}
};
Exec(std::move(task));
return pro->get_future();
}
void Exec(std::function<void()>&& f) noexcept {
data_->q.Push(std::move(f));
q_.Push(std::move(f));
std::unique_lock<std::mutex> k(data_->mtx);
if (!std::exchange(data_->working, true)) {
data_->ctx->env().ExecAsync(
data_->ctx, [data = data_]() { Handle(data); });
std::unique_lock<std::mutex> k(mtx_);
if (!std::exchange(working_, true)) {
ctx_->env().ExecAsync(
ctx_, [self = shared_from_this()]() { self->Handle(); });
}
}
static void Handle(const std::shared_ptr<Data>& data) noexcept {
std::unique_lock<std::mutex> k(data->mtx);
if (auto task = data->q.Pop()) {
void Handle() noexcept {
std::unique_lock<std::mutex> k(mtx_);
if (auto task = q_.Pop()) {
k.unlock();
(*task)();
data->ctx->env().ExecAsync(data->ctx, [data]() { Handle(data); });
ctx_->env().ExecAsync(
ctx_, [self = shared_from_this()]() { self->Handle(); });
} else {
data->working = false;
working_ = false;
}
}
};

View File

@ -1,60 +0,0 @@
#pragma once
#include <chrono>
#include <memory>
#include <functional>
#include <future>
#include "common/queue.hh"
namespace nf7 {
class ConditionalQueue final :
public nf7::Queue<std::function<bool(void)>> {
public:
ConditionalQueue() = default;
template <typename T>
void Push(std::future<T>&& fu, auto&& f) {
auto fu_ptr = std::make_shared<std::future<T>>(std::move(fu));
auto task = [fu_ptr = std::move(fu_ptr), f = std::move(f)]() mutable {
if (fu_ptr->wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
return false;
}
f(*fu_ptr);
return true;
};
Queue<std::function<bool(void)>>::Push(std::move(task));
}
template <typename T>
void Push(std::shared_future<T> fu, auto&& f) {
auto task = [fu, f = std::move(f)]() mutable {
if (fu.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
return false;
}
f(fu);
return true;
};
Queue<std::function<bool(void)>>::Push(std::move(task));
}
void Push(const std::shared_ptr<nf7::Lock>& k, auto&& f) {
auto task = [k, f = std::move(f)]() {
if (!k->acquired() && !k->cancelled()) {
return false;
}
f(k);
return true;
};
Queue<std::function<bool(void)>>::Push(std::move(task));
}
bool PopAndExec() noexcept {
if (auto task = Pop()) {
if ((*task)()) return true;
Interrupt(std::move(*task));
}
return false;
}
};
} // namespace nf7

266
common/future.hh Normal file
View File

@ -0,0 +1,266 @@
#pragma once
#include <cassert>
#include <coroutine>
#include <exception>
#include <memory>
#include <mutex>
#include <optional>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "nf7.hh"
#include "common/generic_context.hh"
namespace nf7 {
template <typename T>
class Future final {
public:
static constexpr bool kVoid = std::is_same<T, void>::value;
class Promise;
class Coro;
class Awaiter;
using Handle = std::coroutine_handle<Promise>;
using Return = typename std::conditional<kVoid, int, T>::type;
enum State { kYet, kDone, kError, };
struct Data final {
public:
std::atomic<bool> aborted = false;
std::atomic<size_t> pros = 0;
std::atomic<State> state = kYet;
std::mutex mtx;
std::optional<Return> value;
std::exception_ptr exception;
std::vector<std::function<void()>> recv;
};
class Promise final {
public:
template <typename U> friend class nf7::Future;
template <typename U> friend class nf7::Future<U>::Coro;
static constexpr bool kThisIsNf7FuturePromise = true;
Promise() noexcept : data_(std::make_shared<Data>()) {
++data_->pros;
}
Promise(const Promise& src) noexcept : data_(src.data_) {
++data_->pros;
}
Promise(Promise&&) = default;
Promise& operator=(const Promise& src) noexcept {
data_ = src.data_;
++data_->pros;
}
Promise& operator=(Promise&&) = default;
~Promise() noexcept {
if (data_ && --data_->pros == 0 && data_->state == kYet) {
Throw(std::make_exception_ptr<nf7::Exception>({"promise forgotten"}));
}
}
// thread-safe
auto Wrap(const std::function<T()>& f) noexcept requires(!kVoid)
try {
Return(f());
} catch (Exception&) {
Throw(std::current_exception());
}
// thread-safe
auto Return(T&& v) requires(!kVoid) {
std::unique_lock<std::mutex> k(data_->mtx);
if (data_->state == kYet) {
data_->state = kDone;
data_->value = std::move(v);
for (auto recv : data_->recv) recv();
}
}
// thread-safe
auto Return(int = 0) requires(kVoid) {
std::unique_lock<std::mutex> k(data_->mtx);
if (data_->state == kYet) {
data_->state = kDone;
for (auto recv : data_->recv) recv();
}
}
// thread-safe
void Throw(std::exception_ptr e) noexcept {
std::unique_lock<std::mutex> k(data_->mtx);
if (data_->state == kYet) {
data_->exception = e;
data_->state = kError;
for (auto recv : data_->recv) recv();
}
}
Future future() const noexcept {
assert(data_);
return Future(data_);
}
auto get_return_object() noexcept {
return Coro(Handle::from_promise(*this), data_);
}
auto initial_suspend() const noexcept {
return std::suspend_always();
}
auto final_suspend() const noexcept {
return std::suspend_always();
}
auto yield_value(const T& v) requires(!kVoid) {
Return(T(v));
return std::suspend_never();
}
auto yield_value(T&& v) requires(!kVoid) {
Return(std::move(v));
return std::suspend_never();
}
auto return_void() {
if constexpr (kVoid) Return();
return std::suspend_never();
}
auto unhandled_exception() noexcept {
Throw(std::current_exception());
}
private:
std::shared_ptr<Data> data_;
};
class Coro final {
public:
friend Promise;
using promise_type = Promise;
Coro() = delete;
~Coro() noexcept {
if (data_) h_.destroy();
}
Coro(const Coro&) = delete;
Coro(Coro&&) = default;
Coro& operator=(const Coro&) = delete;
Coro& operator=(Coro&&) = default;
Future Start(File& f, std::string_view desc) noexcept {
auto ctx = std::make_shared<nf7::GenericContext>(f.env(), f.id());
ctx->description() = desc;
return Start(ctx);
}
Future Start(const std::shared_ptr<nf7::Context>& ctx) noexcept {
ctx->env().ExecSub(ctx, [ctx, h = h_]() { h.resume(); });
return Future(data_);
}
void Abort(const std::shared_ptr<nf7::Context>& ctx) noexcept {
h_.promise().Throw(
std::make_exception_ptr<nf7::Exception>({"coroutine aborted"}));
data_->aborted = true;
ctx->env().ExecSub(ctx, [h = h_]() { h.destroy(); });
}
private:
Handle h_;
std::shared_ptr<Data> data_;
Coro(Handle h, const std::shared_ptr<Data>& data) noexcept : h_(h), data_(data) { }
};
class Awaiter final {
public:
Awaiter() = delete;
Awaiter(Future& fu, const std::shared_ptr<nf7::Context>& ctx) noexcept :
fu_(&fu), ctx_(ctx) {
}
Awaiter(const Awaiter&) = delete;
Awaiter(Awaiter&&) = delete;
Awaiter& operator=(const Awaiter&) = delete;
Awaiter& operator=(Awaiter&&) = delete;
bool await_ready() const noexcept { return !fu_->yet(); }
template <typename U>
void await_suspend(std::coroutine_handle<U> caller) const noexcept {
static_assert(U::kThisIsNf7FuturePromise, "illegal coroutine");
assert(fu_->data_);
std::unique_lock<std::mutex> k(fu_->data_->mtx);
if (fu_->yet()) {
fu_->data_->recv.push_back([caller, ctx = ctx_]() {
ctx->env().ExecSub(ctx, [caller]() {
if (!caller.promise().data_->aborted) caller.resume();
});
});
} else {
// promise has ended after await_ready() is called
caller.resume();
}
}
auto await_resume() const {
if constexpr (!kVoid) {
if (std::holds_alternative<Return>(fu_->imm_)) {
return std::move(std::get<Return>(fu_->imm_));
}
}
if (std::holds_alternative<std::exception_ptr>(fu_->imm_)) {
std::rethrow_exception(std::get<std::exception_ptr>(fu_->imm_));
}
assert(fu_->data_);
switch (fu_->data_->state) {
case kDone:
if constexpr (kVoid) {
return;
} else {
return std::move(*fu_->data_->value);
}
case kError:
std::rethrow_exception(fu_->data_->exception);
default:
assert(false);
throw 0;
}
}
private:
Future* const fu_;
std::shared_ptr<nf7::Context> ctx_;
};
Future() = delete;
template <typename U=T> requires (!kVoid)
Future(T&& v) noexcept : imm_(std::move(v)) {
}
Future(std::exception_ptr e) noexcept : imm_(e) {
}
Future(const Future&) = default;
Future(Future&&) = default;
Future& operator=(const Future&) = default;
Future& operator=(Future&&) = default;
bool yet() const noexcept {
return std::holds_alternative<std::monostate>(imm_) && data_->state == kYet;
}
bool done() const noexcept {
return std::holds_alternative<Return>(imm_) || data_->state == kDone;
}
bool error() const noexcept {
return std::holds_alternative<std::exception_ptr>(imm_) || data_->state == kError;
}
Awaiter awaiter(const std::shared_ptr<nf7::Context>& ctx) noexcept {
return Awaiter(*this, ctx);
}
private:
std::variant<std::monostate, Return, std::exception_ptr> imm_;
std::shared_ptr<Data> data_;
Future(const std::shared_ptr<Data>& data) noexcept : data_(data) { }
};
} // namespace nf7

View File

@ -1,19 +1,27 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <coroutine>
#include <deque>
#include <exception>
#include <memory>
#include <utility>
#include <vector>
#include "nf7.hh"
#include "common/future.hh"
namespace nf7 {
class Lock {
class Lock final {
public:
class Resource;
class Exception : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
Lock() = default;
Lock(Resource& res, bool ex) noexcept : res_(&res), ex_(ex) {
@ -24,13 +32,13 @@ class Lock {
Lock& operator=(const Lock&) = delete;
Lock& operator=(Lock&&) = delete;
bool cancelled() const noexcept { return !res_; }
bool acquired() const noexcept { return acquired_; }
void Validate() const {
if (!res_) throw Lock::Exception("target expired");
}
private:
Resource* res_ = nullptr;
bool ex_ = false;
bool acquired_ = false;
};
class Lock::Resource {
@ -42,8 +50,8 @@ class Lock::Resource {
if (auto lock = lock_.lock()) {
lock->res_ = nullptr;
}
for (auto lock : plocks_) {
lock->res_ = nullptr;
for (auto pend : pends_) {
pend.pro.Throw(std::make_exception_ptr<Lock::Exception>({"lock cancelled"}));
}
}
Resource(const Resource&) = delete;
@ -51,23 +59,22 @@ class Lock::Resource {
Resource& operator=(const Resource&) = delete;
Resource& operator=(Resource&&) = delete;
std::shared_ptr<Lock> AcquireLock(bool ex) noexcept {
nf7::Future<std::shared_ptr<Lock>> AcquireLock(bool ex) noexcept {
if (auto ret = TryAcquireLock(ex)) return ret;
if (!ex && !plocks_.empty() && !plocks_.back()->ex_) {
return plocks_.back();
if (ex || pends_.empty() || pends_.back().ex) {
pends_.push_back(ex);
}
plocks_.push_back(std::make_shared<Lock>(*this, ex));
return plocks_.back();
return pends_.back().pro.future();
}
std::shared_ptr<Lock> TryAcquireLock(bool ex) noexcept {
if (!lock_.expired()) return nullptr;
auto ret = std::make_shared<Lock>(*this, ex);
ret->acquired_ = true;
lock_ = ret;
if (auto k = lock_.lock()) {
return !ex && !k->ex_ && pends_.empty()? k: nullptr;
}
auto k = std::make_shared<Lock>(*this, ex);
lock_ = k;
OnLock();
return ret;
return k;
}
protected:
@ -75,22 +82,31 @@ class Lock::Resource {
virtual void OnUnlock() noexcept { }
private:
struct Pending final {
public:
Pending(bool ex_) noexcept : ex(ex_) { }
bool ex;
nf7::Future<std::shared_ptr<Lock>>::Promise pro;
};
std::weak_ptr<Lock> lock_;
std::deque<std::shared_ptr<Lock>> plocks_;
std::deque<Pending> pends_;
};
Lock::~Lock() noexcept {
if (!res_) return;
if (res_->plocks_.empty()) {
if (res_->pends_.empty()) {
res_->OnUnlock();
return;
}
auto next = std::move(res_->plocks_.front());
res_->plocks_.pop_front();
res_->lock_ = next;
next->acquired_ = true;
auto next = std::move(res_->pends_.front());
res_->pends_.pop_front();
auto lock = std::make_shared<Lock>(*res_, next.ex);
res_->lock_ = lock;
next.pro.Return(std::move(lock));
}
} // namespace nf7

View File

@ -5,6 +5,7 @@
#include "nf7.hh"
#include "common/future.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_ref.hh"
@ -20,7 +21,7 @@ class Obj : public nf7::File::Interface {
Obj& operator=(Obj&&) = delete;
// result is registered to LUA_REGISTRY
virtual std::shared_future<std::shared_ptr<Ref>> Build() noexcept = 0;
virtual nf7::Future<std::shared_ptr<Ref>> Build() noexcept = 0;
};
} // namespace nf7::luajit

View File

@ -27,6 +27,7 @@ class Ref final {
Ref& operator=(Ref&&) = delete;
int index() const noexcept { return idx_; }
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return q_; }
private:
std::shared_ptr<nf7::Context> ctx_;

View File

@ -1,6 +1,5 @@
#include <atomic>
#include <exception>
#include <future>
#include <memory>
#include <string_view>
#include <utility>
@ -13,9 +12,9 @@
#include "nf7.hh"
#include "common/async_buffer.hh"
#include "common/conditional_queue.hh"
#include "common/dir_item.hh"
#include "common/file_ref.hh"
#include "common/future.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/lock.hh"
@ -65,15 +64,14 @@ class Obj final : public nf7::File,
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
std::shared_future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept override;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::DirItem, nf7::luajit::Queue>(t).Select(this);
}
private:
std::shared_ptr<nf7::LoggerRef> log_;
std::shared_ptr<nf7::luajit::Queue> ljq_;
std::shared_ptr<nf7::LoggerRef> log_;
std::unique_ptr<SrcWatcher> srcwatcher_;
std::shared_ptr<nf7::luajit::Ref> cache_;
@ -121,193 +119,119 @@ class Obj::ExecTask final : public nf7::Context, public std::enable_shared_from_
ExecTask(Obj& target) :
Context(target.env(), target.id()),
target_(&target), log_(target_->log_), ljq_(target_->ljq_),
fu_(pro_.get_future().share()),
chunk_name_(target_->abspath().Stringify()),
src_(&(*target.src_).interfaceOrThrow<nf7::AsyncBuffer>()),
src_lock_(src_->AcquireLock(false)){
target_(&target), log_(target_->log_),
coro_(Proc()) {
}
void Start() noexcept {
Proc();
}
void Update() noexcept {
while (cq_.PopAndExec());
fu_ = coro_.Start(shared_from_this());
}
void Abort() noexcept override {
abort_ = true;
coro_.Abort(shared_from_this());
}
size_t GetMemoryUsage() const noexcept override {
return buf_size_;
}
std::shared_future<std::shared_ptr<nf7::luajit::Ref>>& fu() noexcept { return fu_; }
auto fu() noexcept { return *fu_; }
private:
Obj* target_;
bool abort_ = false;
std::shared_ptr<nf7::LoggerRef> log_;
std::shared_ptr<nf7::LoggerRef> log_;
std::shared_ptr<nf7::luajit::Queue> ljq_;
nf7::ConditionalQueue cq_;
std::promise<std::shared_ptr<nf7::luajit::Ref>> pro_;
std::shared_future<std::shared_ptr<nf7::luajit::Ref>> fu_;
std::string chunk_name_;
nf7::AsyncBuffer* src_;
std::shared_ptr<nf7::Lock> src_lock_;
enum Step { kInitial, kSrcLock, kSrcSize, kSrcRead, kExec, kFinish };
Step step_ = kInitial;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Coro coro_;
std::optional<nf7::Future<std::shared_ptr<nf7::luajit::Ref>>> fu_;
std::string chunkname_;
std::atomic<size_t> buf_size_ = 0;
std::vector<uint8_t> buf_;
bool buf_consumed_ = false;
int reg_idx_;
void Error(std::string_view msg) noexcept {
pro_.set_exception(std::make_exception_ptr<Exception>({msg}));
log_->Error(msg);
}
void Proc(std::future<size_t>& fu) noexcept
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Coro Proc() noexcept
try {
return Proc(fu.get());
auto self = shared_from_this();
auto& srcf = *target_->src_;
chunkname_ = srcf.abspath().Stringify();
auto src = srcf.interfaceOrThrow<nf7::AsyncBuffer>().self();
auto srclock = co_await src->AcquireLock(false).awaiter(self);
log_->Trace("source file lock acquired");
buf_size_ = co_await src->size().awaiter(self);
if (buf_size_ == 0) {
throw nf7::Exception("source is empty");
}
if (buf_size_ > kMaxSize) {
throw nf7::Exception("source is too huge");
}
buf_.resize(buf_size_);
const size_t read = co_await src->Read(0, buf_.data(), buf_size_).awaiter(self);
if (read != buf_size_) {
throw nf7::Exception("failed to read all bytes from source");
}
nf7::Future<int>::Promise lua_pro;
auto ljq = target_->
ResolveUpwardOrThrow("_luajit").
interfaceOrThrow<nf7::luajit::Queue>().self();
ljq->Push(self, [&](auto L) { lua_pro.Wrap([&]() { return ExecLua(L); }); });
const int idx = co_await lua_pro.future().awaiter(self);
log_->Trace("task finished");
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator());
ctx->description() = "luajit object cache";
target_->cache_ = std::make_shared<nf7::luajit::Ref>(ctx, ljq, idx);
co_yield target_->cache_;
} catch (Exception& e) {
log_->Error(e.msg());
pro_.set_exception(std::current_exception());
return;
throw;
}
void Proc(size_t param = 0, lua_State* L = nullptr) noexcept {
if (abort_) {
Error("task aborted");
return;
int ExecLua(lua_State* L) {
static const auto kReader = [](lua_State*, void* selfptr, size_t* size) -> const char* {
auto self = reinterpret_cast<ExecTask*>(selfptr);
if (std::exchange(self->buf_consumed_, true)) {
*size = 0;
return nullptr;
} else {
*size = self->buf_.size();
return reinterpret_cast<const char*>(self->buf_.data());
}
};
if (0 != lua_load(L, kReader, this, chunkname_.c_str())) {
throw nf7::Exception(lua_tostring(L, -1));
}
switch (step_) {
case kInitial:
step_ = kSrcLock;
cq_.Push(src_lock_, [self = shared_from_this()](auto) { self->Proc(); });
return;
case kSrcLock:
if (!src_lock_->acquired()) {
Error("failed to lock source file");
return;
}
log_->Trace("source file lock acquired");
step_ = kSrcSize;
cq_.Push(src_->size(), [self = shared_from_this()](auto& v) { self->Proc(v); });
return;
case kSrcSize:
if (src_lock_->cancelled()) { // ensure src_ is alive
Error("source is expired");
return;
}
if (param == 0) {
Error("source is empty");
return;
}
if (param > kMaxSize) {
Error("source is too huge");
return;
}
log_->Trace("source file size is "+std::to_string(param)+" bytes");
buf_size_ = param;
buf_.resize(param);
step_ = kSrcRead;
cq_.Push(src_->Read(0, buf_.data(), param),
[self = shared_from_this()](auto& v) { self->Proc(v); });
return;
case kSrcRead:
if (buf_.size() != param) {
Error("cannot read whole bytes");
return;
}
log_->Trace("read "+std::to_string(buf_size_)+" bytes from source file");
step_ = kExec;
ljq_->Push(shared_from_this(), [self = shared_from_this()](auto L) { self->Proc(0, L); });
return;
case kExec: // runs on LuaJIT thread
static const auto kReader = [](lua_State*, void* selfptr, size_t* size) -> const char* {
auto self = reinterpret_cast<ExecTask*>(selfptr);
if (std::exchange(self->buf_consumed_, true)) {
*size = 0;
return nullptr;
} else {
*size = self->buf_.size();
return reinterpret_cast<const char*>(self->buf_.data());
}
};
if (0 != lua_load(L, kReader, this, chunk_name_.c_str())) {
Error(lua_tostring(L, -1));
return;
}
if (0 != nf7::luajit::SandboxCall(L, 0, 1)) {
Error(lua_tostring(L, -1));
return;
}
log_->Trace("executed lua script and got "s+lua_typename(L, lua_type(L, -1)));
reg_idx_ = luaL_ref(L, LUA_REGISTRYINDEX);
if (reg_idx_ == LUA_REFNIL) {
Error("got nil object");
return;
}
step_ = kFinish;
env().ExecSub(shared_from_this(),
[self = shared_from_this()]() { self->Proc(); });
return;
case kFinish:
log_->Trace("task finished"s);
{
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator());
ctx->description() = "luajit object cache";
target_->cache_ = std::make_shared<nf7::luajit::Ref>(ctx, ljq_, reg_idx_);
}
pro_.set_value(target_->cache_);
return;
default:
assert(false);
if (0 != nf7::luajit::SandboxCall(L, 0, 1)) {
throw nf7::Exception(lua_tostring(L, -1));
}
log_->Trace("executed lua script and got "s+lua_typename(L, lua_type(L, -1)));
const auto ret = luaL_ref(L, LUA_REGISTRYINDEX);
if (ret == LUA_REFNIL) {
throw nf7::Exception("got nil object");
}
return ret;
}
};
std::shared_future<std::shared_ptr<nf7::luajit::Ref>> Obj::Build() noexcept
try {
if (!ljq_) throw Exception("luajit context not found");
auto exec = exec_.lock();
if (!exec) {
if (cache_) {
std::promise<std::shared_ptr<nf7::luajit::Ref>> pro;
pro.set_value(cache_);
return pro.get_future().share();
}
exec_ = exec = std::make_shared<ExecTask>(*this);
exec->Start();
}
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Obj::Build() noexcept {
if (auto exec = exec_.lock()) return exec->fu();
if (cache_) return std::shared_ptr<nf7::luajit::Ref>{cache_};
auto exec = std::make_shared<ExecTask>(*this);
exec->Start();
exec_ = exec;
return exec->fu();
} catch (Exception& e) {
log_->Error(e.msg());
std::promise<std::shared_ptr<nf7::luajit::Ref>> pro;
pro.set_exception(std::current_exception());
return pro.get_future().share();
}
void Obj::Handle(const Event& ev) noexcept {
switch (ev.type) {
case Event::kAdd:
try {
log_->SetUp(*this);
ljq_ = ResolveUpwardOrThrow("_luajit").
interfaceOrThrow<nf7::luajit::Queue>().self();
auto ctx = std::make_shared<nf7::GenericContext>(env(), id());
ctx->description() = "resetting state";
env().ExecMain(ctx, [this]() { Reset(); });
@ -318,7 +242,6 @@ void Obj::Handle(const Event& ev) noexcept {
if (auto exec = exec_.lock()) exec->Abort();
exec_ = {};
cache_ = nullptr;
ljq_ = nullptr;
srcwatcher_ = nullptr;
log_->TearDown();
break;
@ -339,8 +262,6 @@ void Obj::Reset() noexcept {
}
void Obj::Update() noexcept {
if (auto exec = exec_.lock()) exec->Update();
if (const auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}

View File

@ -63,7 +63,7 @@ class NativeFile final : public File,
Reset();
return;
case Event::kRemove:
buf_ = std::nullopt;
buf_ = nullptr;
return;
default:
return;
@ -71,11 +71,11 @@ class NativeFile final : public File,
}
File::Interface* interface(const std::type_info& t) noexcept override {
return InterfaceSelector<nf7::AsyncBuffer, nf7::DirItem>(t).Select(this, &*buf_);
return InterfaceSelector<nf7::AsyncBuffer, nf7::DirItem>(t).Select(this, buf_.get());
}
private:
std::optional<nf7::AsyncBufferAdaptor> buf_;
std::shared_ptr<nf7::AsyncBufferAdaptor> buf_;
const char* popup_ = nullptr;
@ -96,7 +96,7 @@ class NativeFile final : public File,
}
auto buf = std::make_shared<
nf7::NativeFile>(*this, env().npath()/npath_, flags, exlock);
buf_.emplace(buf, buf);
buf_ = std::make_shared<nf7::AsyncBufferAdaptor>(buf, buf);
}
void Touch() noexcept {
env().Handle({.id = id(), .type = Event::kUpdate,});