#include "nf7.hh" #include #include #include #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/gui_value.hh" #include "common/life.hh" #include "common/ptr_selector.hh" #include "common/sequencer.hh" #include "common/value.hh" namespace nf7 { namespace { class Adaptor final : public nf7::FileBase, public nf7::Sequencer { public: static inline const nf7::GenericTypeInfo kType = {"Sequencer/Adaptor", {"nf7::Sequencer"}}; static void UpdateTypeTooltip() noexcept { ImGui::TextUnformatted("Wraps and Adapts other Sequencer."); ImGui::Bullet(); ImGui::TextUnformatted( "implements nf7::Sequencer"); ImGui::Bullet(); ImGui::TextUnformatted( "changes will be applied to active lambdas immediately"); } class Session; class Lambda; class Editor; struct Var { std::string name; bool peek = false; void serialize(auto& ar) { ar(name, peek); } }; struct Data { nf7::FileHolder::Tag target; std::vector> input_imm; std::vector> input_map; std::vector> output_map; }; Adaptor(nf7::Env& env, Data&& data = {}) noexcept : nf7::FileBase(kType, env, {&target_, &target_editor_}), Sequencer(Sequencer::kCustomItem | Sequencer::kTooltip | Sequencer::kParamPanel), life_(*this), target_(*this, "target", mem_), target_editor_(target_, [](auto& t) { return t.flags().contains("nf7::Sequencer"); }), mem_(std::move(data), *this) { mem_.data().target.SetTarget(target_); mem_.CommitAmend(); } Adaptor(nf7::Deserializer& ar) : Adaptor(ar.env()) { ar(target_, data().input_imm, data().input_map, data().output_map); } void Serialize(nf7::Serializer& ar) const noexcept override { ar(target_, data().input_imm, data().input_map, data().output_map); } 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 nf7::InterfaceSelector< nf7::Memento, nf7::Sequencer>(t).Select(this, &mem_); } private: nf7::Life life_; nf7::FileHolder target_; nf7::gui::FileHolderEditor target_editor_; nf7::GenericMemento mem_; const Data& data() const noexcept { return mem_.data(); } Data& data() noexcept { return mem_.data(); } }; class Adaptor::Session final : public nf7::Sequencer::Session { public: // ensure that Adaptor is alive Session(Adaptor& f, const std::shared_ptr& parent) noexcept : parent_(parent) { for (auto& p : f.data().input_imm) { vars_[p.first] = p.second.entity(); } for (auto& p : f.data().input_map) { if (p.second.name.size() == 0) continue; if (p.second.peek) { if (const auto ptr = parent->Peek(p.second.name)) { vars_[p.first] = *ptr; } } else { if (auto ptr = parent->Receive(p.second.name)) { vars_[p.first] = std::move(*ptr); } } } for (auto& p : f.data().output_map) { outs_[p.first] = p.second; } } const nf7::Value* Peek(std::string_view name) noexcept override { auto itr = vars_.find(std::string {name}); return itr != vars_.end()? &itr->second: nullptr; } std::optional Receive(std::string_view name) noexcept override { auto itr = vars_.find(std::string {name}); if (itr == vars_.end()) { return std::nullopt; } auto ret = std::move(itr->second); vars_.erase(itr); return ret; } void Send(std::string_view name, nf7::Value&& v) noexcept override { if (done_) return; auto itr = outs_.find(std::string {name}); if (itr != outs_.end()) { parent_->Send(itr->second, std::move(v)); } } void Finish() noexcept override { assert(parent_); parent_->Finish(); done_ = true; } private: std::shared_ptr parent_; std::unordered_map vars_; std::unordered_map outs_; bool done_ = false; }; class Adaptor::Lambda final : public nf7::Sequencer::Lambda, public std::enable_shared_from_this { public: Lambda(Adaptor& f, const std::shared_ptr& parent) noexcept : nf7::Sequencer::Lambda(f, parent), f_(f.life_) { } void Run(const std::shared_ptr& ss) noexcept override try { f_.EnforceAlive(); auto& target = f_->target_.GetFileOrThrow(); auto& seq = target.interfaceOrThrow(); if (!la_ || target.id() != cached_id_) { la_ = seq.CreateLambda(shared_from_this()); cached_id_ = target.id(); } la_->Run(std::make_shared(*f_, ss)); } catch (nf7::Exception&) { ss->Finish(); } private: nf7::Life::Ref f_; nf7::File::Id cached_id_ = 0; std::shared_ptr la_; }; std::shared_ptr Adaptor::CreateLambda( const std::shared_ptr& parent) noexcept { return std::make_shared(*this, parent); } class Adaptor::Editor final : public nf7::Sequencer::Editor { public: using nf7::Sequencer::Editor::Editor; }; void Adaptor::UpdateItem(Sequencer::Editor&) noexcept { try { auto& seq = target_.GetFileOrThrow().interfaceOrThrow(); if (seq.flags() & nf7::Sequencer::kCustomItem) { Adaptor::Editor ed; seq.UpdateItem(ed); } } catch (nf7::Exception&) { ImGui::Text("%s", target_editor_.GetDisplayText().c_str()); } } void Adaptor::UpdateParamPanel(Sequencer::Editor&) noexcept { bool commit = false; auto& imm = data().input_imm; auto& inputs = data().input_map; auto& outputs = data().output_map; const auto em = ImGui::GetFontSize(); if (ImGui::CollapsingHeader("Sequencer/Adaptor", ImGuiTreeNodeFlags_DefaultOpen)) { target_editor_.ButtonWithLabel("target"); if (ImGui::BeginTable("table", 3)) { ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch, 1.f); ImGui::TableSetupColumn("arrow", ImGuiTableColumnFlags_WidthFixed, 1*em); ImGui::TableSetupColumn("right", ImGuiTableColumnFlags_WidthStretch, 1.f); // ---- immediate values ImGui::PushID("imm"); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Spacing(); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("imm input"); ImGui::SameLine(); if (ImGui::Button("+")) { imm.push_back({"target_input", {}}); commit = true; } if (imm.size() == 0) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextDisabled("no rule"); } for (size_t i = 0; i < imm.size(); ++i) { auto& p = imm[i]; ImGui::TableNextRow(); ImGui::PushID(static_cast(i)); if (ImGui::TableNextColumn()) { commit |= p.second.UpdateTypeButton("T"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); commit |= p.second.UpdateEditor(); } if (ImGui::TableNextColumn()) { ImGui::TextUnformatted("->"); } if (ImGui::TableNextColumn()) { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputTextWithHint("##name", "dst", &p.first); commit |= ImGui::IsItemDeactivatedAfterEdit(); } ImGui::PopID(); } ImGui::PopID(); // ---- input map ImGui::PushID("input"); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Spacing(); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("input"); ImGui::SameLine(); if (ImGui::Button("+")) { inputs.push_back({"target_input", {}}); commit = true; } if (inputs.size() == 0) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextDisabled("no rule"); } for (size_t i = 0; i < inputs.size(); ++i) { auto& in = inputs[i]; ImGui::TableNextRow(); ImGui::PushID(static_cast(i)); if (ImGui::TableNextColumn()) { const char* text = in.second.peek? "P": "R"; if (ImGui::Button(text)) { in.second.peek = !in.second.peek; commit = true; } ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputTextWithHint("##src", "src", &in.second.name); commit |= ImGui::IsItemDeactivatedAfterEdit(); } if (ImGui::TableNextColumn()) { ImGui::TextUnformatted("->"); } if (ImGui::TableNextColumn()) { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputTextWithHint("##dst", "dst", &in.first); commit |= ImGui::IsItemDeactivatedAfterEdit(); } ImGui::PopID(); } ImGui::PopID(); // ---- output map ImGui::PushID("output"); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Spacing(); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("output"); ImGui::SameLine(); if (ImGui::Button("+")) { outputs.push_back({"target_output", ""}); commit = true; } if (outputs.size() == 0) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextDisabled("no rule"); } for (size_t i = 0; i < outputs.size(); ++i) { auto& out = outputs[i]; ImGui::TableNextRow(); ImGui::PushID(static_cast(i)); if (ImGui::TableNextColumn()) { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputTextWithHint("##src", "src", &out.first); commit |= ImGui::IsItemDeactivatedAfterEdit(); } if (ImGui::TableNextColumn()) { ImGui::TextUnformatted("->"); } if (ImGui::TableNextColumn()) { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputTextWithHint("##dst", "dst", &out.second); commit |= ImGui::IsItemDeactivatedAfterEdit(); } ImGui::PopID(); } ImGui::PopID(); ImGui::EndTable(); } } if (commit) { imm.erase( std::remove_if( imm.begin(), imm.end(), [](auto& x) { return x.first.size() == 0; }), imm.end()); inputs.erase( std::remove_if( inputs.begin(), inputs.end(), [](auto& x) { return x.first.size() == 0; }), inputs.end()); outputs.erase( std::remove_if( outputs.begin(), outputs.end(), [](auto& x) { return x.first.size() == 0; }), outputs.end()); mem_.Commit(); } ImGui::Spacing(); try { auto& seq = target_.GetFileOrThrow().interfaceOrThrow(); if (seq.flags() & nf7::Sequencer::kParamPanel) { Adaptor::Editor ed; seq.UpdateParamPanel(ed); } } catch (nf7::Exception&) { ImGui::Separator(); ImGui::TextUnformatted("TARGET HAS NO SEQUENCER INTERFACE"); } } void Adaptor::UpdateTooltip(Sequencer::Editor&) noexcept { ImGui::TextUnformatted("Sequencer/Adaptor"); } } } // namespace nf7