unify LuaJIT/Node and LuaJIT/InlineNode into LuaJIT/Node
add std.import to LuaJIT std lib
This commit is contained in:
parent
4a25b88e25
commit
e3dbcb016d
@ -95,6 +95,7 @@ target_sources(nf7
|
||||
common/logger_ref.hh
|
||||
common/luajit.hh
|
||||
common/luajit.cc
|
||||
common/luajit_nfile_importer.hh
|
||||
common/luajit_queue.hh
|
||||
common/luajit_ref.hh
|
||||
common/luajit_thread.hh
|
||||
@ -133,7 +134,6 @@ 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/node_imm.cc
|
||||
file/node_network.cc
|
||||
|
@ -10,13 +10,18 @@
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class GenericContext : public Context {
|
||||
class GenericContext : public nf7::Context {
|
||||
public:
|
||||
GenericContext(Env& env, File::Id id, std::string_view desc = "") noexcept :
|
||||
Context(env, id), desc_(desc) {
|
||||
GenericContext(nf7::Env& env,
|
||||
nf7::File::Id id,
|
||||
std::string_view desc = "",
|
||||
const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
|
||||
nf7::Context(env, id, parent), desc_(desc) {
|
||||
}
|
||||
GenericContext(File& f, std::string_view desc = "") noexcept :
|
||||
GenericContext(f.env(), f.id(), desc) {
|
||||
GenericContext(nf7::File& f,
|
||||
std::string_view desc = "",
|
||||
const std::shared_ptr<nf7::Context>& parent = nullptr) noexcept :
|
||||
GenericContext(f.env(), f.id(), desc, parent) {
|
||||
}
|
||||
|
||||
void CleanUp() noexcept override {
|
||||
|
107
common/luajit_nfile_importer.hh
Normal file
107
common/luajit_nfile_importer.hh
Normal file
@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <lua.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/future.hh"
|
||||
#include "common/generic_context.hh"
|
||||
#include "common/luajit_ref.hh"
|
||||
#include "common/luajit_thread.hh"
|
||||
|
||||
|
||||
namespace nf7::luajit {
|
||||
|
||||
class NFileImporter :
|
||||
public nf7::luajit::Thread::Importer,
|
||||
public std::enable_shared_from_this<NFileImporter> {
|
||||
public:
|
||||
NFileImporter(const std::filesystem::path& base) noexcept : base_(base) {
|
||||
}
|
||||
|
||||
nf7::Future<std::shared_ptr<luajit::Ref>> Import(
|
||||
const luajit::Thread& th, std::string_view name) noexcept {
|
||||
auto self = shared_from_this();
|
||||
|
||||
const auto path = base_ / std::string {name};
|
||||
|
||||
auto ljq = th.ljq();
|
||||
auto ctx = std::make_shared<
|
||||
nf7::GenericContext>(th.env(), th.ctx()->initiator(),
|
||||
"LuaJIT imported script (nfile)", th.ctx());
|
||||
nf7::Future<std::shared_ptr<luajit::Ref>>::Promise pro {ctx};
|
||||
|
||||
// create new thread
|
||||
auto handler = luajit::Thread::CreatePromiseHandler<std::shared_ptr<luajit::Ref>>(
|
||||
pro, [self, this, path, ljq, ctx](auto L) {
|
||||
if (lua_gettop(L) <= 1) {
|
||||
AddImport(path);
|
||||
return std::make_shared<nf7::luajit::Ref>(ctx, ljq, L);
|
||||
} else {
|
||||
throw nf7::Exception {"imported script can return 1 or less results"};
|
||||
}
|
||||
});
|
||||
auto th_sub = std::make_shared<
|
||||
nf7::luajit::Thread>(ctx, ljq, std::move(handler));
|
||||
th_sub->Install(th);
|
||||
|
||||
// install new importer for sub thread
|
||||
auto dir = path;
|
||||
dir.remove_filename();
|
||||
th_sub->Install(std::make_shared<NFileImporter>(dir));
|
||||
|
||||
// start the thread
|
||||
ljq->Push(ctx, [pro, path, th_sub](auto L) mutable {
|
||||
L = th_sub->Init(L);
|
||||
if (0 == luaL_loadfile(L, path.string().c_str())) {
|
||||
th_sub->Resume(L, 0);
|
||||
} else {
|
||||
pro.Throw<nf7::Exception>(std::string {"import failed: "}+lua_tostring(L, -1));
|
||||
}
|
||||
});
|
||||
return pro.future();
|
||||
}
|
||||
|
||||
void ClearImports() noexcept {
|
||||
std::unique_lock<std::mutex> _ {mtx_};
|
||||
imports_.clear();
|
||||
}
|
||||
|
||||
std::filesystem::file_time_type GetLatestMod() const noexcept {
|
||||
std::unique_lock<std::mutex> _ {mtx_};
|
||||
|
||||
std::filesystem::file_time_type ret = {};
|
||||
for (const auto& p : imports_) {
|
||||
try {
|
||||
ret = std::max(ret, std::filesystem::last_write_time(p));
|
||||
} catch (std::filesystem::filesystem_error&) {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::filesystem::path base_;
|
||||
|
||||
mutable std::mutex mtx_;
|
||||
std::vector<std::string> imports_;
|
||||
|
||||
|
||||
void AddImport(const std::filesystem::path& p) noexcept {
|
||||
auto str = p.string();
|
||||
|
||||
std::unique_lock<std::mutex> _ {mtx_};
|
||||
if (imports_.end() == std::find(imports_.begin(), imports_.end(), str)) {
|
||||
imports_.emplace_back(std::move(str));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7::luajit
|
@ -44,4 +44,8 @@ class Ref final : public nf7::Value::Data {
|
||||
int idx_;
|
||||
};
|
||||
|
||||
inline void Push(lua_State* L, const std::shared_ptr<Ref>& ref) noexcept {
|
||||
ref->PushSelf(L);
|
||||
}
|
||||
|
||||
} // namespace nf7::luajit
|
||||
|
@ -128,6 +128,31 @@ static void PushMeta(lua_State* L) noexcept {
|
||||
|
||||
lua_createtable(L, 0, 0);
|
||||
{
|
||||
// nf7:import(npath)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto th = Thread::GetPtr(L, 1);
|
||||
auto im = th->importer();
|
||||
if (!im) {
|
||||
return luaL_error(L, "import is not available in the current thread");
|
||||
}
|
||||
if (const auto name = lua_tostring(L, 2)) {
|
||||
auto fu = im->Import(*th, name);
|
||||
fu.ThenIf([L, th](auto& obj) {
|
||||
th->ExecResume(L, obj);
|
||||
}).
|
||||
template Catch<nf7::Exception>([L, th](auto&) {
|
||||
if (auto log = th->logger()) {
|
||||
log->Warn("import failed, returning nil");
|
||||
}
|
||||
th->ExecResume(L);
|
||||
});
|
||||
return th->Yield(L);
|
||||
} else {
|
||||
return luaL_error(L, "path should be a string");
|
||||
}
|
||||
});
|
||||
lua_setfield(L, -2, "import");
|
||||
|
||||
// nf7:resolve(path)
|
||||
lua_pushcfunction(L, [](auto L) {
|
||||
auto th = Thread::GetPtr(L, 1);
|
||||
|
@ -30,7 +30,7 @@ class Thread final : public std::enable_shared_from_this<Thread> {
|
||||
enum State { kInitial, kRunning, kPaused, kFinished, kAborted, };
|
||||
using Handler = std::function<void(Thread&, lua_State*)>;
|
||||
|
||||
class Lambda;
|
||||
class Importer;
|
||||
|
||||
class Exception final : public nf7::Exception {
|
||||
public:
|
||||
@ -74,6 +74,15 @@ class Thread final : public std::enable_shared_from_this<Thread> {
|
||||
assert(state_ == kInitial);
|
||||
logger_ = logger;
|
||||
}
|
||||
void Install(const std::shared_ptr<Importer>& importer) noexcept {
|
||||
assert(state_ == kInitial);
|
||||
importer_ = importer;
|
||||
}
|
||||
void Install(const Thread& th) noexcept {
|
||||
assert(state_ == kInitial);
|
||||
logger_ = th.logger_;
|
||||
importer_ = th.importer_;
|
||||
}
|
||||
|
||||
// must be called on luajit thread
|
||||
lua_State* Init(lua_State* L) noexcept;
|
||||
@ -109,10 +118,11 @@ class Thread final : public std::enable_shared_from_this<Thread> {
|
||||
});
|
||||
}
|
||||
|
||||
nf7::Env& env() noexcept { return ctx_->env(); }
|
||||
nf7::Env& env() const noexcept { return ctx_->env(); }
|
||||
const std::shared_ptr<nf7::Context>& ctx() const noexcept { return ctx_; }
|
||||
const std::shared_ptr<nf7::luajit::Queue>& ljq() const noexcept { return ljq_; }
|
||||
const std::shared_ptr<nf7::LoggerRef>& logger() const noexcept { return logger_; }
|
||||
const std::shared_ptr<Importer>& importer() const noexcept { return importer_; }
|
||||
State state() const noexcept { return state_; }
|
||||
|
||||
private:
|
||||
@ -133,6 +143,7 @@ class Thread final : public std::enable_shared_from_this<Thread> {
|
||||
|
||||
// installed features
|
||||
std::shared_ptr<nf7::LoggerRef> logger_;
|
||||
std::shared_ptr<Importer> importer_;
|
||||
|
||||
|
||||
// mutable params
|
||||
@ -141,6 +152,21 @@ class Thread final : public std::enable_shared_from_this<Thread> {
|
||||
};
|
||||
|
||||
|
||||
class Thread::Importer {
|
||||
public:
|
||||
Importer() = default;
|
||||
virtual ~Importer() = default;
|
||||
Importer(const Importer&) = delete;
|
||||
Importer(Importer&&) = delete;
|
||||
Importer& operator=(const Importer&) = delete;
|
||||
Importer& operator=(Importer&&) = delete;
|
||||
|
||||
// be called on luajit thread
|
||||
virtual nf7::Future<std::shared_ptr<luajit::Ref>> Import(
|
||||
const luajit::Thread&, std::string_view) noexcept = 0;
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
Thread::Handler Thread::CreatePromiseHandler(
|
||||
nf7::Future<T>::Promise& pro, std::function<T(lua_State*)>&& f) noexcept {
|
||||
|
@ -1,287 +0,0 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include <ImNodes.h>
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/dir_item.hh"
|
||||
#include "common/file_base.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/generic_memento.hh"
|
||||
#include "common/gui_config.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"
|
||||
#include "common/util_algorithm.hh"
|
||||
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
namespace {
|
||||
|
||||
class InlineNode final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
public:
|
||||
static inline const nf7::GenericTypeInfo<InlineNode> kType =
|
||||
{"LuaJIT/InlineNode", {"nf7::DirItem", "nf7::Node"}};
|
||||
static void UpdateTypeTooltip() noexcept {
|
||||
ImGui::TextUnformatted("Defines new pure Node without creating nfile.");
|
||||
}
|
||||
|
||||
class Lambda;
|
||||
|
||||
struct Data {
|
||||
Data() noexcept { }
|
||||
std::string Stringify() const noexcept;
|
||||
void Parse(const std::string&);
|
||||
|
||||
std::string script;
|
||||
std::vector<std::string> inputs = {"in"};
|
||||
std::vector<std::string> outputs = {"out"};
|
||||
};
|
||||
|
||||
InlineNode(nf7::Env& env, Data&& data = {}) noexcept :
|
||||
nf7::FileBase(kType, env, {}),
|
||||
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
||||
nf7::Node(nf7::Node::kCustomNode),
|
||||
life_(*this),
|
||||
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
||||
mem_(std::move(data), *this) {
|
||||
nf7::FileBase::Install(*log_);
|
||||
|
||||
mem_.onCommit = mem_.onRestore = [this]() {
|
||||
cache_ = std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
InlineNode(nf7::Deserializer& ar) : InlineNode(ar.env()) {
|
||||
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||
nf7::util::Uniq(mem_->inputs);
|
||||
nf7::util::Uniq(mem_->outputs);
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||
}
|
||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||
return std::make_unique<InlineNode>(env, Data {mem_.data()});
|
||||
}
|
||||
|
||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||
|
||||
std::span<const std::string> GetInputs() const noexcept override {
|
||||
return mem_->inputs;
|
||||
}
|
||||
std::span<const std::string> GetOutputs() const noexcept override {
|
||||
return mem_->outputs;
|
||||
}
|
||||
|
||||
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept;
|
||||
|
||||
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<InlineNode> life_;
|
||||
|
||||
std::shared_ptr<nf7::LoggerRef> log_;
|
||||
|
||||
nf7::GenericMemento<Data> mem_;
|
||||
|
||||
std::optional<nf7::Future<std::shared_ptr<nf7::luajit::Ref>>> cache_;
|
||||
};
|
||||
|
||||
|
||||
class InlineNode::Lambda final : public nf7::Node::Lambda,
|
||||
public std::enable_shared_from_this<InlineNode::Lambda> {
|
||||
public:
|
||||
Lambda(InlineNode& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
|
||||
}
|
||||
|
||||
void Handle(std::string_view k, const nf7::Value& v,
|
||||
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
|
||||
try {
|
||||
f_.EnforceAlive();
|
||||
|
||||
auto self = shared_from_this();
|
||||
f_->Build().
|
||||
ThenIf(self, [this, k = std::string {k}, v, caller](auto& func) mutable {
|
||||
if (f_) StartThread(std::move(k), v, func, caller);
|
||||
}).
|
||||
Catch<nf7::Exception>([log = log_](auto&) {
|
||||
log->Warn("skips execution because of build failure");
|
||||
});
|
||||
} catch (nf7::ExpiredException&) {
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<InlineNode>::Ref f_;
|
||||
|
||||
std::shared_ptr<nf7::LoggerRef> log_;
|
||||
|
||||
std::mutex mtx_;
|
||||
std::optional<nf7::luajit::Ref> ctx_;
|
||||
|
||||
|
||||
void StartThread(std::string&& k, const nf7::Value& v,
|
||||
const std::shared_ptr<nf7::luajit::Ref>& func,
|
||||
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept {
|
||||
auto ljq = func->ljq();
|
||||
auto self = shared_from_this();
|
||||
|
||||
auto hndl = nf7::luajit::Thread::CreateNodeLambdaHandler(caller, self);
|
||||
auto th = std::make_shared<nf7::luajit::Thread>(self, ljq, std::move(hndl));
|
||||
th->Install(log_);
|
||||
|
||||
ljq->Push(self, [this, ljq, th, func, k = std::move(k), v, caller](auto L) mutable {
|
||||
{
|
||||
std::unique_lock<std::mutex> k {mtx_};
|
||||
if (!ctx_ || ctx_->ljq() != ljq) {
|
||||
lua_createtable(L, 0, 0);
|
||||
ctx_.emplace(shared_from_this(), ljq, L);
|
||||
}
|
||||
}
|
||||
L = th->Init(L);
|
||||
func->PushSelf(L);
|
||||
nf7::luajit::PushAll(L, k, v);
|
||||
ctx_->PushSelf(L);
|
||||
th->Resume(L, 3);
|
||||
});
|
||||
}
|
||||
};
|
||||
std::shared_ptr<nf7::Node::Lambda> InlineNode::CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
||||
return std::make_shared<Lambda>(*this, parent);
|
||||
}
|
||||
|
||||
|
||||
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> InlineNode::Build() noexcept
|
||||
try {
|
||||
if (cache_) return *cache_;
|
||||
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*this, "inline function builder");
|
||||
auto ljq =
|
||||
ResolveUpwardOrThrow("_luajit").
|
||||
interfaceOrThrow<nf7::luajit::Queue>().self();
|
||||
|
||||
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Promise pro;
|
||||
ljq->Push(ctx, [ctx, ljq, pro, script = mem_->script](auto L) mutable {
|
||||
if (0 == luaL_loadstring(L, script.c_str())) {
|
||||
pro.Return(std::make_shared<nf7::luajit::Ref>(ctx, ljq, L));
|
||||
} else {
|
||||
pro.Throw<nf7::Exception>(lua_tostring(L, -1));
|
||||
}
|
||||
});
|
||||
|
||||
cache_ = pro.future().
|
||||
Catch<nf7::Exception>([log = log_](auto& e) {
|
||||
log->Error(e);
|
||||
});
|
||||
return *cache_;
|
||||
} catch (nf7::Exception&) {
|
||||
return {std::current_exception()};
|
||||
}
|
||||
|
||||
|
||||
void InlineNode::UpdateMenu() noexcept {
|
||||
if (ImGui::BeginMenu("config")) {
|
||||
nf7::gui::Config(mem_);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
void InlineNode::UpdateNode(nf7::Node::Editor&) noexcept {
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
ImGui::TextUnformatted("LuaJIT/InlineNode");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("config")) {
|
||||
ImGui::OpenPopup("ConfigPopup");
|
||||
}
|
||||
if (ImGui::BeginPopup("ConfigPopup")) {
|
||||
nf7::gui::Config(mem_);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("build")) {
|
||||
Build();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("try to compile the script (for syntax check)");
|
||||
}
|
||||
|
||||
nf7::gui::NodeInputSockets(mem_->inputs);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputTextMultiline("##script", &mem_->script, {24*em, 8*em});
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
mem_.Commit();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
nf7::gui::NodeOutputSockets(mem_->outputs);
|
||||
}
|
||||
void InlineNode::UpdateWidget() noexcept {
|
||||
nf7::gui::Config(mem_);
|
||||
}
|
||||
|
||||
|
||||
std::string InlineNode::Data::Stringify() const noexcept {
|
||||
YAML::Emitter st;
|
||||
st << YAML::BeginMap;
|
||||
st << YAML::Key << "inputs";
|
||||
st << YAML::Value << inputs;
|
||||
st << YAML::Key << "outputs";
|
||||
st << YAML::Value << outputs;
|
||||
st << YAML::Key << "script";
|
||||
st << YAML::Value << YAML::Literal << script;
|
||||
st << YAML::EndMap;
|
||||
return std::string {st.c_str(), st.size()};
|
||||
}
|
||||
void InlineNode::Data::Parse(const std::string& str)
|
||||
try {
|
||||
const auto yaml = YAML::Load(str);
|
||||
auto new_inputs = yaml["inputs"] .as<std::vector<std::string>>();
|
||||
auto new_outputs = yaml["outputs"].as<std::vector<std::string>>();
|
||||
auto new_script = yaml["script"].as<std::string>();
|
||||
|
||||
if (nf7::util::Uniq(new_inputs) > 0) {
|
||||
throw nf7::Exception {"duplicated inputs"};
|
||||
}
|
||||
if (nf7::util::Uniq(new_outputs) > 0) {
|
||||
throw nf7::Exception {"duplicated outputs"};
|
||||
}
|
||||
|
||||
inputs = std::move(new_inputs);
|
||||
outputs = std::move(new_outputs);
|
||||
script = std::move(new_script);
|
||||
} catch (YAML::Exception& e) {
|
||||
throw nf7::Exception {e.what()};
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace nf7
|
||||
|
@ -1,316 +1,316 @@
|
||||
#include <exception>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
|
||||
#include <lua.hpp>
|
||||
#include <ImNodes.h>
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/std/string.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/dir_item.hh"
|
||||
#include "common/file_base.hh"
|
||||
#include "common/future.hh"
|
||||
#include "common/generic_context.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/generic_memento.hh"
|
||||
#include "common/gui_config.hh"
|
||||
#include "common/gui_file.hh"
|
||||
#include "common/gui_node.hh"
|
||||
#include "common/life.hh"
|
||||
#include "common/logger_ref.hh"
|
||||
#include "common/luajit.hh"
|
||||
#include "common/luajit_nfile_importer.hh"
|
||||
#include "common/luajit_queue.hh"
|
||||
#include "common/luajit_ref.hh"
|
||||
#include "common/luajit_thread.hh"
|
||||
#include "common/memento.hh"
|
||||
#include "common/nfile_watcher.hh"
|
||||
#include "common/node.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/util_algorithm.hh"
|
||||
#include "common/yas_std_filesystem.hh"
|
||||
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
namespace {
|
||||
|
||||
class LuaNode final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
class Node final : public nf7::FileBase, public nf7::DirItem, public nf7::Node {
|
||||
public:
|
||||
static inline const nf7::GenericTypeInfo<LuaNode> kType =
|
||||
{"LuaJIT/Node", {"nf7::DirItem"}};
|
||||
static inline const nf7::GenericTypeInfo<Node> kType =
|
||||
{"LuaJIT/Node", {"nf7::DirItem", "nf7::Node"}};
|
||||
static void UpdateTypeTooltip() noexcept {
|
||||
ImGui::TextUnformatted("defines new pure Node");
|
||||
ImGui::TextUnformatted("Defines new pure Node without creating nfile.");
|
||||
}
|
||||
|
||||
class Builder;
|
||||
class Lambda;
|
||||
|
||||
struct Meta {
|
||||
std::vector<std::string> inputs, outputs;
|
||||
std::optional<nf7::luajit::Ref> lambda;
|
||||
};
|
||||
struct Data {
|
||||
Data() noexcept { }
|
||||
std::string Stringify() const noexcept;
|
||||
void Parse(const std::string&);
|
||||
|
||||
std::filesystem::path npath;
|
||||
std::string script;
|
||||
std::vector<std::string> inputs = {"in"};
|
||||
std::vector<std::string> outputs = {"out"};
|
||||
};
|
||||
|
||||
LuaNode(Env& env, Data&& data = {}) noexcept :
|
||||
nf7::FileBase(kType, env, {&nfile_watcher_}),
|
||||
nf7::DirItem(nf7::DirItem::kTooltip | nf7::DirItem::kWidget),
|
||||
nf7::Node(nf7::Node::kNone),
|
||||
Node(nf7::Env& env, Data&& data = {}) noexcept :
|
||||
nf7::FileBase(kType, env, {}),
|
||||
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
||||
nf7::Node(nf7::Node::kCustomNode),
|
||||
life_(*this),
|
||||
log_(std::make_shared<nf7::LoggerRef>(*this)),
|
||||
mem_(std::move(data), *this) {
|
||||
mem_(std::move(data), *this),
|
||||
importer_(std::make_shared<nf7::luajit::NFileImporter>(env.npath())) {
|
||||
nf7::FileBase::Install(*log_);
|
||||
|
||||
nfile_watcher_.onMod = [this]() {
|
||||
mem_.onCommit = mem_.onRestore = [this]() {
|
||||
cache_ = std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
LuaNode(nf7::Deserializer& ar) : LuaNode(ar.env()) {
|
||||
ar(mem_->npath);
|
||||
Node(nf7::Deserializer& ar) : Node(ar.env()) {
|
||||
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||
nf7::util::Uniq(mem_->inputs);
|
||||
nf7::util::Uniq(mem_->outputs);
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(mem_->npath);
|
||||
ar(mem_->script, mem_->inputs, mem_->outputs);
|
||||
}
|
||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||
return std::make_unique<LuaNode>(env, Data {mem_.data()});
|
||||
return std::make_unique<Node>(env, Data {mem_.data()});
|
||||
}
|
||||
|
||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||
|
||||
std::span<const std::string> GetInputs() const noexcept override {
|
||||
if (cache_ && cache_->done()) return cache_->value()->inputs;
|
||||
return {};
|
||||
return mem_->inputs;
|
||||
}
|
||||
std::span<const std::string> GetOutputs() const noexcept override {
|
||||
if (cache_ && cache_->done()) return cache_->value()->outputs;
|
||||
return {};
|
||||
return mem_->outputs;
|
||||
}
|
||||
|
||||
nf7::Future<std::shared_ptr<Meta>> Build() noexcept;
|
||||
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept;
|
||||
|
||||
void Handle(const nf7::File::Event& ev) noexcept override {
|
||||
nf7::FileBase::Handle(ev);
|
||||
switch (ev.type) {
|
||||
case nf7::File::Event::kAdd:
|
||||
Build();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTooltip() noexcept override;
|
||||
void Update() noexcept override;
|
||||
void UpdateMenu() noexcept override;
|
||||
void UpdateNode(nf7::Node::Editor&) noexcept override;
|
||||
void UpdateWidget() noexcept override;
|
||||
|
||||
nf7::File::Interface* interface(const std::type_info& t) 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<LuaNode> life_;
|
||||
nf7::Life<Node> life_;
|
||||
|
||||
std::shared_ptr<nf7::LoggerRef> log_;
|
||||
|
||||
NFileWatcher nfile_watcher_;
|
||||
|
||||
nf7::GenericMemento<Data> mem_;
|
||||
|
||||
std::optional<nf7::Future<std::shared_ptr<Meta>>> cache_;
|
||||
std::optional<nf7::Future<std::shared_ptr<nf7::luajit::Ref>>> cache_;
|
||||
|
||||
std::filesystem::file_time_type last_build_ = {};
|
||||
std::shared_ptr<nf7::luajit::NFileImporter> importer_;
|
||||
};
|
||||
|
||||
class LuaNode::Lambda final : public nf7::Node::Lambda,
|
||||
public std::enable_shared_from_this<LuaNode::Lambda> {
|
||||
|
||||
class Node::Lambda final : public nf7::Node::Lambda,
|
||||
public std::enable_shared_from_this<Node::Lambda> {
|
||||
public:
|
||||
Lambda(LuaNode& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||
nf7::Node::Lambda(f, parent), f_(f.life_) {
|
||||
Lambda(Node& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||
nf7::Node::Lambda(f, parent), f_(f.life_), log_(f.log_) {
|
||||
}
|
||||
|
||||
void Handle(std::string_view k, const nf7::Value& v,
|
||||
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept override
|
||||
try {
|
||||
th_.erase(
|
||||
std::remove_if(th_.begin(), th_.end(), [](auto& x) { return x.expired(); }),
|
||||
th_.end());
|
||||
f_.EnforceAlive();
|
||||
|
||||
auto self = shared_from_this();
|
||||
|
||||
f_.EnforceAlive();
|
||||
f_->Build().
|
||||
ThenIf(self, [this, k = std::string {k}, v, caller](auto& meta) mutable {
|
||||
if (f_) StartThread(std::move(k), v, caller, meta);
|
||||
ThenIf(self, [this, k = std::string {k}, v, caller](auto& func) mutable {
|
||||
if (f_) StartThread(std::move(k), v, func, caller);
|
||||
}).
|
||||
Catch<nf7::Exception>([log = f_->log_](auto& e) {
|
||||
log->Error(e);
|
||||
Catch<nf7::Exception>([log = log_](auto&) {
|
||||
log->Warn("skips execution because of build failure");
|
||||
});
|
||||
} catch (nf7::ExpiredException&) {
|
||||
}
|
||||
|
||||
void Abort() noexcept override {
|
||||
for (auto wth : th_) {
|
||||
auto th = wth.lock();
|
||||
th->Abort();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<LuaNode>::Ref f_;
|
||||
nf7::Life<Node>::Ref f_;
|
||||
|
||||
std::vector<std::weak_ptr<nf7::luajit::Thread>> th_;
|
||||
std::shared_ptr<nf7::LoggerRef> log_;
|
||||
|
||||
std::mutex mtx_;
|
||||
std::optional<nf7::luajit::Ref> ctx_;
|
||||
|
||||
|
||||
void StartThread(std::string&& k, const nf7::Value& v,
|
||||
const std::shared_ptr<nf7::Node::Lambda>& caller,
|
||||
const std::shared_ptr<Meta>& meta) {
|
||||
const std::shared_ptr<nf7::luajit::Ref>& func,
|
||||
const std::shared_ptr<nf7::Node::Lambda>& caller) noexcept {
|
||||
auto ljq = func->ljq();
|
||||
auto self = shared_from_this();
|
||||
auto log = f_->log_;
|
||||
auto ljq = meta->lambda->ljq();
|
||||
|
||||
auto hndl = nf7::luajit::Thread::CreateNodeLambdaHandler(caller, self);
|
||||
auto th = std::make_shared<nf7::luajit::Thread>(self, ljq, std::move(hndl));
|
||||
th->Install(log);
|
||||
th_.emplace_back(th);
|
||||
th->Install(log_);
|
||||
th->Install(f_->importer_);
|
||||
|
||||
ljq->Push(self, [this, ljq, th, meta, k = std::move(k), v](auto L) {
|
||||
// create context table
|
||||
ljq->Push(self, [this, ljq, th, func, k = std::move(k), v, caller](auto L) mutable {
|
||||
{
|
||||
std::unique_lock<std::mutex> _(mtx_);
|
||||
std::unique_lock<std::mutex> k {mtx_};
|
||||
if (!ctx_ || ctx_->ljq() != ljq) {
|
||||
lua_createtable(L, 0, 0);
|
||||
ctx_.emplace(shared_from_this(), ljq, L);
|
||||
}
|
||||
}
|
||||
|
||||
// start thread
|
||||
L = th->Init(L);
|
||||
meta->lambda->PushSelf(L);
|
||||
func->PushSelf(L);
|
||||
nf7::luajit::PushAll(L, k, v);
|
||||
ctx_->PushSelf(L);
|
||||
th->Resume(L, 3);
|
||||
});
|
||||
}
|
||||
};
|
||||
std::shared_ptr<nf7::Node::Lambda> LuaNode::CreateLambda(
|
||||
std::shared_ptr<nf7::Node::Lambda> Node::CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
||||
return std::make_shared<Lambda>(*this, parent);
|
||||
}
|
||||
|
||||
|
||||
nf7::Future<std::shared_ptr<LuaNode::Meta>> LuaNode::Build() noexcept {
|
||||
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Node::Build() noexcept
|
||||
try {
|
||||
if (cache_) return *cache_;
|
||||
last_build_ = std::chrono::file_clock::now();
|
||||
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*this, "LuaJIT Node builder");
|
||||
nf7::Future<std::shared_ptr<Meta>>::Promise pro {ctx};
|
||||
try {
|
||||
auto ljq =
|
||||
ResolveUpwardOrThrow("_luajit").
|
||||
interfaceOrThrow<nf7::luajit::Queue>().self();
|
||||
nf7::Future<std::shared_ptr<nf7::luajit::Ref>>::Promise pro;
|
||||
|
||||
auto handler = nf7::luajit::Thread::CreatePromiseHandler<std::shared_ptr<Meta>>(pro, [ctx, ljq](auto L) {
|
||||
if (1 != lua_gettop(L) || !lua_istable(L, 1)) {
|
||||
throw nf7::Exception {"builder script should return a table"};
|
||||
}
|
||||
auto ctx = std::make_shared<nf7::GenericContext>(*this, "lambda function builder");
|
||||
auto ljq =
|
||||
ResolveUpwardOrThrow("_luajit").
|
||||
interfaceOrThrow<nf7::luajit::Queue>().self();
|
||||
|
||||
auto ret = std::make_shared<Meta>();
|
||||
// create new thread
|
||||
auto handler = luajit::Thread::CreatePromiseHandler<std::shared_ptr<luajit::Ref>>(pro, [ctx, ljq](auto L) {
|
||||
if (lua_gettop(L) == 1 && lua_isfunction(L, 1)) {
|
||||
return std::make_shared<nf7::luajit::Ref>(ctx, ljq, L);
|
||||
} else {
|
||||
throw nf7::Exception {"lambda script must return a function"};
|
||||
}
|
||||
});
|
||||
auto th = std::make_shared<nf7::luajit::Thread>(ctx, ljq, std::move(handler));
|
||||
th->Install(log_);
|
||||
th->Install(importer_);
|
||||
|
||||
lua_getfield(L, 1, "inputs");
|
||||
nf7::luajit::ToStringList(L, -1, ret->inputs);
|
||||
if (nf7::util::Uniq(ret->inputs) > 0) {
|
||||
throw nf7::Exception {"duplicated inputs"};
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 1, "outputs");
|
||||
nf7::luajit::ToStringList(L, -1, ret->outputs);
|
||||
if (nf7::util::Uniq(ret->outputs)) {
|
||||
throw nf7::Exception {"duplicated outputs"};
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 1, "lambda");
|
||||
ret->lambda.emplace(ctx, ljq, L);
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
auto th = std::make_shared<nf7::luajit::Thread>(ctx, ljq, std::move(handler));
|
||||
th->Install(log_);
|
||||
ljq->Push(ctx, [ljq, pro, th, npath = mem_->npath](auto L) mutable {
|
||||
auto thL = th->Init(L);
|
||||
|
||||
const auto npathstr = npath.string();
|
||||
const auto ret = luaL_loadfile(thL, npathstr.c_str());
|
||||
switch (ret) {
|
||||
case 0:
|
||||
th->Resume(thL, 0);
|
||||
break;
|
||||
default:
|
||||
pro.Throw<nf7::Exception>(lua_tostring(thL, -1));
|
||||
break;
|
||||
}
|
||||
});
|
||||
} catch (nf7::Exception&) {
|
||||
pro.Throw(std::current_exception());
|
||||
}
|
||||
// start the thread
|
||||
ljq->Push(ctx, [ctx, ljq, th, pro, script = mem_->script](auto L) mutable {
|
||||
L = th->Init(L);
|
||||
if (0 == luaL_loadstring(L, script.c_str())) {
|
||||
th->Resume(L, 0);
|
||||
} else {
|
||||
pro.Throw<nf7::Exception>(lua_tostring(L, -1));
|
||||
}
|
||||
});
|
||||
|
||||
cache_ = pro.future().
|
||||
Catch<nf7::Exception>(ctx, [log = log_](auto& e) {
|
||||
log->Error(e);
|
||||
});
|
||||
Catch<nf7::Exception>([log = log_](auto& e) {
|
||||
log->Error(e);
|
||||
});
|
||||
return *cache_;
|
||||
} catch (nf7::Exception&) {
|
||||
return {std::current_exception()};
|
||||
}
|
||||
|
||||
|
||||
void LuaNode::UpdateTooltip() noexcept {
|
||||
ImGui::Text("cache : %s", cache_? "ready": "none");
|
||||
void Node::Update() noexcept {
|
||||
nf7::FileBase::Update();
|
||||
|
||||
if (cache_ && cache_->done()) {
|
||||
auto cache = cache_->value();
|
||||
ImGui::TextUnformatted("inputs:");
|
||||
for (const auto& name : cache->inputs) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
|
||||
}
|
||||
ImGui::TextUnformatted("outputs:");
|
||||
for (const auto& name : cache->outputs) {
|
||||
ImGui::Bullet(); ImGui::TextUnformatted(name.c_str());
|
||||
}
|
||||
if (last_build_ < importer_->GetLatestMod()) {
|
||||
cache_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
void LuaNode::UpdateWidget() noexcept {
|
||||
|
||||
void Node::UpdateMenu() noexcept {
|
||||
if (ImGui::BeginMenu("config")) {
|
||||
nf7::gui::Config(mem_);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
void Node::UpdateNode(nf7::Node::Editor&) noexcept {
|
||||
const auto em = ImGui::GetFontSize();
|
||||
|
||||
ImGui::TextUnformatted("LuaJIT/Node");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("config")) {
|
||||
ImGui::OpenPopup("ConfigPopup");
|
||||
}
|
||||
if (ImGui::BeginPopup("ConfigPopup")) {
|
||||
nf7::gui::Config(mem_);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("build")) {
|
||||
Build();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("try to compile the script (for syntax check)");
|
||||
}
|
||||
|
||||
nf7::gui::NodeInputSockets(mem_->inputs);
|
||||
ImGui::SameLine();
|
||||
ImGui::InputTextMultiline("##script", &mem_->script, {24*em, 8*em});
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
mem_.Commit();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
nf7::gui::NodeOutputSockets(mem_->outputs);
|
||||
}
|
||||
void Node::UpdateWidget() noexcept {
|
||||
nf7::gui::Config(mem_);
|
||||
}
|
||||
|
||||
|
||||
std::string LuaNode::Data::Stringify() const noexcept {
|
||||
std::string Node::Data::Stringify() const noexcept {
|
||||
YAML::Emitter st;
|
||||
st << YAML::BeginMap;
|
||||
st << YAML::Key << "npath";
|
||||
st << YAML::Value << npath.string();
|
||||
st << YAML::Key << "inputs";
|
||||
st << YAML::Value << inputs;
|
||||
st << YAML::Key << "outputs";
|
||||
st << YAML::Value << outputs;
|
||||
st << YAML::Key << "script";
|
||||
st << YAML::Value << YAML::Literal << script;
|
||||
st << YAML::EndMap;
|
||||
return std::string {st.c_str(), st.size()};
|
||||
}
|
||||
void LuaNode::Data::Parse(const std::string& str)
|
||||
void Node::Data::Parse(const std::string& str)
|
||||
try {
|
||||
const auto yaml = YAML::Load(str);
|
||||
npath = yaml["npath"].as<std::string>();
|
||||
auto new_inputs = yaml["inputs"] .as<std::vector<std::string>>();
|
||||
auto new_outputs = yaml["outputs"].as<std::vector<std::string>>();
|
||||
auto new_script = yaml["script"].as<std::string>();
|
||||
|
||||
if (nf7::util::Uniq(new_inputs) > 0) {
|
||||
throw nf7::Exception {"duplicated inputs"};
|
||||
}
|
||||
if (nf7::util::Uniq(new_outputs) > 0) {
|
||||
throw nf7::Exception {"duplicated outputs"};
|
||||
}
|
||||
|
||||
inputs = std::move(new_inputs);
|
||||
outputs = std::move(new_outputs);
|
||||
script = std::move(new_script);
|
||||
} catch (YAML::Exception& e) {
|
||||
throw nf7::Exception {e.what()};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user