From 3a0c6f7316c61925d972aec17d26f1f8ceb81e13 Mon Sep 17 00:00:00 2001 From: falsycat Date: Wed, 24 Aug 2022 10:27:40 +0900 Subject: [PATCH] add LuaJIT/InlineNode --- CMakeLists.txt | 1 + common/luajit.cc | 37 +++++ common/luajit.hh | 4 + file/luajit_inline_node.cc | 270 +++++++++++++++++++++++++++++++++++++ file/luajit_node.cc | 37 +---- 5 files changed, 313 insertions(+), 36 deletions(-) create mode 100644 file/luajit_inline_node.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 80728de..12ac01a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,7 @@ target_sources(nf7 file/audio_context.cc file/audio_device.cc file/luajit_context.cc + file/luajit_inline_node.cc file/luajit_node.cc file/luajit_obj.cc file/node_imm.cc diff --git a/common/luajit.cc b/common/luajit.cc index af3d1e8..7f26a5f 100644 --- a/common/luajit.cc +++ b/common/luajit.cc @@ -357,6 +357,43 @@ void PushMutableVector(lua_State* L, std::vector&& v) noexcept { lua_setmetatable(L, -2); } +void PushNodeLambda(lua_State* L, + const std::shared_ptr& callee, + const std::weak_ptr& caller) noexcept { + constexpr auto kTypeName = "nf7::Node::Lambda"; + struct Data { + std::shared_ptr callee; + std::weak_ptr caller; + }; + new (lua_newuserdata(L, sizeof(Data))) Data { .callee = callee, .caller = caller }; + + if (luaL_newmetatable(L, kTypeName)) { + lua_pushcfunction(L, [](auto L) { + const auto& d = *reinterpret_cast(luaL_checkudata(L, 1, kTypeName)); + + auto caller = d.caller.lock(); + if (!caller) return luaL_error(L, "caller expired"); + + std::string n = luaL_checkstring(L, 2); + auto v = nf7::luajit::CheckValue(L, 3); + + auto callee = d.callee; + callee->env().ExecSub(callee, [callee, caller, n = std::move(n), v = std::move(v)]() { + callee->Handle(n, v, caller); + }); + return 0; + }); + lua_setfield(L, -2, "__call"); + + lua_pushcfunction(L, [](auto L) { + reinterpret_cast(luaL_checkudata(L, 1, kTypeName))->~Data(); + return 0; + }); + lua_setfield(L, -2, "__gc"); + } + lua_setmetatable(L, -2); +} + std::optional ToValue(lua_State* L, int idx) noexcept { if (lua_isnoneornil(L, idx)) { diff --git a/common/luajit.hh b/common/luajit.hh index 4f1fd53..b7ea418 100644 --- a/common/luajit.hh +++ b/common/luajit.hh @@ -9,6 +9,7 @@ #include +#include "common/node.hh" #include "common/value.hh" @@ -19,6 +20,9 @@ void PushImmEnv(lua_State*) noexcept; void PushValue(lua_State*, const nf7::Value&) noexcept; void PushVector(lua_State*, const nf7::Value::ConstVector&) noexcept; void PushMutableVector(lua_State*, std::vector&&) noexcept; +void PushNodeLambda(lua_State*, + const std::shared_ptr& callee, + const std::weak_ptr& caller) noexcept; std::optional ToValue(lua_State*, int) noexcept; std::optional ToVector(lua_State*, int) noexcept; diff --git a/file/luajit_inline_node.cc b/file/luajit_inline_node.cc new file mode 100644 index 0000000..e0d8e49 --- /dev/null +++ b/file/luajit_inline_node.cc @@ -0,0 +1,270 @@ +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include "nf7.hh" + +#include "common/dir_item.hh" +#include "common/generic_type_info.hh" +#include "common/generic_memento.hh" +#include "common/gui_file.hh" +#include "common/gui_node.hh" +#include "common/life.hh" +#include "common/logger_ref.hh" +#include "common/luajit_queue.hh" +#include "common/luajit_ref.hh" +#include "common/luajit_thread.hh" +#include "common/memento.hh" +#include "common/node.hh" +#include "common/ptr_selector.hh" + + +using namespace std::literals; + + +namespace nf7 { +namespace { + +class InlineNode final : public nf7::File, public nf7::DirItem, public nf7::Node { + public: + static inline const GenericTypeInfo kType = + {"LuaJIT/InlineNode", {"nf7::Node"}}; + static void UpdateTypeTooltip() noexcept { + ImGui::TextUnformatted("Defines new Node using Lua object factory."); + ImGui::Bullet(); + ImGui::TextUnformatted("refers nf7::luajit::Queue through linked LuaJIT/Obj"); + } + + class Lambda; + + struct Data { + std::string script; + }; + + InlineNode(nf7::Env& env, Data&& data = {}) noexcept : + nf7::File(kType, env), + nf7::DirItem(nf7::DirItem::kWidget), + life_(*this), + log_(std::make_shared()), + mem_(std::move(data)) { + mem_.onRestore = [this]() { Touch(); }; + mem_.onCommit = [this]() { Touch(); }; + } + + InlineNode(nf7::Env& env, nf7::Deserializer& ar) : InlineNode(env) { + ar(data().script); + } + void Serialize(nf7::Serializer& ar) const noexcept override { + ar(data().script); + } + std::unique_ptr Clone(nf7::Env& env) const noexcept override { + return std::make_unique(env, Data {data()}); + } + + std::shared_ptr CreateLambda( + const std::shared_ptr&) noexcept override; + + std::span GetInputs() const noexcept override { + static const std::vector kInputs = {"in"}; + return kInputs; + } + std::span GetOutputs() const noexcept override { + static const std::vector kOutputs = {"out"}; + return kOutputs; + } + + void Handle(const Event&) noexcept override; + void UpdateMenu() noexcept override; + void UpdateNode(nf7::Node::Editor&) noexcept override; + void UpdateWidget() noexcept override; + + File::Interface* interface(const std::type_info& t) noexcept override { + return nf7::InterfaceSelector< + nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_); + } + + private: + nf7::Life life_; + + std::shared_ptr mtx_; + + std::shared_ptr log_; + + nf7::GenericMemento mem_; + const Data& data() const noexcept { return mem_.data(); } + Data& data() noexcept { return mem_.data(); } +}; + +class InlineNode::Lambda final : public nf7::Node::Lambda, + public std::enable_shared_from_this { + public: + Lambda(InlineNode& f, const std::shared_ptr& parent) noexcept : + nf7::Node::Lambda(f, parent), file_(f.life_), log_(f.log_) { + } + + void Handle(std::string_view k, const nf7::Value& v, + const std::shared_ptr& caller) noexcept override + try { + file_.EnforceAlive(); + + auto ljq = file_-> + ResolveUpwardOrThrow("_luajit"). + interfaceOrThrow().self(); + + std::optional scr; + + auto& mem = file_->mem_; + if (last_ != std::exchange(last_, mem.Save()->id())) { + scr = mem.last().script; + } + + auto self = shared_from_this(); + auto th = std::make_shared( + self, ljq, + [self, ljq](auto& th, auto L) { self->HandleThread(ljq, th, L); }); + th->Install(log_); + th_.emplace_back(th); + + auto p = std::make_pair(std::string {k}, std::move(v)); + ljq->Push(self, [self, this, ljq, caller, th, scr = std::move(scr), p = std::move(p)](auto L) { + auto thL = th->Init(L); + + // push function + if (scr) { + luaL_loadstring(thL, scr->c_str()); + lua_pushvalue(thL, -1); + func_.emplace(self, ljq, luaL_ref(thL, LUA_REGISTRYINDEX)); + } else { + lua_rawgeti(thL, LUA_REGISTRYINDEX, func_->index()); + } + + // push args + lua_pushstring(thL, p.first.c_str()); // key + nf7::luajit::PushValue(thL, p.second); // value + nf7::luajit::PushNodeLambda(thL, caller, self); // caller + + // push ctx table + if (ctxtable_ && ctxtable_->ljq() != ljq) { + ctxtable_ = std::nullopt; + log_->Warn("LuaJIT queue changed, ctxtable is cleared"); + } + if (ctxtable_) { + lua_rawgeti(thL, LUA_REGISTRYINDEX, ctxtable_->index()); + } else { + lua_createtable(thL, 0, 0); + lua_pushvalue(thL, -1); + ctxtable_.emplace(self, ljq, luaL_ref(thL, LUA_REGISTRYINDEX)); + } + + // start function + th->Resume(thL, 4); + }); + + } catch (nf7::LifeExpiredException&) { + } catch (nf7::Exception& e) { + log_->Error(e.msg()); + } + void Abort() noexcept override { + for (auto& wth : th_) { + if (auto th = wth.lock()) { + th->Abort(); + } + } + } + + private: + // synchronized with filesystem + nf7::Life::Ref file_; + + std::shared_ptr log_; + + std::optional last_; + + std::vector> th_; + + // used on luajit thread + std::optional func_; + std::optional ctxtable_; + + + void HandleThread(const std::shared_ptr& ljq, + nf7::luajit::Thread& th, lua_State* L) noexcept { + switch (th.state()) { + case nf7::luajit::Thread::kFinished: + return; + + case nf7::luajit::Thread::kPaused: + log_->Warn("unexpected yield"); + ljq->Push(shared_from_this(), + [th = th.shared_from_this(), L](auto) { th->Resume(L, 0); }); + return; + + default: + log_->Warn("luajit execution error: "s+lua_tostring(L, -1)); + return; + } + } +}; + + +std::shared_ptr InlineNode::CreateLambda( + const std::shared_ptr& parent) noexcept { + return std::make_shared(*this, parent); +} + +void InlineNode::Handle(const Event& ev) noexcept { + switch (ev.type) { + case Event::kAdd: + log_->SetUp(*this); + return; + + case Event::kRemove: + log_->TearDown(); + return; + + default: + return; + } +} +void InlineNode::UpdateMenu() noexcept { +} +void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept { + ImGui::TextUnformatted("LuaJIT/InlineNode"); + + if (ImNodes::BeginInputSlot("in", 1)) { + ImGui::AlignTextToFramePadding(); + nf7::gui::NodeSocket(); + ImNodes::EndSlot(); + } + ImGui::SameLine(); + ImGui::InputTextMultiline("##script", &data().script); + if (ImGui::IsItemDeactivatedAfterEdit()) { + mem_.Commit(); + } + ImGui::SameLine(); + if (ImNodes::BeginOutputSlot("out", 1)) { + ImGui::AlignTextToFramePadding(); + nf7::gui::NodeSocket(); + ImNodes::EndSlot(); + } +} +void InlineNode::UpdateWidget() noexcept { + ImGui::TextUnformatted("LuaJIT/InlineNode"); + ImGui::InputTextMultiline("script", &data().script); + if (ImGui::IsItemDeactivatedAfterEdit()) { + mem_.Commit(); + } +} + +} +} // namespace nf7 + diff --git a/file/luajit_node.cc b/file/luajit_node.cc index eb7ab79..a6a1340 100644 --- a/file/luajit_node.cc +++ b/file/luajit_node.cc @@ -202,7 +202,7 @@ class Node::Lambda final : public nf7::Node::Lambda, lua_rawgeti(thL, LUA_REGISTRYINDEX, handler->index()); lua_pushstring(thL, p.first.c_str()); nf7::luajit::PushValue(thL, p.second); - PushCaller(thL, caller); + nf7::luajit::PushNodeLambda(thL, caller, self); PushContextTable(ljq, thL); th->Resume(thL, 4); }); @@ -228,41 +228,6 @@ class Node::Lambda final : public nf7::Node::Lambda, } } - void PushCaller(lua_State* L, const std::shared_ptr& caller) noexcept { - constexpr auto kTypeName = "nf7::File/LuaJIT/Node::Owner"; - struct D final { - std::weak_ptr self; - std::shared_ptr caller; - }; - new (lua_newuserdata(L, sizeof(D))) D { .self = weak_from_this(), .caller = caller }; - - if (luaL_newmetatable(L, kTypeName)) { - lua_pushcfunction(L, [](auto L) { - const auto& d = *reinterpret_cast(luaL_checkudata(L, 1, kTypeName)); - - auto self = d.self.lock(); - if (!self) return luaL_error(L, "context expired"); - - std::string n = luaL_checkstring(L, 2); - auto v = nf7::luajit::CheckValue(L, 3); - - auto caller = d.caller; - caller->env().ExecSub(self, [self, caller, n, v]() mutable { - caller->Handle(n, std::move(v), self); - }); - return 0; - }); - lua_setfield(L, -2, "__call"); - - lua_pushcfunction(L, [](auto L) { - reinterpret_cast(luaL_checkudata(L, 1, kTypeName))->~D(); - return 0; - }); - lua_setfield(L, -2, "__gc"); - } - lua_setmetatable(L, -2); - } - void PushContextTable(const std::shared_ptr& ljq, lua_State* L) noexcept { if (ctxtable_ && ctxtable_->ljq() != ljq) {