add Sequencer/Call

This commit is contained in:
falsycat 2022-08-14 17:39:18 +09:00
parent cec2763e0a
commit 540ce3bb0c
10 changed files with 809 additions and 1 deletions

View File

@ -51,6 +51,9 @@ target_sources(nf7
common/buffer.hh
common/dir.hh
common/dir_item.hh
common/file_base.hh
common/file_holder.hh
common/file_holder.cc
common/file_ref.hh
common/future.hh
common/generic_context.hh
@ -68,6 +71,7 @@ target_sources(nf7
common/gui_timeline.cc
common/gui_window.hh
common/history.hh
common/life.hh
common/lock.hh
common/logger.hh
common/logger_ref.hh
@ -98,6 +102,7 @@ target_sources(nf7
common/yas_nf7.hh
common/yas_std_atomic.hh
common/yas_std_filesystem.hh
common/yas_std_variant.hh
$<$<PLATFORM_ID:Linux>:common/native_file_unix.cc>
@ -109,6 +114,7 @@ target_sources(nf7
file/node_imm.cc
file/node_network.cc
file/node_ref.cc
file/sequencer_call.cc
file/sequencer_timeline.cc
file/system_dir.cc
file/system_imgui_config.cc

57
common/file_base.hh Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include <string_view>
#include <vector>
#include "nf7.hh"
namespace nf7 {
class FileBase : public nf7::File {
public:
class Feature {
public:
Feature() = default;
virtual ~Feature() = default;
Feature(const Feature&) = delete;
Feature(Feature&&) = delete;
Feature& operator=(const Feature&) = delete;
Feature& operator=(Feature&&) = delete;
// Feature* is just for avoiding multi inheritance issues with Env::Watcher
virtual nf7::File* Find(std::string_view) const noexcept { return nullptr; }
virtual void Handle(const nf7::File::Event&) noexcept { }
virtual void Update() noexcept { }
};
FileBase(const nf7::File::TypeInfo& t,
nf7::Env& env,
std::vector<Feature*>&& feats) noexcept :
nf7::File(t, env), feats_(std::move(feats)) {
}
nf7::File* Find(std::string_view name) const noexcept override final {
for (auto feat : feats_) {
if (auto ret = feat->Find(name)) {
return ret;
}
}
return nullptr;
}
void Handle(const nf7::File::Event& ev) noexcept override final {
for (auto feat : feats_) {
feat->Handle(ev);
}
}
void Update() noexcept override final {
for (auto feat : feats_) {
feat->Update();
}
}
private:
std::vector<Feature*> feats_;
};
} // namespace nf7

213
common/file_holder.cc Normal file
View File

@ -0,0 +1,213 @@
#include "common/file_holder.hh"
#include <optional>
#include <imgui.h>
#include <imgui_stdlib.h>
#include "common/generic_context.hh"
using namespace std::literals;
namespace nf7 {
FileHolder::FileHolder(
nf7::File& owner, std::string_view id, std::string_view iface, const FileHolder* src) :
owner_(&owner), id_(id), popup_config_(*this, iface) {
if (src) {
if (src->own()) {
entity_ = src->file()->Clone(owner.env());
} else {
entity_ = src->entity_;
}
// SetUp() will be called by kAdd event
}
}
nf7::File* FileHolder::Find(std::string_view name) const noexcept {
return name == id_? file_: nullptr;
}
void FileHolder::Handle(const nf7::File::Event& ev) noexcept {
switch (ev.type) {
case nf7::File::Event::kAdd:
assert(!ready_);
ready_ = true;
SetUp();
break;
case nf7::File::Event::kRemove:
assert(ready_);
TearDown();
ready_ = false;
break;
default:
break;
}
}
void FileHolder::Update() noexcept {
if (own()) {
ImGui::PushID(this);
file_->Update();
ImGui::PopID();
}
popup_config_.Update();
}
std::string FileHolder::GetDisplayText() const noexcept {
std::string text;
if (own()) {
text = "OWN: " + file_->type().name();
} else if (empty()) {
text = "NULL:";
} else {
text = "REF: "s + path().Stringify();
}
return text;
}
void FileHolder::UpdateButton(bool small) noexcept {
ImGui::PushID(this);
const auto text = GetDisplayText();
if (small? ImGui::SmallButton(text.c_str()): ImGui::Button(text.c_str())) {
popup_config_.Open();
}
ImGui::PopID();
}
void FileHolder::UpdateLabel(const char* name) noexcept {
ImGui::PushID(this);
if (ImGui::Button(GetDisplayText().c_str(), {ImGui::CalcItemWidth(), 0})) {
popup_config_.Open();
}
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::TextUnformatted(name);
ImGui::PopID();
}
void FileHolder::SetUp() noexcept {
if (!ready_ || file_) return;
if (own()) {
file_->MoveUnder(*owner_, id_);
} else if (ref()) {
try {
file_ = &owner_->ResolveOrThrow(path());
} catch (nf7::File::NotFoundException&) {
}
}
if (file_) {
watcher_.emplace(file_->env());
watcher_->Watch(file_->id());
watcher_->AddHandler(nf7::File::Event::kRemove, [this](auto&) {
file_ = nullptr;
});
auto mem = file_->interface<nf7::Memento>();
if (own() && mem) {
watcher_->AddHandler(nf7::File::Event::kUpdate, [this, mem](auto&) {
auto ptag = std::exchange(tag_, mem->Save());
if (ptag != tag_) {
onChange();
}
});
}
}
}
void FileHolder::TearDown() noexcept {
if (!ready_) return;
if (own()) {
file_->Isolate();
}
file_ = nullptr;
watcher_ = std::nullopt;
}
FileHolder::Tag::Tag(const Tag& src) noexcept {
if (src.target_) {
entity_ = src.target_->entity_;
tag_ = src.target_->tag_;
} else {
entity_ = src.entity_;
tag_ = src.tag_;
}
}
FileHolder::Tag& FileHolder::Tag::operator=(const Tag& src) noexcept {
assert(!src.target_);
assert(target_);
target_->TearDown();
target_->entity_ = src.entity_;
target_->tag_ = src.tag_;
if (target_->tag_) {
target_->file()->interfaceOrThrow<nf7::Memento>().Restore(target_->tag_);
}
target_->SetUp();
return *this;
}
void FileHolder::ConfigPopup::Open() noexcept {
if (h_->ref()) {
type_ = 1;
path_ = h_->path().Stringify();
} else {
type_ = 0;
path_ = "";
}
nf7::gui::Popup::Open();
}
void FileHolder::ConfigPopup::Update() noexcept {
ImGui::PushID(this);
auto& owner = *h_->owner_;
if (Begin()) {
if (ImGui::RadioButton("own", type_ == 0)) { type_ = 0; }
ImGui::SameLine();
if (ImGui::RadioButton("ref", type_ == 1)) { type_ = 1; }
if (type_ == 0) {
if (factory_.Update(owner)) {
// TODO
}
} else if (type_ == 1) {
ImGui::InputText("path", &path_);
bool missing = false;
try {
auto path = nf7::File::Path::Parse(path_);
try {
owner.ResolveOrThrow(path);
} catch (nf7::File::NotFoundException&) {
missing = true;
}
if (ImGui::Button("ok")) {
ImGui::CloseCurrentPopup();
auto ctx = std::make_shared<nf7::GenericContext>(
owner, "emplacing reference to file holder '"+h_->id_+"'");
owner.env().ExecMain(
ctx, [this, p = std::move(path)]() mutable {
h_->Emplace(std::move(p));
h_->onChange();
});
}
} catch (nf7::Exception& e) {
ImGui::Bullet(); ImGui::TextUnformatted(e.msg().c_str());
}
if (missing) {
ImGui::Bullet(); ImGui::TextUnformatted("the file is missing :(");
}
}
ImGui::EndPopup();
}
ImGui::PopID();
}
} // namespace nf7

156
common/file_holder.hh Normal file
View File

@ -0,0 +1,156 @@
#pragma once
#include <cassert>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
#include <yas/serialize.hpp>
#include <yas/types/std/variant.hpp>
#include <yas/types/utility/usertype.hpp>
#include "nf7.hh"
#include "common/file_base.hh"
#include "common/generic_watcher.hh"
#include "common/gui_file.hh"
#include "common/gui_popup.hh"
#include "common/memento.hh"
#include "common/yas_nf7.hh"
#include "common/yas_std_variant.hh"
namespace nf7 {
class FileHolder : public nf7::FileBase::Feature {
public:
class Tag;
class EmptyException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
using Entity = std::variant<
std::monostate, nf7::File::Path, std::shared_ptr<nf7::File>>;
FileHolder(nf7::File& owner, std::string_view id, std::string_view iface,
const FileHolder* src = nullptr);
FileHolder(const FileHolder&) = delete;
FileHolder(FileHolder&&) = delete;
FileHolder& operator=(const FileHolder&) = delete;
FileHolder& operator=(FileHolder&&) = delete;
// yas usertype serializer
void serialize(auto& ar) {
ar(entity_);
}
void Emplace(nf7::File::Path&& path) noexcept {
TearDown();
entity_ = std::move(path);
tag_ = nullptr;
SetUp();
}
void Emplace(std::unique_ptr<nf7::File>&& f) noexcept {
TearDown();
entity_ = std::move(f);
tag_ = nullptr;
SetUp();
}
nf7::File& GetFileOrThrow() {
SetUp();
if (!file_) {
throw EmptyException {"holder is empty"};
}
return *file_;
}
// nf7::FileBase::Feature methods
nf7::File* Find(std::string_view name) const noexcept override;
void Handle(const nf7::File::Event&) noexcept override;
void Update() noexcept override;
std::string GetDisplayText() const noexcept;
void UpdateButton(bool small = false) noexcept;
void UpdateLabel(const char* name) noexcept;
bool own() const noexcept {
return std::holds_alternative<std::shared_ptr<nf7::File>>(entity_);
}
bool ref() const noexcept {
return std::holds_alternative<nf7::File::Path>(entity_);
}
bool empty() const noexcept {
return std::holds_alternative<std::monostate>(entity_);
}
nf7::File* file() const noexcept { return file_; }
nf7::File::Path path() const noexcept {
assert(!empty());
return own()? nf7::File::Path {{id_}}: std::get<nf7::File::Path>(entity_);
}
// called when memento of owned file is changed, or target is changed by GUI.
// Emplace() doesn't.
std::function<void(void)> onChange = [](){};
private:
nf7::File* const owner_;
const std::string id_;
bool ready_ = false; // whether owner is added to file tree
Entity entity_;
std::shared_ptr<nf7::Memento::Tag> tag_;
nf7::File* file_ = nullptr;
std::optional<nf7::GenericWatcher> watcher_;
// GUI popup
struct ConfigPopup final : nf7::gui::Popup {
public:
ConfigPopup(FileHolder& h, std::string_view iface) noexcept :
nf7::gui::Popup("ConfigPopup"), h_(&h), factory_({std::string {iface}}) {
}
void Open() noexcept;
void Update() noexcept;
private:
FileHolder* const h_;
uint32_t type_;
std::string path_;
nf7::gui::FileFactory<0> factory_;
} popup_config_;
void SetUp() noexcept;
void TearDown() noexcept;
};
// to save/restore FileHolder's changes through GenericMemento
class FileHolder::Tag final {
public:
Tag(nf7::FileHolder& target) noexcept : target_(&target) {
}
Tag(const Tag&) noexcept;
Tag& operator=(const Tag&) noexcept;
Tag(Tag&&) = default;
Tag& operator=(Tag&&) = default;
private:
nf7::FileHolder* target_ = nullptr;
Entity entity_;
std::shared_ptr<nf7::Memento::Tag> tag_;
};
} // namespace nf7

80
common/life.hh Normal file
View File

@ -0,0 +1,80 @@
#pragma once
#include <cassert>
#include <memory>
#include "nf7.hh"
namespace nf7 {
class LifeExpiredException final : public nf7::Exception {
public:
using nf7::Exception::Exception;
};
template <typename T>
class Life final {
public:
class Ref;
Life() = delete;
Life(T& target) noexcept : ptr_(&target) {
}
~Life() noexcept {
if (data_) data_->ptr = nullptr;
}
Life(const Life&) = delete;
Life(Life&&) = delete;
Life& operator=(const Life&) = delete;
Life& operator=(Life&&) = delete;
private:
T* const ptr_;
struct Data final {
T* ptr;
};
std::shared_ptr<Data> data_;
};
template <typename T>
class Life<T>::Ref final {
public:
Ref() = default;
Ref(const Life& life) noexcept {
if (!life.data_) {
auto& l = const_cast<Life&>(life);
l.data_ = std::make_shared<Data>();
l.data_->ptr = l.ptr_;
}
data_ = life.data_;
}
Ref(const Ref&) = default;
Ref(Ref&&) = default;
Ref& operator=(const Ref&) = default;
Ref& operator=(Ref&&) = default;
void EnforceAlive() const {
if (!data_->ptr) {
throw LifeExpiredException {"target expired"};
}
}
operator bool() noexcept {
return !!data_;
}
T& operator*() noexcept {
assert(data_->ptr);
return *data_->ptr;
}
T* operator->() noexcept {
return &**this;
}
private:
std::shared_ptr<Data> data_;
};
} // namespace nf7

View File

@ -10,6 +10,8 @@
#include "nf7.hh"
#include "common/value.hh"
namespace nf7 {

View File

@ -33,6 +33,29 @@ struct serializer<
}
};
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
std::shared_ptr<nf7::File>> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const std::shared_ptr<nf7::File>& f) {
std::unique_ptr<nf7::File> uf(f.get());
ar(uf);
uf.release();
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, std::shared_ptr<nf7::File>& f) {
std::unique_ptr<nf7::File> uf;
ar(uf);
f = std::move(uf);
return ar;
}
};
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,

25
common/yas_std_variant.hh Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <yas/serialize.hpp>
namespace yas::detail {
template <size_t F>
struct serializer<
type_prop::not_a_fundamental,
ser_case::use_internal_serializer,
F,
std::monostate> {
public:
template <typename Archive>
static Archive& save(Archive& ar, const std::monostate&) {
return ar;
}
template <typename Archive>
static Archive& load(Archive& ar, std::monostate&) {
return ar;
}
};
} // namespace yas::detail

237
file/sequencer_call.cc Normal file
View File

@ -0,0 +1,237 @@
#include "nf7.hh"
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <yas/types/std/string.hpp>
#include <yas/types/std/vector.hpp>
#include "common/file_base.hh"
#include "common/file_holder.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.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<Call> kType =
{"Sequencer/Call", {"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;
Call(Env& env, const nf7::FileHolder* callee = nullptr, std::string_view expects = "") noexcept :
FileBase(kType, env, {&callee_}),
Sequencer(Sequencer::kCustomItem |
Sequencer::kTooltip |
Sequencer::kParamPanel),
life_(*this),
callee_(*this, "callee", "Node", callee),
mem_(*this, Data {*this, expects}){
callee_.onChange = [this]() {
mem_.Commit();
};
}
Call(Env& env, Deserializer& ar) : Call(env) {
ar(callee_, data().expects, data().pure);
}
void Serialize(Serializer& ar) const noexcept override {
ar(callee_, data().expects, data().pure);
}
std::unique_ptr<File> Clone(Env& env) const noexcept override {
return std::make_unique<Call>(env, &callee_, data().expects);
}
std::shared_ptr<Sequencer::Lambda> CreateLambda(
const std::shared_ptr<nf7::Context>&) 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<Call> life_;
nf7::FileHolder callee_;
struct Data {
Data(Call& f, std::string_view ex) noexcept : callee(f.callee_), expects(ex) {
}
nf7::FileHolder::Tag callee;
std::string expects;
bool pure = false;
};
nf7::GenericMemento<Data> 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<Call::Lambda> {
public:
Lambda(Call& f, const std::shared_ptr<nf7::Context>& ctx) noexcept :
Sequencer::Lambda(f, ctx), file_(f.life_) {
}
void Run(const std::shared_ptr<Sequencer::Session>& ss) noexcept;
private:
nf7::Life<Call>::Ref file_;
std::shared_ptr<Call::SessionLambda> ssla_;
nf7::Node* cached_node_ = nullptr;
std::shared_ptr<Node::Lambda> la_;
};
class Call::SessionLambda final : public nf7::Node::Lambda {
public:
SessionLambda(Call& f, const std::shared_ptr<Call::Lambda>& parent) noexcept :
nf7::Node::Lambda(f, parent) {
}
~SessionLambda() noexcept {
if (ss_ && expects_.size() > 0) {
ss_->Finish();
}
}
void Listen(Call& f, const std::shared_ptr<Sequencer::Session>& 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(std::string_view name, const nf7::Value& val,
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override {
if (!ss_) return;
ss_->Send(name, nf7::Value {val});
expects_.erase(std::string {name});
FinishIf();
}
private:
std::shared_ptr<Sequencer::Session> ss_;
std::unordered_set<std::string> expects_;
void FinishIf() noexcept {
if (expects_.size() == 0) {
ss_->Finish();
ss_ = nullptr;
}
}
};
std::shared_ptr<Sequencer::Lambda> Call::CreateLambda(
const std::shared_ptr<nf7::Context>& parent) noexcept {
return std::make_shared<Call::Lambda>(*this, parent);
}
void Call::Lambda::Run(const std::shared_ptr<Sequencer::Session>& ss) noexcept
try {
file_.EnforceAlive();
auto& data = file_->data();
auto& callee = file_->callee_.GetFileOrThrow();
auto& node = callee.interfaceOrThrow<nf7::Node>();
if (!ssla_) {
ssla_ = std::make_shared<Call::SessionLambda>(*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.input()) {
if (auto v = ss->Receive(name)) {
la_->Handle(name, *v, ssla_);
}
}
if (data.pure) {
ssla_ = nullptr;
la_ = nullptr;
}
} catch (nf7::LifeExpiredException&) {
ss->Finish();
} catch (nf7::FileHolder::EmptyException&) {
ss->Finish();
} catch (nf7::File::NotImplementedException&) {
ss->Finish();
}
void Call::UpdateItem(Sequencer::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
ImGui::SetCursorPos({.25f*em, .25f*em});
callee_.UpdateButton(true);
}
void Call::UpdateParamPanel(Sequencer::Editor&) noexcept {
const auto em = ImGui::GetFontSize();
if (ImGui::CollapsingHeader("Sequencer/Call", ImGuiTreeNodeFlags_DefaultOpen)) {
callee_.UpdateLabel("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");
}
}
}
void Call::UpdateTooltip(Sequencer::Editor&) noexcept {
ImGui::TextUnformatted("Sequencer/Call");
}
}
} // namespace nf7

View File

@ -305,7 +305,7 @@ class TL::Item final : nf7::Env::Watcher {
Watcher(f->env()),
id_(id), file_(std::move(f)),
seq_(&file_->interfaceOrThrow<nf7::Sequencer>()),
mem_(f->interface<nf7::Memento>()),
mem_(file_->interface<nf7::Memento>()),
timing_(t), display_timing_(t) {
}
Item(const Item&) = delete;
@ -648,6 +648,9 @@ class TL::Lambda final : public Node::Lambda,
void Abort() noexcept {
aborted_ = true;
for (auto& p : lambdas_) {
p.second->Abort();
}
}
bool aborted() const noexcept { return aborted_; }
@ -1245,6 +1248,12 @@ void TL::Handle(const Event& ev) noexcept {
}
void TL::Update() noexcept {
for (const auto& layer : layers_) {
for (const auto& item : layer->items()) {
item->file().Update();
}
}
popup_add_item_.Update();
popup_config_.Update();