340 lines
9.1 KiB
C++
340 lines
9.1 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_ref.hh"
|
|
#include "common/future.hh"
|
|
#include "common/generic_context.hh"
|
|
#include "common/generic_type_info.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::File,
|
|
public nf7::DirItem,
|
|
public nf7::luajit::Obj {
|
|
public:
|
|
static inline const GenericTypeInfo<Obj> kType = {"LuaJIT/Obj", {"DirItem",}};
|
|
|
|
static constexpr size_t kMaxSize = 1024*1024*16; /* = 16 MiB */
|
|
|
|
class SrcWatcher;
|
|
class ExecTask;
|
|
|
|
Obj(Env& env, Path&& path = {}) noexcept :
|
|
File(kType, env), DirItem(DirItem::kTooltip | DirItem::kMenu),
|
|
log_(std::make_shared<nf7::LoggerRef>()),
|
|
src_(*this, std::move(path)) {
|
|
}
|
|
|
|
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, Path(src_.path()));
|
|
}
|
|
|
|
void Handle(const Event&) noexcept override;
|
|
void Update() noexcept override;
|
|
void UpdateMenu() noexcept override;
|
|
void UpdateTooltip() 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>(t).Select(this);
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<nf7::LoggerRef> log_;
|
|
|
|
std::unique_ptr<SrcWatcher> srcwatcher_;
|
|
std::shared_ptr<nf7::luajit::Ref> cache_;
|
|
|
|
nf7::Task<std::shared_ptr<nf7::luajit::Ref>>::Holder exec_;
|
|
|
|
const char* popup_ = nullptr;
|
|
|
|
// persistent params
|
|
nf7::FileRef src_;
|
|
|
|
|
|
void Touch() noexcept {
|
|
if (!id()) return;
|
|
env().Handle({.id = id(), .type = Event::kUpdate});
|
|
}
|
|
|
|
void Reset() noexcept;
|
|
};
|
|
|
|
class Obj::SrcWatcher final : public nf7::Env::Watcher {
|
|
public:
|
|
SrcWatcher(Obj& owner, File::Id id) :
|
|
Watcher(owner.env()), owner_(&owner) {
|
|
if (owner.id() == id) throw Exception("self watch");
|
|
if (id == 0) throw Exception("invalid id");
|
|
Watch(id);
|
|
}
|
|
|
|
void Handle(const File::Event& ev) noexcept override {
|
|
if (ev.type == File::Event::kUpdate) {
|
|
owner_->log_->Info("detected update of source file, drops cache automatically");
|
|
owner_->cache_ = nullptr;
|
|
owner_->Touch();
|
|
}
|
|
}
|
|
|
|
private:
|
|
Obj* const owner_;
|
|
};
|
|
|
|
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* 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_;
|
|
chunkname_ = srcf.abspath().Stringify();
|
|
|
|
// acquire lock of source
|
|
auto src = srcf.interfaceOrThrow<nf7::AsyncBuffer>().self();
|
|
auto srclock = co_await src->AcquireLock(false).awaiter(self());
|
|
log_->Trace("source file lock acquired");
|
|
|
|
// get size of source
|
|
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");
|
|
}
|
|
|
|
// read source
|
|
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");
|
|
}
|
|
|
|
// create thread to compile lua script
|
|
nf7::Future<int>::Promise lua_pro(self());
|
|
auto th = nf7::luajit::Thread::CreateForPromise<int>(lua_pro, [&](auto L) {
|
|
if (lua_gettop(L) != 1) {
|
|
throw nf7::Exception("expected one object to be returned");
|
|
}
|
|
if (auto str = lua_tostring(L, -1)) {
|
|
log_->Info("got '"s+str+"'");
|
|
} else {
|
|
log_->Info("got ["s+lua_typename(L, lua_type(L, -1))+"]");
|
|
}
|
|
return luaL_ref(L, LUA_REGISTRYINDEX);
|
|
});
|
|
|
|
// context for luajit script running
|
|
auto lua_ctx = std::make_shared<nf7::GenericContext>(env(), initiator());
|
|
lua_ctx->description() = "luajit object build script runner";
|
|
|
|
// queue task to trigger the thread
|
|
auto ljq = target_->
|
|
ResolveUpwardOrThrow("_luajit").
|
|
interfaceOrThrow<nf7::luajit::Queue>().self();
|
|
ljq->Push(self(), [&](auto L) {
|
|
try {
|
|
auto thL = th.Init(self(), ljq, 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().awaiter(self());
|
|
log_->Trace("task finished");
|
|
|
|
// context for object cache
|
|
// TODO use specific Context type
|
|
auto ctx = std::make_shared<nf7::GenericContext>(env(), initiator());
|
|
ctx->description() = "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) {
|
|
log_->Error(e.msg());
|
|
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 {
|
|
switch (ev.type) {
|
|
case Event::kAdd:
|
|
try {
|
|
log_->SetUp(*this);
|
|
auto ctx = std::make_shared<nf7::GenericContext>(env(), id());
|
|
ctx->description() = "resetting state";
|
|
env().ExecMain(ctx, [this]() { Reset(); });
|
|
} catch (Exception&) {
|
|
}
|
|
break;
|
|
case Event::kRemove:
|
|
exec_ = {};
|
|
cache_ = nullptr;
|
|
srcwatcher_ = nullptr;
|
|
log_->TearDown();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
void Obj::Reset() noexcept {
|
|
exec_ = {};
|
|
cache_ = nullptr;
|
|
try {
|
|
srcwatcher_ = std::make_unique<SrcWatcher>(*this, src_.id());
|
|
} catch (Exception&) {
|
|
srcwatcher_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void Obj::Update() noexcept {
|
|
if (const auto popup = std::exchange(popup_, nullptr)) {
|
|
ImGui::OpenPopup(popup);
|
|
}
|
|
if (ImGui::BeginPopup("ConfigPopup")) {
|
|
static std::string path_str;
|
|
ImGui::TextUnformatted("LuaJIT/Obj: config");
|
|
if (ImGui::IsWindowAppearing()) {
|
|
path_str = src_.path().Stringify();
|
|
}
|
|
|
|
const bool submit = ImGui::InputText(
|
|
"path", &path_str, ImGuiInputTextFlags_EnterReturnsTrue);
|
|
|
|
Path path;
|
|
bool err = false;
|
|
try {
|
|
path = Path::Parse(path_str);
|
|
} catch (Exception& e) {
|
|
ImGui::Bullet(); ImGui::Text("invalid path: %s", e.msg().c_str());
|
|
err = true;
|
|
}
|
|
try {
|
|
ResolveOrThrow(path);
|
|
} catch (File::NotFoundException&) {
|
|
ImGui::Bullet(); ImGui::Text("(target seems to be missing)");
|
|
}
|
|
|
|
if (!err) {
|
|
if (ImGui::Button("ok") || submit) {
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
if (path != src_.path()) {
|
|
auto ctx = std::make_shared<nf7::GenericContext>(env(), id());
|
|
auto task = [this, p = std::move(path)]() mutable {
|
|
src_ = std::move(p);
|
|
Reset();
|
|
};
|
|
ctx->description() = "changing source path";
|
|
env().ExecMain(ctx, std::move(task));
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
void Obj::UpdateMenu() noexcept {
|
|
if (ImGui::MenuItem("config")) {
|
|
popup_ = "ConfigPopup";
|
|
}
|
|
ImGui::Separator();
|
|
if (ImGui::MenuItem("try build")) {
|
|
Build();
|
|
}
|
|
if (ImGui::MenuItem("drop cache", nullptr, nullptr, !!cache_)) {
|
|
Reset();
|
|
}
|
|
}
|
|
void Obj::UpdateTooltip() noexcept {
|
|
ImGui::Text("source: %s", src_.path().Stringify().c_str());
|
|
ImGui::Text("cache : %d", cache_? cache_->index(): -1);
|
|
}
|
|
|
|
}
|
|
} // namespace nf7
|
|
|