#include "nf7.hh" #include #include #include #include #include #include #include #include #include "common/file_base.hh" #include "common/file_holder.hh" #include "common/generic_context.hh" #include "common/generic_memento.hh" #include "common/generic_type_info.hh" #include "common/gui_file.hh" #include "common/gui_popup.hh" #include "common/life.hh" #include "common/node.hh" #include "common/ptr_selector.hh" #include "common/sequencer.hh" namespace nf7 { namespace { class Call final : public nf7::FileBase, public nf7::Sequencer { public: static inline const nf7::GenericTypeInfo kType = { "Sequencer/Call", {"nf7::Sequencer"}}; static void UpdateTypeTooltip() noexcept { ImGui::TextUnformatted("Calls a Node."); ImGui::Bullet(); ImGui::TextUnformatted( "implements nf7::Sequencer"); ImGui::Bullet(); ImGui::TextUnformatted( "changes will be applied to active lambdas immediately"); } class Lambda; class SessionLambda; struct Data { nf7::FileHolder::Tag callee; std::string expects; bool pure; }; Call(nf7::Env& env, Data&& data = {}) noexcept : FileBase(kType, env, {&callee_, &callee_editor_}), Sequencer(Sequencer::kCustomItem | Sequencer::kTooltip | Sequencer::kParamPanel), life_(*this), callee_(*this, "callee", mem_), callee_editor_(callee_, [](auto& t) { return t.flags().contains("nf7::Node"); }), mem_(std::move(data), *this) { mem_.data().callee.SetTarget(callee_); mem_.CommitAmend(); } Call(nf7::Deserializer& ar) : Call(ar.env()) { ar(callee_, data().expects, data().pure); } void Serialize(nf7::Serializer& ar) const noexcept override { ar(callee_, data().expects, data().pure); } std::unique_ptr Clone(nf7::Env& env) const noexcept override { return std::make_unique(env, Data {data()}); } std::shared_ptr CreateLambda( const std::shared_ptr&) noexcept override; void UpdateItem(nf7::Sequencer::Editor&) noexcept override; void UpdateParamPanel(nf7::Sequencer::Editor&) noexcept override; void UpdateTooltip(nf7::Sequencer::Editor&) noexcept override; nf7::File::Interface* interface(const std::type_info& t) noexcept override { return InterfaceSelector< nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_); } private: nf7::Life life_; nf7::FileHolder callee_; nf7::gui::FileHolderEditor callee_editor_; nf7::GenericMemento mem_; Data& data() noexcept { return mem_.data(); } const Data& data() const noexcept { return mem_.data(); } }; class Call::Lambda final : public nf7::Sequencer::Lambda, public std::enable_shared_from_this { public: Lambda(Call& f, const std::shared_ptr& ctx) noexcept : Sequencer::Lambda(f, ctx), file_(f.life_) { } void Run(const std::shared_ptr& ss) noexcept override; void Abort() noexcept override; private: nf7::Life::Ref file_; std::shared_ptr ssla_; nf7::Node* cached_node_ = nullptr; std::shared_ptr la_; bool abort_ = false; }; class Call::SessionLambda final : public nf7::Node::Lambda { public: SessionLambda(Call& f, const std::shared_ptr& parent) noexcept : nf7::Node::Lambda(f, parent) { } void Listen(Call& f, const std::shared_ptr& ss) noexcept { assert(!ss_); ss_ = ss; const auto ex = f.data().expects; size_t begin = 0; for (size_t i = 0; i <= ex.size(); ++i) { if (i == ex.size() || ex[i] == '\n') { auto name = ex.substr(begin, i-begin); if (name.size() > 0) { expects_.insert(std::move(name)); } begin = i+1; } } FinishIf(); } void Handle(const nf7::Node::Lambda::Msg& in) noexcept override { if (!ss_) return; ss_->Send(in.name, nf7::Value {in.value}); expects_.erase(in.name); FinishIf(); } void Abort() noexcept override { if (ss_) { ss_->Finish(); ss_ = nullptr; expects_.clear(); } } private: std::shared_ptr ss_; std::unordered_set expects_; void FinishIf() noexcept { if (expects_.size() == 0) { ss_->Finish(); ss_ = nullptr; } } }; std::shared_ptr Call::CreateLambda( const std::shared_ptr& parent) noexcept { return std::make_shared(*this, parent); } void Call::Lambda::Run(const std::shared_ptr& ss) noexcept try { if (abort_) return; file_.EnforceAlive(); auto& data = file_->data(); auto& callee = file_->callee_.GetFileOrThrow(); auto& node = callee.interfaceOrThrow(); if (!ssla_) { ssla_ = std::make_shared(*file_, shared_from_this()); } auto self = shared_from_this(); if (!la_ || &node != std::exchange(cached_node_, &node)) { la_ = node.CreateLambda(ssla_); } ssla_->Listen(*file_, ss); for (const auto& name : node.GetInputs()) { if (auto v = ss->Receive(name)) { la_->Handle(name, *v, ssla_); } } if (data.pure) { ssla_ = nullptr; la_ = nullptr; } } catch (nf7::ExpiredException&) { ss->Finish(); } catch (nf7::FileHolder::EmptyException&) { ss->Finish(); } catch (nf7::File::NotImplementedException&) { ss->Finish(); } void Call::Lambda::Abort() noexcept { if (ssla_) { ssla_->Abort(); ssla_ = nullptr; } if (la_) { la_->Abort(); la_ = nullptr; } } void Call::UpdateItem(Sequencer::Editor&) noexcept { ImGui::Text("%s", callee_editor_.GetDisplayText().c_str()); } void Call::UpdateParamPanel(Sequencer::Editor&) noexcept { const auto em = ImGui::GetFontSize(); if (ImGui::CollapsingHeader("Sequencer/Call", ImGuiTreeNodeFlags_DefaultOpen)) { callee_editor_.ButtonWithLabel("callee"); ImGui::InputTextMultiline("expects", &data().expects, {0, 4.f*em}); if (ImGui::IsItemDeactivatedAfterEdit()) { mem_.Commit(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("session ends right after receiving these outputs"); } if (ImGui::Checkbox("pure", &data().pure)) { mem_.Commit(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("callee's lambda is created for each session"); } ImGui::Spacing(); callee_editor_.ItemWidget("callee"); } } void Call::UpdateTooltip(Sequencer::Editor&) noexcept { ImGui::TextUnformatted("Sequencer/Call"); } } } // namespace nf7