nf7/file/luajit_obj.cc

313 lines
8.6 KiB
C++

#include <atomic>
#include <exception>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/serialize.hpp>
#include "nf7.hh"
#include "common/async_buffer.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/future.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/generic_memento.hh"
#include "common/generic_watcher.hh"
#include "common/gui_file.hh"
#include "common/life.hh"
#include "common/lock.hh"
#include "common/luajit.hh"
#include "common/luajit_obj.hh"
#include "common/luajit_queue.hh"
#include "common/luajit_thread.hh"
#include "common/logger_ref.hh"
#include "common/ptr_selector.hh"
#include "common/task.hh"
#include "common/yas_nf7.hh"
using namespace std::literals;
namespace nf7 {
namespace {
class Obj final : public nf7::FileBase, public nf7::DirItem, public nf7::luajit::Obj {
public:
static inline const nf7::GenericTypeInfo<Obj> kType = {
"LuaJIT/Obj", {"nf7::DirItem", "nf7::luajit::Obj"}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted(
"Compiles and runs LuaJIT script, and caches the object returned from the script.");
ImGui::Bullet(); ImGui::TextUnformatted(
"implements nf7::luajit::Obj implementation");
ImGui::Bullet(); ImGui::TextUnformatted(
"requires nf7::luajit::Queue implementation with name '_luajit' on upper dir");
ImGui::Bullet(); ImGui::TextUnformatted(
"requires nf7::AsyncBuffer implementation to load LuaJIT script");
}
static constexpr size_t kMaxSize = 1024*1024*16; /* = 16 MiB */
class ExecTask;
struct Data final {
nf7::FileHolder::Tag src;
};
Obj(Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env, {&src_, &src_editor_}),
nf7::DirItem(nf7::DirItem::kTooltip |
nf7::DirItem::kMenu |
nf7::DirItem::kWidget),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>()),
src_(*this, "src"),
src_editor_(src_, [](auto& t) { return t.flags().contains("nf7::AsyncBuffer"); }),
mem_(std::move(data)) {
mem_.data().src.SetTarget(src_);
mem_.CommitAmend();
src_.onChildMementoChange = [this]() { mem_.Commit(); };
src_.onEmplace = [this]() { mem_.Commit(); };
mem_.onRestore = [this]() { DropCache(); Touch(); };
mem_.onCommit = [this]() { DropCache(); Touch(); };
}
Obj(Env& env, Deserializer& ar) noexcept : Obj(env) {
ar(src_);
}
void Serialize(Serializer& ar) const noexcept override {
ar(src_);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<Obj>(env, Data {mem_.data()});
}
void Handle(const Event&) noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateWidget() 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::Obj, nf7::Memento>(t).Select(this, &mem_);
}
private:
nf7::Life<Obj> life_;
std::shared_ptr<nf7::LoggerRef> log_;
std::optional<nf7::GenericWatcher> watcher_;
std::shared_ptr<nf7::luajit::Ref> cache_;
nf7::Task<std::shared_ptr<nf7::luajit::Ref>>::Holder exec_;
nf7::FileHolder src_;
nf7::gui::FileHolderEditor src_editor_;
nf7::GenericMemento<Data> mem_;
void DropCache() noexcept;
};
class Obj::ExecTask final : public nf7::Task<std::shared_ptr<nf7::luajit::Ref>> {
public:
ExecTask(Obj& target) noexcept :
Task(target.env(), target.id()), target_(&target), log_(target.log_) {
}
size_t GetMemoryUsage() const noexcept override {
return buf_size_;
}
private:
Obj* const target_;
std::shared_ptr<nf7::LoggerRef> log_;
std::string chunkname_;
std::atomic<size_t> buf_size_ = 0;
std::vector<uint8_t> buf_;
bool buf_consumed_ = false;
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Coro Proc() noexcept override {
try {
auto& srcf = target_->src_.GetFileOrThrow();
chunkname_ = srcf.abspath().Stringify();
const auto srcf_id = srcf.id();
// acquire lock of source
auto src = srcf.interfaceOrThrow<nf7::AsyncBuffer>().self();
auto srclock = co_await src->AcquireLock(false);
// get size of source
buf_size_ = co_await src->size();
if (buf_size_ == 0) {
throw nf7::Exception("source is empty");
}
if (buf_size_ > kMaxSize) {
throw nf7::Exception("source is too huge");
}
// read source
buf_.resize(buf_size_);
const size_t read = co_await src->Read(0, buf_.data(), buf_size_);
if (read != buf_size_) {
throw nf7::Exception("failed to read all bytes from source");
}
// find luajit queue
auto ljq = target_->
ResolveUpwardOrThrow("_luajit").
interfaceOrThrow<nf7::luajit::Queue>().self();
// create promise handler for new luajit thread
nf7::Future<int>::Promise lua_pro(self());
auto handler = nf7::luajit::Thread::CreatePromiseHandler<int>(
lua_pro, [&](auto L) {
if (lua_gettop(L) != 1) {
throw nf7::Exception("expected one object to be returned");
}
return luaL_ref(L, LUA_REGISTRYINDEX);
});
// start watcher on target_->watcher_
try {
auto& w = target_->watcher_;
w.emplace(env());
w->Watch(srcf_id);
std::weak_ptr<Task> wself = self();
w->AddHandler(Event::kUpdate, [t = target_, wself](auto&) {
if (auto self = wself.lock()) {
t->log_->Info("detected update of source file, aborts building");
t->exec_ = {};
} else if (t->cache_) {
t->log_->Info("detected update of source file, drops cache automatically");
t->cache_ = nullptr;
t->Touch();
}
});
} catch (Exception& e) {
log_->Warn("watcher setup error: "+e.msg());
}
// queue task to trigger the thread
auto th = std::make_shared<nf7::luajit::Thread>(self(), ljq, std::move(handler));
th->Install(log_);
ljq->Push(self(), [&](auto L) {
try {
auto thL = th->Init(L);
Compile(thL);
th->Resume(thL, 0);
} catch (Exception&) {
lua_pro.Throw(std::current_exception());
}
});
// wait for end of execution and return built object's index
const int idx = co_await lua_pro.future();
// context for object cache
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator(), "luajit object cache");
// return the object and cache it
target_->cache_ = std::make_shared<nf7::luajit::Ref>(ctx, ljq, idx);
co_yield target_->cache_;
} catch (Exception& e) {
throw;
}
}
void Compile(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));
}
}
};
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();
}
void Obj::Handle(const Event& ev) noexcept {
nf7::FileBase::Handle(ev);
switch (ev.type) {
case Event::kAdd:
log_->SetUp(*this);
break;
case Event::kRemove:
DropCache();
log_->TearDown();
break;
default:
break;
}
}
void Obj::DropCache() noexcept {
exec_ = {};
cache_ = nullptr;
watcher_ = std::nullopt;
}
void Obj::UpdateMenu() noexcept {
if (ImGui::MenuItem("build")) {
Build();
}
if (ImGui::MenuItem("drop cache", nullptr, nullptr, !!cache_)) {
DropCache();
}
}
void Obj::UpdateTooltip() noexcept {
ImGui::Text("cache: %s", cache_? "ready": "nothing");
ImGui::Text("src :");
ImGui::Indent();
src_editor_.Tooltip();
ImGui::Unindent();
}
void Obj::UpdateWidget() noexcept {
ImGui::TextUnformatted("LuaJIT/Obj: config");
src_editor_.ButtonWithLabel("src");
ImGui::Spacing();
src_editor_.ItemWidget("src");
}
}
} // namespace nf7