From 61bccc3acbbe2b057c862035152f2f8f200d275b Mon Sep 17 00:00:00 2001 From: falsycat Date: Mon, 15 Aug 2022 23:31:20 +0900 Subject: [PATCH] add Sequencer/Adaptor --- CMakeLists.txt | 1 + common/gui_value.hh | 217 ++++++++++++++++++++ file/sequencer_adaptor.cc | 413 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 631 insertions(+) create mode 100644 common/gui_value.hh create mode 100644 file/sequencer_adaptor.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b451b3..d70a6d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ target_sources(nf7 file/node_imm.cc file/node_network.cc file/node_ref.cc + file/sequencer_adaptor.cc file/sequencer_call.cc file/sequencer_timeline.cc file/system_dir.cc diff --git a/common/gui_value.hh b/common/gui_value.hh new file mode 100644 index 0000000..5a231f9 --- /dev/null +++ b/common/gui_value.hh @@ -0,0 +1,217 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include + +#include "nf7.hh" + +#include "common/value.hh" + + +namespace nf7::gui { + +class Value { + public: + enum Type { + kPulse, + kInteger, + kScalar, + kNormalizedScalar, + kString, + kMultilineString, + }; + + static const char* StringifyType(Type t) noexcept { + switch (t) { + case kPulse: return "Pulse"; + case kInteger: return "Integer"; + case kScalar: return "Scalar"; + case kNormalizedScalar: return "NormalizedScalar"; + case kString: return "kString"; + case kMultilineString: return "kMultilineString"; + } + assert(false); + return nullptr; + } + static Type ParseType(std::string_view v) { + return + v == "Pulse"? kPulse: + v == "Integer"? kInteger: + v == "Scalar"? kScalar: + v == "NormalizedScalar"? kNormalizedScalar: + v == "String"? kString: + v == "MultilineString"? kMultilineString: + throw nf7::DeserializeException {"unknown type: "+std::string {v}}; + } + + Value() = default; + Value(const Value&) = default; + Value(Value&&) = default; + Value& operator=(const Value&) = default; + Value& operator=(Value&&) = default; + + bool ReplaceType(Type t) noexcept { + if (type_ == t) return false; + + type_ = t; + switch (type_) { + case nf7::gui::Value::kPulse: + entity_ = nf7::Value::Pulse {}; + break; + case nf7::gui::Value::kInteger: + entity_ = nf7::Value::Integer {0}; + break; + case nf7::gui::Value::kScalar: + case nf7::gui::Value::kNormalizedScalar: + entity_ = nf7::Value::Scalar {0}; + break; + case nf7::gui::Value::kString: + case nf7::gui::Value::kMultilineString: + entity_ = nf7::Value::String {}; + break; + default: + assert(false); + } + return true; + } + + void ReplaceEntity(const nf7::Value& v) noexcept { + entity_ = v; + ValidateValue(); + } + void ReplaceEntity(nf7::Value&& v) noexcept { + entity_ = std::move(v); + ValidateValue(); + } + void ValidateValue() const { + bool valid = true; + switch (type_) { + case nf7::gui::Value::kPulse: + valid = entity_.isPulse(); + break; + case nf7::gui::Value::kInteger: + valid = entity_.isInteger(); + break; + case nf7::gui::Value::kScalar: + case nf7::gui::Value::kNormalizedScalar: + valid = entity_.isScalar(); + break; + case nf7::gui::Value::kString: + case nf7::gui::Value::kMultilineString: + valid = entity_.isString(); + break; + } + if (!valid) { + throw nf7::DeserializeException {"invalid entity type"}; + } + } + + bool UpdateEditor() noexcept { + bool ret = false; + + ImGui::Button("T"); + if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::MenuItem("Pulse", nullptr, type_ == kPulse)) { + ret |= ReplaceType(kPulse); + } + if (ImGui::MenuItem("Integer", nullptr, type_ == kInteger)) { + ret |= ReplaceType(kInteger); + } + if (ImGui::MenuItem("Scalar", nullptr, type_ == kScalar)) { + ret |= ReplaceType(kScalar); + } + if (ImGui::MenuItem("Normalized Scalar", nullptr, type_ == kNormalizedScalar)) { + ret |= ReplaceType(kNormalizedScalar); + } + if (ImGui::MenuItem("String", nullptr, type_ == kString)) { + ret |= ReplaceType(kString); + } + if (ImGui::MenuItem("Multiline String", nullptr, type_ == kMultilineString)) { + ret |= ReplaceType(kMultilineString); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); + + const auto em = ImGui::GetFontSize(); + const auto w = ImGui::CalcItemWidth(); + ImGui::PushItemWidth(w); + switch (type_) { + case kPulse: + ImGui::BeginDisabled(); + ImGui::Button("PULSE", {w, 0}); + ImGui::EndDisabled(); + break; + case kInteger: + ImGui::DragScalar("##value", ImGuiDataType_S64, &entity_.integer()); + ret |= ImGui::IsItemDeactivatedAfterEdit(); + break; + case kScalar: + ImGui::DragScalar("##value", ImGuiDataType_Double, &entity_.scalar()); + ret |= ImGui::IsItemDeactivatedAfterEdit(); + break; + case kNormalizedScalar: + ImGui::DragScalar("##value", ImGuiDataType_Double, &entity_.scalar()); + ret |= ImGui::IsItemDeactivatedAfterEdit(); + break; + case kString: + ImGui::InputTextWithHint("##value", "string", &entity_.string()); + ret |= ImGui::IsItemDeactivatedAfterEdit(); + break; + case kMultilineString: + ImGui::InputTextMultiline("##value", &entity_.string(), {w, 2.4f*em}); + ret |= ImGui::IsItemDeactivatedAfterEdit(); + break; + default: + assert(false); + } + ImGui::PopItemWidth(); + + return ret; + } + + Type type() const noexcept { return type_; } + const nf7::Value& entity() const noexcept { return entity_; } + + private: + Type type_ = kInteger; + nf7::Value entity_ = nf7::Value::Integer {0}; +}; + +} // namespace nf7::gui + + +namespace yas::detail { + +template +struct serializer< + type_prop::not_a_fundamental, + ser_case::use_internal_serializer, + F, + nf7::gui::Value> { + public: + template + static Archive& save(Archive& ar, const nf7::gui::Value& v) { + ar(std::string_view {v.StringifyType(v.type())}, v.entity()); + return ar; + } + template + static Archive& load(Archive& ar, nf7::gui::Value& v) { + std::string type; + nf7::Value entity; + ar(type, entity); + + v.ReplaceType(v.ParseType(type)); + v.ReplaceEntity(entity); + return ar; + } +}; + +} // namespace yas::detail diff --git a/file/sequencer_adaptor.cc b/file/sequencer_adaptor.cc new file mode 100644 index 0000000..491e8c5 --- /dev/null +++ b/file/sequencer_adaptor.cc @@ -0,0 +1,413 @@ +#include "nf7.hh" + +#include +#include +#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 SessionLambda; + + struct Var { + std::string name; + bool peek = false; + + void serialize(auto& ar) { + ar(name, peek); + } + }; + struct Data { + Data(Adaptor& f, const Data* src) noexcept : target(f.target_) { + if (src) { + input_imm = src->input_imm; + input_map = src->input_map; + output_map = src->output_map; + } + } + + nf7::FileHolder::Tag target; + + std::vector> input_imm; + std::vector> input_map; + std::vector> output_map; + }; + + Adaptor(Env& env, const nf7::FileHolder* target = nullptr, const Data* data = nullptr) noexcept : + nf7::FileBase(kType, env, {&target_, &target_popup_}), + Sequencer(Sequencer::kCustomItem | + Sequencer::kTooltip | + Sequencer::kParamPanel), + life_(*this), + target_(*this, "target", target), + target_editor_(*this, + [](auto& t) { return t.flags().contains("nf7::Sequencer"); }), + target_popup_("TargetEditorPopup", + "Sequencer/Adaptor: replacing target...", + target_editor_), + mem_(*this, Data {*this, data}) { + target_.onChildMementoChange = [this]() { mem_.Commit(); }; + + target_popup_.onOpen = [this]() { + target_editor_.Reset(target_); + }; + target_popup_.onDone = [this]() { + auto ctx = std::make_shared(*this, "updating target"); + this->env().ExecMain(ctx, [this]() { + target_editor_.Apply(target_); + mem_.Commit(); + }); + }; + } + + Adaptor(Env& env, Deserializer& ar) : Adaptor(env) { + ar(target_, data().input_imm, data().input_map, data().output_map); + } + void Serialize(Serializer& ar) const noexcept override { + ar(target_, data().input_imm, data().input_map, data().output_map); + } + std::unique_ptr Clone(Env& env) const noexcept override { + return std::make_unique(env, &target_, &data()); + } + + std::shared_ptr CreateLambda( + const std::shared_ptr&) noexcept override; + + void UpdateItem(Sequencer::Editor&) noexcept override; + void UpdateParamPanel(Sequencer::Editor&) noexcept override; + void UpdateTooltip(Sequencer::Editor&) noexcept override; + + 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 target_; + + nf7::gui::FileHolderEditor target_editor_; + nf7::gui::PopupWrapper target_popup_; + + 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 { + 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 { + assert(parent_); + auto itr = vars_.find(std::string {name}); + return itr != vars_.end()? &itr->second: nullptr; + } + std::optional Receive(std::string_view name) noexcept override { + assert(parent_); + + 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 { + assert(parent_); + auto itr = outs_.find(std::string {name}); + if (itr != outs_.end()) { + parent_->Send(itr->second, std::move(v)); + } + } + + void Finish() noexcept override { + parent_->Finish(); + parent_ = nullptr; + } + + const Info& info() const noexcept override { return parent_->info(); } + + private: + std::shared_ptr parent_; + + std::unordered_map vars_; + std::unordered_map outs_; +}; +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); +} + + +void Adaptor::UpdateItem(Sequencer::Editor&) noexcept { + const auto em = ImGui::GetFontSize(); + ImGui::SetCursorPos({.25f*em, .25f*em}); + if (target_.UpdateButton(true)) { + target_popup_.Open(); + } +} +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)) { + if (target_.UpdateButtonWithLabel("target")) { + target_popup_.Open(); + } + + 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::AlignTextToFramePadding(); + ImGui::Spacing(); + 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()) { + 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::AlignTextToFramePadding(); + ImGui::Spacing(); + 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::AlignTextToFramePadding(); + ImGui::Spacing(); + 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(); + } +} +void Adaptor::UpdateTooltip(Sequencer::Editor&) noexcept { + ImGui::TextUnformatted("Sequencer/Adaptor"); +} + +} +} // namespace nf7 +