add Node/ExprTk

This commit is contained in:
falsycat 2022-11-24 00:56:22 +09:00
parent 1978d28316
commit dcc3668d6c
4 changed files with 331 additions and 26 deletions

View File

@ -158,6 +158,7 @@ target_sources(nf7
file/gl_obj.cc
file/luajit_context.cc
file/luajit_node.cc
file/node_exprtk.cc
file/node_mutex.cc
file/node_network.cc
file/node_ref.cc

View File

@ -96,7 +96,7 @@ class Value {
Value(DataPtr&& v) noexcept : value_(std::move(v)) { }
Value& operator=(DataPtr&& v) noexcept { value_ = std::move(v); return *this; }
auto Visit(auto visitor) const noexcept {
auto Visit(auto visitor) const {
return std::visit(visitor, value_);
}
@ -225,15 +225,6 @@ class Value {
st << "expected " << typeid(T).name() << " but it's " << typeName();
throw IncompatibleException(st.str());
}
template <typename T>
std::shared_ptr<typename std::remove_const<typename T::element_type>::type> getUniq() {
auto v = std::move(get<T>());
if (v.use_count() == 1) {
return std::const_pointer_cast<typename std::remove_const<typename T::element_type>::type>(v);
} else {
return std::make_shared<typename std::remove_const<typename T::element_type>::type>(*v);
}
}
template <typename R, typename N>
static R SafeCast(N in) {

View File

@ -65,8 +65,7 @@ class Node final : public nf7::FileBase,
Node(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu |
nf7::DirItem::kTooltip),
nf7::DirItem(nf7::DirItem::kTooltip),
nf7::Node(nf7::Node::kCustomNode),
life_(*this),
log_(std::make_shared<nf7::LoggerRef>(*this)),
@ -99,7 +98,6 @@ class Node final : public nf7::FileBase,
nf7::Future<std::shared_ptr<nf7::luajit::Ref>> Build() noexcept;
void PostUpdate() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
void UpdateNode(nf7::Node::Editor&) noexcept override;
@ -247,12 +245,6 @@ void Node::PostUpdate() noexcept {
}
}
void Node::UpdateMenu() noexcept {
if (ImGui::MenuItem("build")) {
Build();
}
}
void Node::UpdateTooltip() noexcept {
const char* state = "unused";
if (cache_) {
@ -276,13 +268,6 @@ void Node::UpdateNode(nf7::Node::Editor&) noexcept {
ed(*this);
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();

328
file/node_exprtk.cc Normal file
View File

@ -0,0 +1,328 @@
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <exprtk.hpp>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <tracy/Tracy.hpp>
#include <yaml-cpp/yaml.h>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/dir_item.hh"
#include "common/file_base.hh"
#include "common/generic_config.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/life.hh"
#include "common/logger_ref.hh"
#include "common/memento.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/value.hh"
#include "common/yas_enum.hh"
namespace nf7 {
namespace {
class ExprTk final : public nf7::FileBase,
public nf7::GenericConfig, public nf7::DirItem, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<ExprTk> kType = {
"Node/ExprTk", {"nf7::DirItem", "nf7::Node"},
"defines new pure Node using ExprTk"
};
class Lambda;
using Scalar = double;
struct Data {
std::vector<std::string> inputs = {"x"};
std::vector<std::string> outputs = {"out"};
std::string script = "";
Data() noexcept { }
void serialize(auto& ar) {
ar(inputs, outputs, script);
}
std::string Stringify() const noexcept;
void Parse(const std::string&);
void Sanitize() const;
};
ExprTk(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kNone),
nf7::Node(nf7::Node::kCustomNode),
life_(*this), log_(*this), mem_(*this, std::move(data)) {
}
ExprTk(nf7::Deserializer& ar) : ExprTk(ar.env()) {
ar(mem_.data());
mem_->Sanitize();
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(mem_.data());
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<ExprTk>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
return {mem_->inputs, mem_->outputs};
}
void UpdateNode(nf7::Node::Editor&) noexcept override;
File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::Config, nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<ExprTk> life_;
nf7::LoggerRef log_;
nf7::GenericMemento<Data> mem_;
};
class ExprTk::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<ExprTk::Lambda> {
public:
Lambda(ExprTk& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent), f_(f.life_) {
}
void Handle(const nf7::Node::Lambda::Msg& in) noexcept override
try {
f_.EnforceAlive();
ZoneScoped;
vars_.emplace(in.name, in.value);
const auto& inputs = f_->mem_->inputs;
exprtk::symbol_table<Scalar> sym;
// check if satisfied all inputs and serialize input values
std::vector<nf7::Value*> vars;
vars.resize(inputs.size());
for (size_t i = 0; i < inputs.size(); ++i) {
auto itr = vars_.find(inputs[i]);
if (itr != vars_.end()) {
vars[i] = &itr->second;
} else {
return; // abort when required input is not satisfied
}
}
assert(vars.size() == inputs.size());
// assign input values
for (size_t i = 0; i < inputs.size(); ++i) {
vars[i]->Visit(ConstRegisterVisitor {sym, inputs[i]});
}
vars_.clear();
// register system functions
OutputFunction yield_func {in.sender, shared_from_this()};
sym.add_function("yield", yield_func);
// compile the expr
exprtk::parser<Scalar> parser;
exprtk::expression<Scalar> expr;
expr.register_symbol_table(sym);
{
ZoneScopedN("ExprTk compile");
if (!parser.compile(f_->mem_->script, expr)) {
throw nf7::Exception {parser.error()};
}
}
// calculate the expression!
{
ZoneScopedN("ExprTk calc");
expr.value();
}
} catch (nf7::ExpiredException&) {
} catch (nf7::Exception& e) {
f_->log_.Error(e);
}
private:
nf7::Life<ExprTk>::Ref f_;
std::unordered_map<std::string, nf7::Value> vars_;
bool Satisfy() noexcept {
for (const auto& in : f_->mem_->inputs) {
if (vars_.end() == vars_.find(in)) {
return false;
}
}
return true;
}
struct ConstRegisterVisitor final {
public:
ConstRegisterVisitor(exprtk::symbol_table<Scalar>& sym, const std::string& name) noexcept :
sym_(sym), name_(name) {
}
void operator()(nf7::Value::Scalar v) noexcept {
sym_.add_constant(name_, v);
}
// TODO:
void operator()(auto) {
throw nf7::Exception {"unsupported input value type"};
}
private:
exprtk::symbol_table<Scalar>& sym_;
const std::string& name_;
};
struct OutputFunction final : exprtk::igeneric_function<Scalar> {
public:
OutputFunction(const std::shared_ptr<nf7::Node::Lambda>& callee,
const std::shared_ptr<nf7::Node::Lambda>& caller) :
exprtk::igeneric_function<Scalar>("S|ST|SS|SV"),
callee_(callee), caller_(caller) {
}
Scalar operator()(const std::size_t& idx, parameter_list_t params) {
nf7::Value ret;
switch (idx) {
case 0: // pulse
ret = nf7::Value::Pulse {};
break;
case 1: // scalar
ret = {static_cast<nf7::Value::Scalar>(generic_type::scalar_view {params[1]}())};
break;
case 2: { // string
generic_type::string_view v {params[1]};
ret = {std::string {v.begin(), v.size()}};
} break;
case 3: { // vector
generic_type::vector_view v {params[1]};
std::vector<nf7::Value::TuplePair> pairs;
pairs.resize(v.size());
for (size_t i = 0; i < v.size(); ++i) {
pairs[i].second = nf7::Value {static_cast<nf7::Value::Scalar>(v[i])};
}
ret = {std::move(pairs)};
} break;
default:
assert(false);
break;
}
generic_type::string_view n {params[0]};
const std::string name {n.begin(), n.size()};
callee_->env().ExecSub(callee_, [=, *this]() {
callee_->Handle(name, ret, caller_);
});
return 0;
}
private:
std::shared_ptr<nf7::Node::Lambda> callee_, caller_;
};
};
std::shared_ptr<nf7::Node::Lambda> ExprTk::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent);
}
void ExprTk::UpdateNode(nf7::Node::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::TextUnformatted("Node/ExprTk");
ImGui::SameLine();
if (ImGui::SmallButton("config")) {
ImGui::OpenPopup("ConfigPopup");
}
if (ImGui::BeginPopup("ConfigPopup")) {
static gui::ConfigEditor ed;
ed(*this);
ImGui::EndPopup();
}
ImGui::BeginGroup();
for (const auto& in : mem_->inputs) {
if (ImNodes::BeginInputSlot(in.c_str(), 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImGui::SameLine();
ImGui::TextUnformatted(in.c_str());
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
ImGui::SameLine();
ImGui::InputTextMultiline("##script", &mem_->script, ImVec2 {24*em, 8*em});
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_.Commit();
}
ImGui::SameLine();
gui::NodeOutputSockets(mem_->outputs);
}
std::string ExprTk::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 ExprTk::Data::Parse(const std::string& str) {
const auto yaml = YAML::Load(str);
Data d;
d.inputs = yaml["inputs"].as<std::vector<std::string>>();
d.outputs = yaml["outputs"].as<std::vector<std::string>>();
d.script = yaml["script"].as<std::string>();
d.Sanitize();
*this = std::move(d);
}
void ExprTk::Data::Sanitize() const {
nf7::Node::ValidateSockets(inputs);
nf7::Node::ValidateSockets(outputs);
}
}
} // namespace nf7