#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nf7.hh" #include "common/dir_item.hh" #include "common/file_base.hh" #include "common/generic_context.hh" #include "common/generic_memento.hh" #include "common/generic_type_info.hh" #include "common/gui_context.hh" #include "common/gui_file.hh" #include "common/gui_node.hh" #include "common/gui_popup.hh" #include "common/gui_window.hh" #include "common/life.hh" #include "common/memento.hh" #include "common/memento_recorder.hh" #include "common/node.hh" #include "common/node_link_store.hh" #include "common/ptr_selector.hh" #include "common/squashed_history.hh" #include "common/util_algorithm.hh" #include "common/yas_imgui.hh" #include "common/yas_imnodes.hh" #include "common/yas_nf7.hh" using namespace std::literals; namespace nf7 { namespace { class Network final : public nf7::FileBase, public nf7::DirItem, public nf7::Node { public: static inline const GenericTypeInfo kType = { "Node/Network", {"nf7::DirItem", "nf7::Node"}}; static void UpdateTypeTooltip() noexcept { ImGui::TextUnformatted("A Node composed of multiple child Nodes, whose sockets are linked to each other"); ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node"); ImGui::Bullet(); ImGui::TextUnformatted( "connection changes will be applied to active lambdas immediately"); } class InternalNode; class Item; class Lambda; class Editor; class SocketSwapCommand; // special Node types class Initiator; class Terminal; using ItemId = uint64_t; using ItemList = std::vector>; Network(nf7::Env& env, const gui::Window* win = nullptr, ItemList&& items = {}, nf7::NodeLinkStore&& links = {}) : nf7::FileBase(kType, env, {&add_popup_, &socket_popup_}), nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kTooltip | nf7::DirItem::kWidget), nf7::Node(nf7::Node::kCustomNode), life_(*this), win_(*this, "Editor Node/Network", win), items_(std::move(items)), links_(std::move(links)), add_popup_(*this) { socket_popup_.onSubmit = [this](auto&& i, auto&& o) { ExecSwapSocket(std::move(i), std::move(o)); }; Sanitize(); } ~Network() noexcept { history_.Clear(); } Network(nf7::Deserializer& ar) : Network(ar.env()) { ar(win_, links_, canvas_, inputs_, outputs_, items_); Sanitize(); } void Serialize(nf7::Serializer& ar) const noexcept override { ar(win_, links_, canvas_, inputs_, outputs_, items_); } std::unique_ptr Clone(nf7::Env& env) const noexcept override { ItemList items; items.reserve(items_.size()); for (const auto& item : items_) { items.push_back(std::make_unique(env, *item)); } return std::make_unique( env, &win_, std::move(items), NodeLinkStore(links_)); } File* Find(std::string_view name) const noexcept; void Handle(const Event& ev) noexcept override; void Update() noexcept override; void UpdateMenu() noexcept override; void UpdateTooltip() noexcept override; void UpdateWidget() noexcept override; void UpdateMenu(nf7::Node::Editor&) noexcept override { UpdateMenu(); } void UpdateNode(nf7::Node::Editor&) noexcept override; std::shared_ptr CreateLambda( const std::shared_ptr&) noexcept override; std::span GetInputs() const noexcept override { return inputs_; } std::span GetOutputs() const noexcept override { return outputs_; } File::Interface* interface(const std::type_info& t) noexcept override { return InterfaceSelector(t).Select(this); } private: nf7::Life life_; ItemId next_ = 1; nf7::SquashedHistory history_; std::unordered_map item_map_; std::unordered_map node_map_; std::shared_ptr lambda_; std::vector> lambdas_running_; // persistent params gui::Window win_; std::vector> items_; NodeLinkStore links_; ImNodes::CanvasState canvas_; std::vector inputs_, outputs_; // GUI popup nf7::gui::IOSocketListPopup socket_popup_; class AddPopup final : public nf7::FileBase::Feature, private nf7::gui::Popup { public: static bool TypeFilter(const nf7::File::TypeInfo& t) noexcept { return t.flags().contains("nf7::Node") || t.name().find("Node/Network/") == 0; } AddPopup(Network& owner) noexcept : nf7::gui::Popup("AddPopup"), owner_(&owner), factory_(owner, TypeFilter) { } void Open(const ImVec2& pos) noexcept; void Update() noexcept override; private: Network* const owner_; nf7::gui::FileFactory factory_; ImVec2 pos_; } add_popup_; // initialization void Sanitize(); void AttachLambda(const std::shared_ptr&) noexcept; // history operation void UnDo() { env().ExecMain( std::make_shared(*this, "reverting command to undo"), [this]() { history_.UnDo(); Touch(); }); } void ReDo() { env().ExecMain( std::make_shared(*this, "applying command to redo"), [this]() { history_.ReDo(); Touch(); }); } // IO socket operation void ExecSwapSocket(std::vector&&, std::vector&&) noexcept; // item operation void ExecAddItem(std::unique_ptr&&) noexcept; void ExecAddItem(std::unique_ptr&& item, const ImVec2& pos) noexcept; void ExecRemoveItem(ItemId) noexcept; // link operation void ExecLink(nf7::NodeLinkStore::Link&& lk) noexcept { history_. Add(nf7::NodeLinkStore::SwapCommand::CreateToAdd(links_, std::move(lk))). ExecApply(std::make_shared(*this, "adding new link")); } void ExecUnlink(const nf7::NodeLinkStore::Link& lk) noexcept { history_. Add(nf7::NodeLinkStore::SwapCommand:: CreateToRemove(links_, nf7::NodeLinkStore::Link {lk})). ExecApply(std::make_shared(*this, "removing link")); } // accessors Item& GetItem(ItemId id) const { auto itr = item_map_.find(id); if (itr == item_map_.end()) { throw Exception("missing item ("+std::to_string(id)+")"); } return *itr->second; } Item& GetItem(const Node& node) const { auto itr = node_map_.find(&node); if (itr == node_map_.end()) { throw Exception("missing item"); } return *itr->second; } }; // InternalNode is an interface which provides additional parameters. class Network::InternalNode : public nf7::File::Interface { public: enum Flag : uint8_t { kNone = 0, kInputHandler = 1 << 0, // receives all input from outer kOutputEmitter = 1 << 1, // all output is transmitted to outer }; using Flags = uint8_t; InternalNode() = default; InternalNode(const InternalNode&) = delete; InternalNode(InternalNode&&) = delete; InternalNode& operator=(const InternalNode&) = delete; InternalNode& operator=(InternalNode&&) = delete; virtual Flags flags() const noexcept = 0; }; // Item holds an entity of File, and its watcher // to manage a Node owned by Node/Network. class Network::Item final { public: class SwapCommand; class MoveCommand; Item(ItemId id, std::unique_ptr&& file) : id_(id), file_(std::move(file)) { Initialize(); } Item(nf7::Env& env, const Item& src) noexcept : id_(src.id_), file_(src.file_->Clone(env)), pos_(src.pos_), select_(src.select_) { Initialize(); } Item(Item&&) = delete; Item& operator=(const Item&) = delete; Item& operator=(Item&&) = delete; explicit Item(Deserializer& ar) try { ar(id_, pos_, select_, file_); Initialize(); } catch (std::exception&) { throw DeserializeException("failed to deserialize Node/Network item"); } void Serialize(Serializer& ar) { ar(id_, pos_, select_, file_); } void Attach(Network& owner) noexcept; void Detach() noexcept; void Update() noexcept { assert(owner_); ImGui::PushID(file_.get()); file_->Update(); ImGui::PopID(); } void UpdateNode(Node::Editor&) noexcept; ItemId id() const noexcept { return id_; } File::Id fileId() const noexcept { return file_->id(); } nf7::Env& env() const noexcept { return file_->env(); } nf7::File& file() const noexcept { return *file_; } nf7::Node& node() const noexcept { return *node_; } InternalNode* inode() const noexcept { return inode_; } InternalNode::Flags iflags() const noexcept { return inode_? inode_->flags(): 0; } private: ItemId id_; std::unique_ptr file_; nf7::Node* node_; InternalNode* inode_; std::optional mem_; Network* owner_ = nullptr; ImVec2 prev_pos_; ImVec2 pos_; bool select_; class Watcher final : private nf7::Env::Watcher { public: Watcher(Item& owner) noexcept : nf7::Env::Watcher(owner.env()), owner_(&owner) { assert(owner.fileId()); Watch(owner.fileId()); } void Handle(const nf7::File::Event&) noexcept override; private: Item* const owner_; }; std::optional watcher_; void Initialize() { node_ = &file_->interfaceOrThrow(); mem_.emplace(file_->interface()); inode_ = file_->interface(); prev_pos_ = pos_; } }; // Builds and holds network information independently from Node/Network. // When it receives an input from outside or an output from Nodes in the network, // propagates it to appropriate Nodes. class Network::Lambda : public Node::Lambda, public std::enable_shared_from_this { public: Lambda(Network& f, const std::shared_ptr& parent = nullptr) noexcept : Node::Lambda(f, parent), f_(f.life_) { } void Handle(std::string_view name, const Value& v, const std::shared_ptr& caller) noexcept override { env().ExecSub(shared_from_this(), [this, name = std::string(name), v, caller]() mutable { if (abort_) return; f_.EnforceAlive(); auto parent = this->parent(); // send input from outer to input handlers if (caller == parent) { for (auto& item : f_->items_) { if (item->iflags() & InternalNode::kInputHandler) { auto la = FindOrCreateLambda(item->id()); la->Handle(name, v, shared_from_this()); } } return; } // send an output from children as input to children try { auto itr = idmap_.find(caller.get()); if (itr == idmap_.end()) { throw nf7::Exception {"called by unknown lambda"}; } const auto src_id = itr->second; const auto& src_item = f_->GetItem(src_id); const auto& src_name = name; if (parent && src_item.iflags() & InternalNode::kOutputEmitter) { parent->Handle(src_name, v, shared_from_this()); } for (auto& lk : f_->links_.items()) { if (lk.src_id == src_id && lk.src_name == src_name) { try { const auto& dst_name = lk.dst_name; const auto dst_la = FindOrCreateLambda(lk.dst_id); dst_la->Handle(dst_name, v, shared_from_this()); } catch (nf7::Exception&) { // ignore missing socket } } } } catch (nf7::Exception&) { } }); } // Ensure that the Network is alive before calling const std::shared_ptr& FindOrCreateLambda(ItemId id) noexcept try { return FindLambda(id); } catch (nf7::Exception&) { return CreateLambda(f_->GetItem(id)); } const std::shared_ptr& FindOrCreateLambda(const Item& item) noexcept try { return FindLambda(item.id()); } catch (nf7::Exception&) { return CreateLambda(item); } const std::shared_ptr& CreateLambda(const Item& item) noexcept { auto la = item.node().CreateLambda(shared_from_this()); idmap_[la.get()] = item.id(); auto [itr, added] = lamap_.emplace(item.id(), std::move(la)); return itr->second; } const std::shared_ptr& FindLambda(ItemId id) { auto itr = lamap_.find(id); if (itr == lamap_.end()) { throw nf7::Exception {"lambda is not registered"}; } return itr->second; } void CleanUp() noexcept override { } void Abort() noexcept override { abort_ = true; for (auto& p : lamap_) { p.second->Abort(); } } size_t GetMemoryUsage() const noexcept override { return 0; } std::string GetDescription() const noexcept override { return "executing Node/Network"; } private: nf7::Life::Ref f_; std::unordered_map> lamap_; std::unordered_map idmap_; bool abort_ = false; }; void Network::AttachLambda(const std::shared_ptr& la) noexcept { if (lambda_ && lambda_->depth() == 0) { lambda_->Abort(); } lambda_ = la; } // An generic implementation of Node::Editor for Node/Network. class Network::Editor final : public nf7::Node::Editor { public: Editor(Network& owner) noexcept : owner_(&owner) { } void Emit(Node& node, std::string_view name, nf7::Value&& v) noexcept override { const auto main = lambda(); const auto sub = GetLambda(node); owner_->env().ExecSub(main, [main, sub, name = std::string(name), v = std::move(v)]() { sub->Handle(name, v, main); }); } std::shared_ptr GetLambda(Node& node) noexcept override { try { const auto& la = lambda()->FindOrCreateLambda(owner_->GetItem(node)); assert(la); return la; } catch (nf7::Exception&) { return nullptr; } } void AddLink(Node& src_node, std::string_view src_name, Node& dst_node, std::string_view dst_name) noexcept override try { auto lk = NodeLinkStore::Link { .src_id = owner_->GetItem(src_node).id(), .src_name = std::string {src_name}, .dst_id = owner_->GetItem(dst_node).id(), .dst_name = std::string {dst_name}, }; auto cmd = NodeLinkStore::SwapCommand::CreateToAdd(owner_->links_, std::move(lk)); auto ctx = std::make_shared(*owner_, "adding node link"); owner_->history_.Add(std::move(cmd)).ExecApply(ctx); } catch (Exception&) { } void RemoveLink(Node& src_node, std::string_view src_name, Node& dst_node, std::string_view dst_name) noexcept override try { auto lk = NodeLinkStore::Link { .src_id = owner_->GetItem(src_node).id(), .src_name = std::string {src_name}, .dst_id = owner_->GetItem(dst_node).id(), .dst_name = std::string {dst_name}, }; auto cmd = NodeLinkStore::SwapCommand::CreateToRemove(owner_->links_, std::move(lk)); auto ctx = std::make_shared(*owner_, "removing node links"); owner_->history_.Add(std::move(cmd)).ExecApply(ctx); } catch (Exception&) { } std::vector> GetSrcOf( Node& dst_node, std::string_view dst_name) const noexcept override try { const auto dst_id = owner_->GetItem(dst_node).id(); std::vector> ret; for (const auto& lk : owner_->links_.items()) { if (lk.dst_id != dst_id || lk.dst_name != dst_name) continue; try { ret.emplace_back(&owner_->GetItem(lk.src_id).node(), lk.src_name); } catch (Exception&) { } } return ret; } catch (Exception&) { return {}; } std::vector> GetDstOf( Node& src_node, std::string_view src_name) const noexcept try { const auto src_id = owner_->GetItem(src_node).id(); std::vector> ret; for (const auto& lk : owner_->links_.items()) { if (lk.src_id != src_id || lk.src_name != src_name) continue; try { ret.emplace_back(&owner_->GetItem(lk.dst_id).node(), lk.dst_name); } catch (Exception&) { } } return ret; } catch (Exception&) { return {}; } const std::shared_ptr& lambda() const noexcept { if (!owner_->lambda_) { owner_->lambda_ = std::make_shared(*owner_); } return owner_->lambda_; } private: Network* const owner_; }; // A command that add or remove a Node socket. class Network::SocketSwapCommand final : public nf7::History::Command { public: struct Pair { std::vector in, out; }; SocketSwapCommand(Network& owner, Pair&& p) noexcept : owner_(&owner), pair_(std::move(p)) { } void Apply() noexcept override { Exec(); } void Revert() noexcept override { Exec(); } private: Network* const owner_; Pair pair_; void Exec() noexcept { std::swap(owner_->inputs_, pair_.in); std::swap(owner_->outputs_, pair_.out); } }; void Network::ExecSwapSocket(std::vector&& i, std::vector&& o) noexcept { auto cmd = std::make_unique( *this, SocketSwapCommand::Pair {std::move(i), std::move(o)}); auto ctx = std::make_shared(*this); history_.Add(std::move(cmd)).ExecApply(ctx); } // A command that add or remove a Node. class Network::Item::SwapCommand final : public nf7::History::Command { public: SwapCommand(Network& owner, std::unique_ptr&& item) noexcept : owner_(&owner), id_(item->id()), item_(std::move(item)) { } SwapCommand(Network& owner, ItemId id) noexcept : owner_(&owner), id_(id) { } void Apply() override { Exec(); } void Revert() override { Exec(); } private: Network* const owner_; ItemId id_; std::unique_ptr item_; void Exec() { if (item_) { if (owner_->item_map_.find(id_) == owner_->item_map_.end()) { auto ptr = item_.get(); owner_->items_.push_back(std::move(item_)); if (owner_->id()) ptr->Attach(*owner_); } else { throw nf7::History::CorruptException( "Item::SwapCommand corruption: id duplication in adding item"); } } else { auto itr = std::find_if(owner_->items_.begin(), owner_->items_.end(), [this](auto& x) { return x->id() == id_; }); if (itr == owner_->items_.end()) { throw nf7::History::CorruptException( "Item::SwapCommand corruption: missing removal item"); } (*itr)->Detach(); item_ = std::move(*itr); owner_->items_.erase(itr); } } }; void Network::ExecAddItem(std::unique_ptr&& item) noexcept { history_. Add(std::make_unique(*this, std::move(item))). ExecApply(std::make_shared(*this, "adding new item")); } void Network::ExecRemoveItem(Network::ItemId id) noexcept { auto ctx = std::make_shared(*this, "removing items"); // remove links connected to the item for (const auto& lk : links_.items()) { if (lk.src_id == id || lk.dst_id == id) { ExecUnlink(lk); } } // do remove history_. Add(std::make_unique(*this, id)).ExecApply(ctx); } // A command that moves displayed position of a Node on Node/Network. class Network::Item::MoveCommand final : public nf7::History::Command { public: MoveCommand(Network::Item& item, const ImVec2& pos) noexcept : target_(&item), pos_(pos) { } void Apply() noexcept override { Exec(); } void Revert() noexcept override { Exec(); } private: Network::Item* const target_; ImVec2 pos_; void Exec() noexcept { std::swap(target_->pos_, pos_); target_->prev_pos_ = target_->pos_; } }; void Network::ExecAddItem( std::unique_ptr&& item, const ImVec2& pos) noexcept { auto ctx = std::make_shared(*this, "adding new item"); auto& ref = *item; history_. Add(std::make_unique(*this, std::move(item))). ExecApply(ctx); history_. Add(std::make_unique(ref, pos)). ExecApply(ctx); } // Node that emits a pulse when Network lambda receives the first input. class Network::Initiator final : public nf7::File, public nf7::Node, public Network::InternalNode { public: static inline const nf7::GenericTypeInfo kType = { "Node/Network/Initiator", {}}; static void UpdateTypeTooltip() noexcept { ImGui::TextUnformatted( "Emits a pulse immediately when Node/Network gets the first input."); ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node"); } Initiator(nf7::Env& env) noexcept : nf7::File(kType, env), nf7::Node(nf7::Node::kCustomNode) { } Initiator(nf7::Deserializer& ar) : Initiator(ar.env()) { } void Serialize(nf7::Serializer&) const noexcept override { } std::unique_ptr Clone(nf7::Env& env) const noexcept override { return std::make_unique(env); } std::shared_ptr CreateLambda( const std::shared_ptr& parent) noexcept override { class Emitter final : public Node::Lambda, public std::enable_shared_from_this { public: using Node::Lambda::Lambda; void Handle(std::string_view name, const Value&, const std::shared_ptr& caller) noexcept override { if (name == "_force" || !std::exchange(done_, true)) { caller->Handle("out", nf7::Value::Pulse {}, shared_from_this()); } } private: bool done_ = false; }; return std::make_shared(*this, parent); } std::span GetInputs() const noexcept { return {}; } std::span GetOutputs() const noexcept { static const std::vector kOutputs = {"out"}; return kOutputs; } void UpdateNode(nf7::Node::Editor& ed) noexcept override; InternalNode::Flags flags() const noexcept override { return InternalNode::kInputHandler; } nf7::File::Interface* interface(const std::type_info& t) noexcept { return nf7::InterfaceSelector(t).Select(this); } }; // Node that emits/receives input or output. class Network::Terminal : public nf7::File, public nf7::Node, public Network::InternalNode { public: static inline const nf7::GenericTypeInfo kType = { "Node/Network/Terminal", {}}; enum Type { kInput, kOutput, }; struct Data { Type type; std::string name; }; Terminal(nf7::Env& env, Data&& data = {}) noexcept : nf7::File(kType, env), nf7::Node(nf7::Node::kCustomNode), life_(*this), mem_(std::move(data), *this) { } Terminal(nf7::Deserializer& ar) : Terminal(ar.env()) { ar(data().type, data().name); } void Serialize(nf7::Serializer& ar) const noexcept override { ar(data().type, data().name); } 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& parent) noexcept override { return std::make_shared(*this, parent); } std::span GetInputs() const noexcept override { if (data().type == kOutput) { static const std::vector kInputs = {"in"}; return kInputs; } return {}; } std::span GetOutputs() const noexcept override { if (data().type == kInput) { static const std::vector kInputs = {"out"}; return kInputs; } return {}; } void UpdateNode(nf7::Node::Editor&) noexcept override; InternalNode::Flags flags() const noexcept override { switch (data().type) { case kInput: return InternalNode::kInputHandler; case kOutput: return InternalNode::kOutputEmitter; default: assert(false); return 0; } } File::Interface* interface(const std::type_info& t) noexcept override { return InterfaceSelector< Network::InternalNode, nf7::Node, nf7::Memento>(t).Select(this, &mem_); } private: nf7::Life life_; nf7::GenericMemento mem_; Data& data() noexcept { return mem_.data(); } const Data& data() const noexcept { return mem_.data(); } Network* owner() noexcept { return dynamic_cast(parent()); } class Emitter final : public nf7::Node::Lambda, public std::enable_shared_from_this { public: Emitter(Terminal& f, const std::shared_ptr& node) noexcept : nf7::Node::Lambda(f, node), f_(f.life_) { } void Handle(std::string_view name, const nf7::Value& v, const std::shared_ptr& caller) noexcept override try { f_.EnforceAlive(); const auto& data = f_->data(); switch (data.type) { case kInput: if (name == data.name) { caller->Handle("out", v, shared_from_this()); } break; case kOutput: if (name == "in") { caller->Handle(data.name, v, shared_from_this()); } break; default: assert(false); break; } } catch (nf7::Exception&) { } private: nf7::Life::Ref f_; }; }; void Network::Sanitize() { // id duplication check and get next id std::unordered_set ids; for (const auto& item : items_) { const auto id = item->id(); if (id == 0) throw Exception("id 0 is invalid"); if (ids.contains(id)) throw Exception("id duplication"); ids.insert(id); next_ = std::max(next_, id+1); } // sanitize IO sockets nf7::util::Uniq(inputs_); for (auto itr = inputs_.begin(); itr < inputs_.end(); ++itr) { try { nf7::File::Path::ValidateTerm(*itr); } catch (nf7::Exception&) { inputs_.erase(itr); } } nf7::util::Uniq(outputs_); for (auto itr = outputs_.begin(); itr < outputs_.end(); ++itr) { try { nf7::File::Path::ValidateTerm(*itr); } catch (nf7::Exception&) { outputs_.erase(itr); } } // remove expired links for (const auto& item : items_) { auto cmd = links_.CreateCommandToRemoveExpired( item->id(), item->node().GetInputs(), item->node().GetOutputs()); if (cmd) { cmd->Apply(); } } } File* Network::Find(std::string_view name) const noexcept try { size_t idx; const auto id = std::stol(std::string(name), &idx); if (idx < name.size()) return nullptr; if (id <= 0) return nullptr; return &GetItem(static_cast(id)).file(); } catch (std::exception&) { return nullptr; } catch (Exception&) { return nullptr; } std::shared_ptr Network::CreateLambda( const std::shared_ptr& parent) noexcept { auto ret = std::make_shared(*this, parent); lambdas_running_.emplace_back(ret); return ret; } void Network::Handle(const Event& ev) noexcept { nf7::FileBase::Handle(ev); switch (ev.type) { case Event::kAdd: for (const auto& item : items_) item->Attach(*this); break; case Event::kRemove: for (const auto& item : items_) item->Detach(); break; case Event::kUpdate: break; case Event::kReqFocus: win_.SetFocus(); break; default: break; } } void Network::Item::Attach(Network& owner) noexcept { assert(!owner_); assert(owner.id()); owner_= &owner; auto [item_itr, item_inserted] = owner_->item_map_.emplace(id_, this); assert(item_inserted); (void) item_inserted; auto [node_itr, node_inserted] = owner_->node_map_.emplace(node_, this); assert(node_inserted); (void) node_inserted; file_->MoveUnder(owner, std::to_string(id_)); watcher_.emplace(*this); } void Network::Item::Detach() noexcept { assert(owner_); owner_->item_map_.erase(id_); owner_->node_map_.erase(node_); owner_ = nullptr; watcher_ = std::nullopt; file_->Isolate(); } void Network::Item::Watcher::Handle(const File::Event& ev) noexcept { auto& item = *owner_; auto& node = item.node(); switch (ev.type) { case File::Event::kUpdate: if (item.owner_) { auto& net = *item.owner_; net.Touch(); const auto inputs = node.GetInputs(); const auto outputs = node.GetOutputs(); // check expired sockets if (auto cmd = net.links_.CreateCommandToRemoveExpired(item.id(), inputs, outputs)) { auto ctx = std::make_shared(net, "removing expired node links"); net.history_.Add(std::move(cmd)).ExecApply(ctx); } // tag change history if (auto cmd = item.mem_->CreateCommandIf()) { net.history_.Add(std::move(cmd)); } } return; default: return; } } void Network::Update() noexcept { nf7::FileBase::Update(); const auto em = ImGui::GetFontSize(); // forget expired lambdas lambdas_running_.erase( std::remove_if(lambdas_running_.begin(), lambdas_running_.end(), [](auto& x) { return x.expired(); }), lambdas_running_.end()); // update children for (const auto& item : items_) { item->Update(); } // ---- editor window if (win_.shownInCurrentFrame()) { ImGui::SetNextWindowSize({36*em, 36*em}, ImGuiCond_FirstUseEver); } if (win_.Begin()) { // ---- editor window / toolbar ImGui::BeginGroup(); { // ---- editor window / toolbar / attached lambda combo const auto current_lambda = !lambda_? "(unselected)"s: lambda_->depth() == 0? "(isolated)"s: nf7::gui::GetContextDisplayName(*lambda_); if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) { if (lambda_) { if (ImGui::Selectable("detach current lambda")) { AttachLambda(nullptr); } ImGui::Separator(); } for (const auto& wptr : lambdas_running_) { auto ptr = wptr.lock(); if (!ptr) continue; const auto name = nf7::gui::GetContextDisplayName(*ptr); if (ImGui::Selectable(name.c_str(), ptr == lambda_)) { AttachLambda(nullptr); lambda_ = ptr; } if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::TextUnformatted("call stack:"); ImGui::Indent(); nf7::gui::ContextStack(*ptr); ImGui::Unindent(); ImGui::EndTooltip(); } } if (lambdas_running_.size() == 0) { ImGui::TextDisabled("no running lambda found..."); } ImGui::EndCombo(); } } ImGui::EndGroup(); // ---- editor window / canvas if (ImGui::BeginChild("canvas", {0, 0}, false, ImGuiWindowFlags_NoMove)) { const auto canvas_pos = ImGui::GetCursorScreenPos(); ImNodes::BeginCanvas(&canvas_); // update child nodes auto ed = Network::Editor {*this}; for (const auto& item : items_) { item->UpdateNode(ed); } // handle existing links for (const auto& lk : links_.items()) { const auto src_id = reinterpret_cast(lk.src_id); const auto dst_id = reinterpret_cast(lk.dst_id); const auto src_name = lk.src_name.c_str(); const auto dst_name = lk.dst_name.c_str(); if (!ImNodes::Connection(dst_id, dst_name, src_id, src_name)) { ExecUnlink(lk); } } // handle new link void* src_ptr; const char* src_name; void* dst_ptr; const char* dst_name; if (ImNodes::GetNewConnection(&dst_ptr, &dst_name, &src_ptr, &src_name)) { ExecLink({ .src_id = reinterpret_cast(src_ptr), .src_name = src_name, .dst_id = reinterpret_cast(dst_ptr), .dst_name = dst_name, }); } ImNodes::EndCanvas(); // popup menu for canvas constexpr auto kFlags = ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverExistingPopup; if (ImGui::BeginPopupContextWindow(nullptr, kFlags)) { const auto mouse = ImGui::GetMousePosOnOpeningCurrentPopup(); if (ImGui::MenuItem("add")) { const auto pos = mouse - canvas_pos - canvas_.Offset/canvas_.Zoom; add_popup_.Open(pos); } ImGui::Separator(); if (ImGui::MenuItem("undo", nullptr, false, !!history_.prev())) { UnDo(); } if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) { ReDo(); } ImGui::Separator(); if (ImGui::MenuItem("reset canvas zoom")) { canvas_.Zoom = 1.f; } ImGui::Separator(); if (ImGui::MenuItem("I/O socket list")) { socket_popup_.Open(inputs_, outputs_); } ImGui::EndPopup(); } } ImGui::EndChild(); } win_.End(); // squash queued commands if (history_.Squash()) { Touch(); } } void Network::UpdateMenu() noexcept { if (ImGui::MenuItem("Editor", nullptr, &win_.shown()) && win_.shown()) { win_.SetFocus(); } if (ImGui::MenuItem("I/O sockets")) { socket_popup_.Open(inputs_, outputs_); } } void Network::UpdateTooltip() noexcept { ImGui::Text("nodes active: %zu", items_.size()); } void Network::UpdateWidget() noexcept { ImGui::TextUnformatted("Node/Network"); if (ImGui::Button("open editor")) { win_.SetFocus(); } if (ImGui::Button("I/O sockets")) { socket_popup_.Open(inputs_, outputs_); } socket_popup_.Update(); } void Network::UpdateNode(nf7::Node::Editor&) noexcept { ImGui::TextUnformatted("Node/Network"); ImGui::BeginGroup(); nf7::gui::NodeInputSockets(inputs_); ImGui::SameLine(); nf7::gui::NodeOutputSockets(outputs_); ImGui::EndGroup(); if (ImGui::Button("open editor")) { win_.SetFocus(); } if (ImGui::Button("I/O sockets")) { socket_popup_.Open(inputs_, outputs_); } socket_popup_.Update(); } void Network::Item::UpdateNode(Node::Editor& ed) noexcept { assert(owner_); ImGui::PushID(node_); const auto id = reinterpret_cast(id_); if (ImNodes::BeginNode(id, &pos_, &select_)) { if (node_->flags() & nf7::Node::kCustomNode) { node_->UpdateNode(ed); } else { ImGui::TextUnformatted(file_->type().name().c_str()); nf7::gui::NodeInputSockets(node_->GetInputs()); ImGui::SameLine(); nf7::gui::NodeOutputSockets(node_->GetOutputs()); } } ImNodes::EndNode(); const bool moved = pos_.x != prev_pos_.x || pos_.y != prev_pos_.y; if (moved && !ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { owner_->history_.Add(std::make_unique(*this, prev_pos_)); prev_pos_ = pos_; } constexpr auto kFlags = ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverExistingPopup; if (ImGui::BeginPopupContextItem(nullptr, kFlags)) { if (ImGui::MenuItem("remove")) { owner_->ExecRemoveItem(id_); } if (ImGui::MenuItem("clone")) { owner_->ExecAddItem( std::make_unique(owner_->next_++, file_->Clone(env()))); } if (node_->flags() & nf7::Node::kMenu_DirItem) { ImGui::Separator(); auto dir = file_->interface(); assert(dir); dir->UpdateMenu(); } if (node_->flags() & nf7::Node::kMenu) { ImGui::Separator(); node_->UpdateMenu(ed); } ImGui::EndPopup(); } ImGui::PopID(); } void Network::AddPopup::Open(const ImVec2& pos) noexcept { nf7::gui::Popup::Open(); pos_ = pos; } void Network::AddPopup::Update() noexcept { if (nf7::gui::Popup::Begin()) { ImGui::TextUnformatted("Node/Network: adding new Node..."); if (factory_.Update()) { ImGui::CloseCurrentPopup(); owner_->ExecAddItem( std::make_unique(owner_->next_++, factory_.Create(owner_->env())), pos_); } ImGui::EndPopup(); } } void Network::Initiator::UpdateNode(nf7::Node::Editor& ed) noexcept { ImGui::TextUnformatted("INITIATOR"); if (ImGui::Button("PULSE")) { ed.Emit(*this, "_force", nf7::Value::Pulse {}); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("generates a pulse manually on debug context"); } ImGui::SameLine(); if (ImNodes::BeginOutputSlot("out", 1)) { ImGui::AlignTextToFramePadding(); nf7::gui::NodeSocket(); ImNodes::EndSlot(); } } void Network::Terminal::UpdateNode(nf7::Node::Editor&) noexcept { const auto UpdateSelector = [&]() { auto net = owner(); if (!net) { ImGui::TextUnformatted("parent must be Node/Network"); return; } auto& name = data().name; ImGui::SetNextItemWidth(12*ImGui::GetFontSize()); if (ImGui::BeginCombo("##name", name.c_str())) { ImGui::PushID("input"); if (net->inputs_.size() > 0) { ImGui::TextDisabled("inputs"); } else { ImGui::TextDisabled("no input"); } for (const auto& sock : net->inputs_) { if (ImGui::Selectable(sock.c_str())) { if (data().type != kInput || name != sock) { data() = Data {kInput, sock}; mem_.Commit(); } } } ImGui::PopID(); ImGui::Separator(); ImGui::PushID("output"); if (net->outputs_.size() > 0) { ImGui::TextDisabled("outputs"); } else { ImGui::TextDisabled("no output"); } for (const auto& sock : net->outputs_) { if (ImGui::Selectable(sock.c_str())) { if (data().type != kOutput || name != sock) { data() = Data {kOutput, sock}; mem_.Commit(); } } } ImGui::PopID(); ImGui::EndCombo(); } }; ImGui::TextUnformatted("Node/Network/Terminal"); switch (data().type) { case kInput: if (ImNodes::BeginOutputSlot("out", 1)) { UpdateSelector(); ImGui::SameLine(); nf7::gui::NodeSocket(); ImNodes::EndSlot(); } break; case kOutput: if (ImNodes::BeginInputSlot("in", 1)) { ImGui::AlignTextToFramePadding(); nf7::gui::NodeSocket(); ImGui::SameLine(); UpdateSelector(); ImNodes::EndSlot(); } break; default: assert(false); break; } if (auto net = owner()) { const auto& socks = data().type == kInput? net->inputs_: net->outputs_; if (socks.end() == std::find(socks.begin(), socks.end(), data().name)) { ImGui::TextUnformatted("SOCKET MISSING X("); } } } } } // namespace nf7 namespace yas::detail { template struct serializer< type_prop::not_a_fundamental, ser_case::use_internal_serializer, F, std::unique_ptr> { public: template static Archive& save(Archive& ar, const std::unique_ptr& item) { item->Serialize(ar); return ar; } template static Archive& load(Archive& ar, std::unique_ptr& item) { try { item = std::make_unique(ar); } catch (nf7::Exception&) { item = nullptr; ar.env().Throw(std::current_exception()); } return ar; } }; template struct serializer< type_prop::not_a_fundamental, ser_case::use_internal_serializer, F, std::vector>> { public: template static Archive& save(Archive& ar, const std::vector>& v) { ar(static_cast(v.size())); for (auto& item : v) { ar(item); } return ar; } template static Archive& load(Archive& ar, std::vector>& v) { uint64_t size; ar(size); v.resize(size); for (auto& item : v) { ar(item); } v.erase(std::remove(v.begin(), v.end(), nullptr), v.end()); return ar; } }; } // namespace yas::detail