add Node/ZipTie

This commit is contained in:
falsycat 2022-11-16 14:10:36 +09:00
parent 06400d4ea4
commit 3c2ed1731a
3 changed files with 549 additions and 0 deletions

View File

@ -151,6 +151,7 @@ target_sources(nf7
file/node_network.cc file/node_network.cc
file/node_ref.cc file/node_ref.cc
file/node_singleton.cc file/node_singleton.cc
file/node_ziptie.cc
file/sequencer_adaptor.cc file/sequencer_adaptor.cc
file/sequencer_call.cc file/sequencer_call.cc
file/sequencer_timeline.cc file/sequencer_timeline.cc

View File

@ -27,6 +27,11 @@ class SquashedHistory : public nf7::GenericHistory {
if (staged_.size() == 0) { if (staged_.size() == 0) {
return false; return false;
} }
if (++tick_ <= 2) {
return false;
}
tick_ = 0;
nf7::GenericHistory::Add( nf7::GenericHistory::Add(
std::make_unique<nf7::AggregateCommand>(std::move(staged_))); std::make_unique<nf7::AggregateCommand>(std::move(staged_)));
return true; return true;
@ -48,6 +53,8 @@ class SquashedHistory : public nf7::GenericHistory {
private: private:
std::vector<std::unique_ptr<Command>> staged_; std::vector<std::unique_ptr<Command>> staged_;
uint8_t tick_ = 0;
}; };
} // namespace nf7 } // namespace nf7

541
file/node_ziptie.cc Normal file
View File

@ -0,0 +1,541 @@
#include <algorithm>
#include <cassert>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <typeinfo>
#include <unordered_set>
#include <utility>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ImNodes.h>
#include <magic_enum.hpp>
#include <yas/serialize.hpp>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.hh"
#include "common/gui.hh"
#include "common/life.hh"
#include "common/node.hh"
#include "common/ptr_selector.hh"
#include "common/yas_enum.hh"
namespace nf7 {
class ZipTie final : public nf7::FileBase, public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<ZipTie> kType = {
"Node/ZipTie", {"nf7::Node",},
"[N to 1] or [1 to N] node",
};
static constexpr size_t kMaxN = 64;
static inline const auto kIndexStrings = ([](){
std::vector<std::string> ret(kMaxN);
for (size_t i = 0; i < kMaxN; ++i) ret[i] = std::to_string(i);
return ret;
})();
class Lambda;
enum Algorithm {
// N to 1
kAwait,
kMakeTuple,
kUpdateTuple,
// 1 to N
kOrderedPulse,
kExtractTuple,
};
static bool IsNto1(Algorithm algo) noexcept {
return algo == kAwait || algo == kMakeTuple || algo == kUpdateTuple;
}
static bool IsNameRequired(Algorithm algo) noexcept {
return algo == kMakeTuple || algo == kUpdateTuple || algo == kExtractTuple;
}
struct AlgoMeta final {
std::string name;
std::string desc;
};
static inline const std::unordered_map<Algorithm, AlgoMeta> kAlgoMetas = {
{kAwait, { .name = "await", .desc = "awaits for all inputs satisfied" }},
{kMakeTuple, { .name = "make tuple", .desc = "emits a tuple when all inputs satisfied" }},
{kUpdateTuple, { .name = "update tuple", .desc = "emits a tuple when one input satisfied" }},
{kOrderedPulse, { .name = "ordered pulse", .desc = "emits a pulse in order" }},
{kExtractTuple, { .name = "extract tuple", .desc = "extracts values from a tuple by thier name" }},
};
struct Data {
public:
Data() {}
Algorithm algo = kAwait;
std::vector<std::string> names = {"", ""};
};
ZipTie(nf7::Env& env, Data&& d = {}) noexcept :
nf7::FileBase(kType, env),
nf7::Node(nf7::Node::kCustomNode |
nf7::Node::kMenu),
life_(*this), mem_(*this, std::move(d)) {
}
ZipTie(nf7::Deserializer& ar) : ZipTie(ar.env()) {
ar(mem_.data());
}
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<ZipTie>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
const auto n = mem_->names.size();
std::vector<std::string> index(
kIndexStrings.begin(),
kIndexStrings.begin()+static_cast<intmax_t>(n));
if (IsNto1(mem_->algo)) {
return {std::move(index), {"out"}};
} else {
return {{"in"}, std::move(index)};
}
}
void UpdateNode(nf7::Node::Editor&) noexcept override;
void UpdateMenu(nf7::Node::Editor&) noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<nf7::Memento, nf7::Node>(t).Select(this, &mem_);
}
private:
nf7::Life<ZipTie> life_;
nf7::GenericMemento<Data> mem_;
// socket list manipulation
void InsertSocket(nf7::Node::Editor&, size_t) noexcept;
void RemoveSocket(nf7::Node::Editor&, size_t) noexcept;
void MoveLinks(nf7::Node::Editor&, std::string_view before, std::string_view after) noexcept;
// widgets
bool SocketMenu(nf7::Node::Editor&, size_t) noexcept;
bool AlgorithmComboItem(Algorithm);
};
class ZipTie::Lambda final : public nf7::Node::Lambda,
public std::enable_shared_from_this<Lambda> {
public:
Lambda(ZipTie& 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();
const auto& d = f_->mem_.data();
if (d.algo != std::exchange(prev_algo_, d.algo)) {
values_.clear();
}
if (IsNto1(d.algo)) {
const auto idx = static_cast<size_t>(std::stoul(in.name));
if (idx >= d.names.size()) {
throw nf7::Exception {"index overflow"};
}
values_.resize(d.names.size());
values_[idx] = in.value;
} else {
values_.clear();
}
switch (d.algo) {
case kAwait:
Await(in);
return;
case kMakeTuple:
MakeTuple(in, d);
return;
case kUpdateTuple:
UpdateTuple(in, d);
return;
case kOrderedPulse:
OrderedPulse(in, d);
return;
case kExtractTuple:
ExtractTuple(in, d);
return;
default:
assert(false);
return;
}
} catch (std::invalid_argument&) {
} catch (std::out_of_range&) {
} catch (nf7::ExpiredException&) {
} catch (nf7::Exception&) {
}
private:
nf7::Life<ZipTie>::Ref f_;
std::optional<Algorithm> prev_algo_;
std::vector<std::optional<nf7::Value>> values_;
void Await(const nf7::Node::Lambda::Msg& in) noexcept {
if (AllSatisifed()) {
in.sender->Handle("out", nf7::Value::Pulse {}, shared_from_this());
values_.clear();
}
}
void MakeTuple(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
if (AllSatisifed()) {
UpdateTuple(in, d);
values_.clear();
}
}
void UpdateTuple(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
std::vector<nf7::Value::TuplePair> pairs;
pairs.reserve(d.names.size());
for (size_t i = 0; i < d.names.size(); ++i) {
const auto& name = d.names[i];
if (name == "" || !values_[i]) continue;
pairs.emplace_back(name, *values_[i]);
}
in.sender->Handle("out", std::move(pairs), shared_from_this());
}
void OrderedPulse(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
for (size_t i = 0; i < d.names.size(); ++i) {
in.sender->Handle(kIndexStrings[i], nf7::Value::Pulse {}, shared_from_this());
}
}
void ExtractTuple(const nf7::Node::Lambda::Msg& in, const Data& d) noexcept {
for (size_t i = 0; i < d.names.size(); ++i)
try {
in.sender->Handle(kIndexStrings[i], in.value.tuple(d.names[i]), shared_from_this());
} catch (nf7::Exception&) {
}
}
bool AllSatisifed() const noexcept {
return std::all_of(values_.begin(), values_.end(), [](auto& x) { return !!x; });
}
};
std::shared_ptr<nf7::Node::Lambda> ZipTie::CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
return std::make_shared<Lambda>(*this, parent);
}
void ZipTie::InsertSocket(nf7::Node::Editor& ed, size_t idx) noexcept {
auto& names = mem_->names;
assert(names.size() < kMaxN);
assert(idx <= names.size());
for (size_t i = names.size()-1; i > idx; --i) {
MoveLinks(ed, kIndexStrings[i-1], kIndexStrings[i]);
}
env().ExecMain(nullptr, [&names, idx](){
names.insert(names.begin()+static_cast<intmax_t>(idx), std::string {});
});
}
void ZipTie::RemoveSocket(nf7::Node::Editor& ed, size_t idx) noexcept {
auto& names = mem_->names;
assert(names.size() >= 1);
assert(idx < names.size());
MoveLinks(ed, kIndexStrings[idx], "");
for (size_t i = idx; i < names.size()-1; ++i) {
MoveLinks(ed, kIndexStrings[i+1], kIndexStrings[i]);
}
env().ExecMain(nullptr, [&names, idx](){
names.erase(names.begin() + static_cast<intmax_t>(idx));
});
}
void ZipTie::MoveLinks(
nf7::Node::Editor& ed, std::string_view before, std::string_view after) noexcept {
const bool self_src = !IsNto1(mem_->algo);
const auto others =
self_src? ed.GetDstOf(*this, before): ed.GetSrcOf(*this, before);
for (const auto& other_ref : others) {
using P = std::pair<nf7::Node*, std::string_view>;
P self = {this, before};
P other = {other_ref.first, other_ref.second};
// remove existing link
{
auto src = &self, dst = &other;
if (!self_src) std::swap(src, dst);
ed.RemoveLink(*src->first, src->second, *dst->first, dst->second);
}
// add removed link
self.second = after;
if (after != "") {
auto src = &self, dst = &other;
if (!self_src) std::swap(src, dst);
ed.AddLink(*src->first, src->second, *dst->first, dst->second);
}
}
}
void ZipTie::UpdateNode(nf7::Node::Editor& ed) noexcept {
const auto em = ImGui::GetFontSize();
auto meta_itr = kAlgoMetas.find(mem_->algo);
assert(meta_itr != kAlgoMetas.end());
const auto& meta = meta_itr->second;
bool mod = false;
ImGui::TextUnformatted("Node/ZipTie");
ImGui::SameLine();
const auto right_top = ImGui::GetCursorPos();
ImGui::NewLine();
const auto left_top = ImGui::GetCursorPos();
ImGui::AlignTextToFramePadding();
ImGui::NewLine();
// inputs
ImGui::BeginGroup();
if (IsNto1(mem_->algo)) {
for (size_t i = 0; i < mem_->names.size(); ++i) {
if (ImNodes::BeginInputSlot(kIndexStrings[i].c_str(), 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
if (ImGui::BeginPopupContextItem()) {
mod |= SocketMenu(ed, i);
ImGui::EndPopup();
}
}
} else {
if (ImNodes::BeginInputSlot("in", 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
}
ImGui::EndGroup();
// text input
ImGui::SameLine();
ImGui::BeginGroup();
for (size_t i = 0; i < mem_->names.size(); ++i) {
ImGui::AlignTextToFramePadding();
if (!IsNto1(mem_->algo)) {
ImGui::TextUnformatted("->");
ImGui::SameLine();
}
if (IsNameRequired(mem_->algo)) {
ImGui::SetNextItemWidth(6*em);
const auto id = "##text"+kIndexStrings[i];
ImGui::InputText(id.c_str(), &mem_->names[i]);
if (ImGui::IsItemDeactivatedAfterEdit()) {
mod = true;
}
if (ImGui::BeginPopupContextItem()) {
mod |= SocketMenu(ed, i);
ImGui::EndPopup();
}
} else {
ImGui::Text("%zu", i);
}
if (IsNto1(mem_->algo)) {
ImGui::SameLine();
ImGui::TextUnformatted("->");
}
}
ImGui::EndGroup();
// outputs
ImGui::SameLine();
ImGui::BeginGroup();
if (IsNto1(mem_->algo)) {
if (ImNodes::BeginOutputSlot("out", 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
} else {
for (size_t i = 0; i < mem_->names.size(); ++i) {
if (ImNodes::BeginOutputSlot(kIndexStrings[i].c_str(), 1)) {
ImGui::AlignTextToFramePadding();
gui::NodeSocket();
ImNodes::EndSlot();
}
if (ImGui::BeginPopupContextItem()) {
mod |= SocketMenu(ed, i);
ImGui::EndPopup();
}
}
}
ImGui::EndGroup();
ImGui::SameLine();
const auto right_bottom = ImGui::GetCursorPos();
ImGui::NewLine();
// algorithm selection
ImGui::SetCursorPos(left_top);
auto w = std::max(right_bottom.x, right_top.x) - left_top.x;
ImGui::Button(meta.name.c_str(), {w, 0});
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft)) {
ImGui::TextDisabled("N to 1");
mod |= AlgorithmComboItem(kAwait);
mod |= AlgorithmComboItem(kMakeTuple);
mod |= AlgorithmComboItem(kUpdateTuple);
ImGui::TextDisabled("1 to N");
mod |= AlgorithmComboItem(kOrderedPulse);
mod |= AlgorithmComboItem(kExtractTuple);
ImGui::EndPopup();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("choose algorithm");
}
// commit changes
if (mod) {
env().ExecMain(
std::make_shared<nf7::GenericContext>(*this, "memento commit"),
[this]() { mem_.Commit(); });
}
}
void ZipTie::UpdateMenu(nf7::Node::Editor&) noexcept {
if (ImGui::BeginMenu("config")) {
static int n;
if (ImGui::IsWindowAppearing()) {
n = static_cast<int>(mem_->names.size());
}
ImGui::PushItemWidth(6*ImGui::GetFontSize());
ImGui::DragInt("sockets", &n, 0.25f, 1, static_cast<int>(kMaxN));
if (ImGui::IsItemDeactivatedAfterEdit()) {
mem_->names.resize(static_cast<size_t>(n));
mem_.Commit();
}
ImGui::PopItemWidth();
ImGui::EndMenu();
}
}
bool ZipTie::SocketMenu(nf7::Node::Editor& ed, size_t i) noexcept {
bool mod = false;
ImGui::BeginDisabled(mem_->names.size() >= kMaxN);
if (ImGui::MenuItem("insert before")) {
InsertSocket(ed, i);
mod = true;
}
if (ImGui::MenuItem("insert after")) {
InsertSocket(ed, i+1);
mod = true;
}
ImGui::EndDisabled();
ImGui::BeginDisabled(mem_->names.size() == 1);
if (ImGui::MenuItem("remove")) {
RemoveSocket(ed, i);
mod = true;
}
ImGui::EndDisabled();
return mod;
}
bool ZipTie::AlgorithmComboItem(Algorithm algo) {
bool mod = false;
auto itr = kAlgoMetas.find(algo);
assert(itr != kAlgoMetas.end());
const auto& meta = itr->second;
if (ImGui::Selectable(meta.name.c_str(), mem_->algo == algo)) {
if (mem_->algo != algo) {
mem_->algo = algo;
mod = true;
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", meta.desc.c_str());
}
return mod;
}
} // namespace nf7
namespace yas::detail {
NF7_YAS_DEFINE_ENUM_SERIALIZER(nf7::ZipTie::Algorithm);
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
nf7::ZipTie::Data> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const nf7::ZipTie::Data& d) {
ar(d.algo);
if (nf7::ZipTie::IsNameRequired(d.algo)) {
ar(d.names);
} else {
ar(d.names.size());
}
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, nf7::ZipTie::Data& d) {
ar(d.algo);
if (nf7::ZipTie::IsNameRequired(d.algo)) {
ar(d.names);
} else {
size_t n;
ar(n);
d.names.clear();
d.names.resize(n);
}
if (d.names.size() > nf7::ZipTie::kMaxN) {
throw nf7::DeserializeException {"Node/ZipTie maximum socket count exceeded"};
}
if (d.names.size() == 0) {
d.names.resize(1);
}
return ar;
}
};
} // namespace yas::detail