From b8cb2a87d01d65ce341c8771d642db7ec84abae1 Mon Sep 17 00:00:00 2001 From: falsycat Date: Thu, 11 Aug 2022 00:45:13 +0900 Subject: [PATCH] implement Lambda of Sequencer/Timeline --- CMakeLists.txt | 1 + common/gui_context.hh | 46 ++++ common/gui_timeline.cc | 78 +++++- common/gui_timeline.hh | 39 ++- common/sequencer.hh | 57 +++- file/sequencer_timeline.cc | 524 ++++++++++++++++++++++++++++++++++--- main.cc | 4 +- 7 files changed, 685 insertions(+), 64 deletions(-) create mode 100644 common/gui_context.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b9adf7..3697a91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ target_sources(nf7 common/generic_type_info.hh common/generic_watcher.hh common/gui_dnd.hh + common/gui_context.hh common/gui_file.hh common/gui_node.hh common/gui_popup.hh diff --git a/common/gui_context.hh b/common/gui_context.hh new file mode 100644 index 0000000..5afcc2b --- /dev/null +++ b/common/gui_context.hh @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include + +#include "nf7.hh" + + +namespace nf7::gui { + +std::string GetContextDisplayName(const nf7::Context& ctx) noexcept { + auto f = ctx.env().GetFile(ctx.initiator()); + + const auto initiator = + f? f->abspath().Stringify(): std::string {""}; + + char buf[32]; + std::snprintf(buf, sizeof(buf), "(0x%0" PRIXPTR ")", reinterpret_cast(&ctx)); + + return initiator + " " + buf; +} + +std::string GetParentContextDisplayName(const nf7::Context& ctx) noexcept { + if (auto parent = ctx.parent()) { + return nf7::gui::GetContextDisplayName(*parent); + } else if (ctx.depth() == 0) { + return "(isolated)"; + } else { + return " MEMORY LEAK? ;("; + } +} + +void ContextStack(const nf7::Context& ctx) noexcept { + for (auto p = ctx.parent(); p; p = p->parent()) { + auto f = ctx.env().GetFile(p->initiator()); + + const auto path = f? f->abspath().Stringify(): "[missing file]"; + + ImGui::TextUnformatted(path.c_str()); + ImGui::TextDisabled("%s", p->GetDescription().c_str()); + } +} + +} // namespace nf7::gui diff --git a/common/gui_timeline.cc b/common/gui_timeline.cc index 2ee1c81..2be4d5e 100644 --- a/common/gui_timeline.cc +++ b/common/gui_timeline.cc @@ -13,8 +13,12 @@ namespace nf7::gui { bool Timeline::Begin(uint64_t len) noexcept { assert(frame_state_ == kRoot); - layer_y_ = 0; - layer_h_ = 0; + layer_idx_ = 0; + layer_y_ = 0; + layer_h_ = 0; + + layer_idx_first_display_ = 0; + layer_offset_y_.clear(); len_ = len; scroll_size_.x = GetXFromTime(len_) + 100; @@ -57,15 +61,30 @@ void Timeline::End() noexcept { void Timeline::NextLayerHeader(Layer layer, float height) noexcept { assert(frame_state_ == kHeader); + assert(height > 0); const auto em = ImGui::GetFontSize(); if (layer_h_ > 0) { + ++layer_idx_; layer_y_ += layer_h_+padding()*2; } layer_h_ = height*em; layer_ = layer; + // save Y offset of the layer if shown + if (layer_idx_first_display_) { + if (layer_y_ < scroll_.y+body_size_.y) { + layer_offset_y_.push_back(layer_y_); + } + } else { + const auto bottom = layer_y_+layer_h_; + if (bottom > scroll_.y) { + layer_idx_first_display_ = layer_idx_; + layer_offset_y_.push_back(layer_y_); + } + } + const auto mouse = ImGui::GetMousePos().y; if (layerTopScreenY() <= mouse && mouse < layerBottomScreenY()) { mouse_layer_ = layer; @@ -87,7 +106,9 @@ void Timeline::NextLayerHeader(Layer layer, float height) noexcept { bool Timeline::BeginBody() noexcept { assert(frame_state_ == kHeader); - const auto em = ImGui::GetFontSize(); + const auto em = ImGui::GetFontSize(); + const auto ctx = ImGui::GetCurrentContext(); + const auto& io = ImGui::GetIO(); // end of header group ImGui::EndGroup(); @@ -104,15 +125,34 @@ bool Timeline::BeginBody() noexcept { frame_state_ = kBody; body_screen_offset_ = ImGui::GetCursorScreenPos(); - ImGui::InvisibleButton("viewport-grip", scroll_size_, ImGuiButtonFlags_MouseButtonMiddle); + ImGui::InvisibleButton( + "viewport-grip", scroll_size_, + ImGuiButtonFlags_MouseButtonMiddle | + ImGuiButtonFlags_MouseButtonLeft); ImGui::SetItemAllowOverlap(); if (ImGui::IsItemActive()) { - scroll_ -= ImGui::GetIO().MouseDelta; + switch (ctx->ActiveIdMouseButton) { + case ImGuiMouseButton_Left: // click timeline to set time + action_time_ = GetTimeFromScreenX(io.MousePos.x); + if (ImGui::IsItemActivated() || action_time_ != action_last_set_time_) { + action_ = kSetTime; + action_last_set_time_ = action_time_; + } + break; + + case ImGuiMouseButton_Middle: // easyscroll + scroll_ -= io.MouseDelta; + break; + + default: + break; + } } - layer_ = nullptr; - layer_y_ = 0; - layer_h_ = 0; + layer_ = nullptr; + layer_idx_ = 0; + layer_y_ = 0; + layer_h_ = 0; return true; } return false; @@ -177,16 +217,19 @@ void Timeline::EndBody() noexcept { } bool Timeline::NextLayer(Layer layer, float height) noexcept { assert(frame_state_ == kBody); + assert(height > 0); const auto em = ImGui::GetFontSize(); if (layer_h_ > 0) { + ++layer_idx_; layer_y_ += layer_h_+padding()*2; } layer_h_ = height*em; layer_ = layer; - return true; // TODO check if shown + // it's shown if y offset is saved + return !!layerTopY(layer_idx_); } bool Timeline::BeginItem(Item item, uint64_t begin, uint64_t end) noexcept { @@ -241,7 +284,7 @@ void Timeline::Cursor(const char* name, uint64_t t, uint32_t col) noexcept { const auto size = ImGui::GetWindowSize(); const auto grid_h = xgridHeight(); const auto x = GetScreenXFromTime(t); - if (x < body_offset_.x) return; + if (x < body_offset_.x || x > body_offset_.x+body_size_.x) return; d->AddLine({x, spos.y}, {x, spos.y+size.y}, col); @@ -250,6 +293,19 @@ void Timeline::Cursor(const char* name, uint64_t t, uint32_t col) noexcept { d->AddText({x, spos.y + grid_h*0.1f }, col, num.c_str()); d->AddText({x, spos.y + grid_h*0.1f+em}, col, name); } +void Timeline::Arrow(uint64_t t, uint64_t layer, uint32_t col) noexcept { + const auto d = ImGui::GetWindowDrawList(); + + const auto em = ImGui::GetFontSize(); + + const auto x = GetScreenXFromTime(t); + if (x < body_offset_.x || x > body_offset_.x+body_size_.x) return; + + const auto y = layerTopScreenY(layer); + if (!y || *y < scroll_.y) return; + + d->AddTriangleFilled({x, *y}, {x+em, *y-em/2}, {x+em, *y+em/2}, col); +} void Timeline::UpdateXGrid() noexcept { constexpr uint64_t kAccentInterval = 5; @@ -295,7 +351,7 @@ void Timeline::HandleGrip(Item item, float off, Action ac, Action acdone, ImGuiM off += ImGui::GetCurrentContext()->ActiveIdClickOffset.x; const auto pos = ImGui::GetMousePos() - ImVec2{off, 0}; - grip_time_ = GetTimeFromScreenX(pos.x); + action_time_ = GetTimeFromScreenX(pos.x); scroll_x_to_mouse_ = true; scroll_y_to_mouse_ = (ac == kMove); diff --git a/common/gui_timeline.hh b/common/gui_timeline.hh index 9dad55b..b7f38b6 100644 --- a/common/gui_timeline.hh +++ b/common/gui_timeline.hh @@ -2,6 +2,7 @@ #include #include +#include #include @@ -14,9 +15,9 @@ namespace nf7::gui { // if (tl.Begin()) { -// tl.NextLayer(layer1, &layer1_height) +// tl.NextLayerHeader(layer1, &layer1_height) // ImGui::Button("layer1"); -// tl.NextLayer(layer2, &layer2_height) +// tl.NextLayerHeader(layer2, &layer2_height) // ImGui::Button("layer2"); // // if (tl.BeginBody()) { @@ -60,6 +61,7 @@ struct Timeline { kResizeEndDone, kMove, kMoveDone, + kSetTime, }; using Layer = void*; using Item = void*; @@ -94,6 +96,7 @@ struct Timeline { void EndItem() noexcept; void Cursor(const char*, uint64_t t, uint32_t col) noexcept; + void Arrow(uint64_t t, uint64_t layer, uint32_t col) noexcept; uint64_t GetTimeFromX(float x) const noexcept { return static_cast(std::max(0.f, x/ImGui::GetFontSize()/zoom_)); @@ -113,6 +116,22 @@ struct Timeline { float xgridHeight() const noexcept { return xgrid_height_*ImGui::GetFontSize(); } float padding() const noexcept { return padding_*ImGui::GetFontSize(); } + std::optional layerTopY(size_t idx) noexcept { + if (!layer_idx_first_display_ || idx < *layer_idx_first_display_) { + return std::nullopt; + } + idx -= *layer_idx_first_display_; + if (idx >= layer_offset_y_.size()) { + return std::nullopt; + } + return layer_offset_y_[idx]; + } + + std::optional layerTopScreenY(size_t idx) noexcept { + auto y = layerTopY(idx); + if (!y) return std::nullopt; + return *y + body_screen_offset_.y; + } float layerTopScreenY() noexcept { return body_screen_offset_.y + layer_y_; } @@ -130,7 +149,7 @@ struct Timeline { Action action() const noexcept { return action_; } Item actionTarget() const noexcept { return action_target_; } - uint64_t gripTime() const noexcept { return grip_time_; } + uint64_t actionTime() const noexcept { return action_time_; } private: // immutable params @@ -161,15 +180,21 @@ struct Timeline { float mouse_layer_y_; float mouse_layer_h_; - Layer layer_; - float layer_y_; - float layer_h_; + Layer layer_; + size_t layer_idx_; + float layer_y_; + float layer_h_; + + std::optional layer_idx_first_display_; + std::vector layer_offset_y_; Item item_; Action action_; Item action_target_; - uint64_t grip_time_; + uint64_t action_time_; + + uint64_t action_last_set_time_ = UINT64_MAX; // for kSetTime void UpdateXGrid() noexcept; diff --git a/common/sequencer.hh b/common/sequencer.hh index e886e3c..5075a6c 100644 --- a/common/sequencer.hh +++ b/common/sequencer.hh @@ -13,8 +13,9 @@ namespace nf7 { class Sequencer : public nf7::File::Interface { public: - class Lambda; class Editor; + class Session; + class Lambda; struct Period { uint64_t begin, end; }; @@ -34,7 +35,8 @@ class Sequencer : public nf7::File::Interface { Sequencer& operator=(Sequencer&&) = delete; // Sequencer* is a dummy parameter to avoid issues of multi inheritance. - virtual std::shared_ptr CreateLambda(const std::shared_ptr&) noexcept = 0; + virtual std::shared_ptr CreateLambda( + const std::shared_ptr&) noexcept = 0; virtual void UpdateItem(Editor&) noexcept { } virtual void UpdateTooltip(Editor&) noexcept { } @@ -42,12 +44,6 @@ class Sequencer : public nf7::File::Interface { Flags flags() const noexcept { return flags_; } - std::span input() const noexcept { return input_; } - std::span output() const noexcept { return output_; } - - protected: - std::vector input_, output_; - private: Flags flags_; }; @@ -62,4 +58,49 @@ class Sequencer::Editor { Editor& operator=(Editor&&) = delete; }; +class Sequencer::Session { + public: + class UnknownNameException final : public nf7::Exception { + public: + using nf7::Exception::Exception; + }; + + Session() = default; + virtual ~Session() = default; + Session(const Session&) = delete; + Session(Session&&) = delete; + Session& operator=(const Session&) = delete; + Session& operator=(Session&&) = delete; + + // these can throw UnknownNameException + virtual const nf7::Value& Peek(std::string_view) = 0; + virtual nf7::Value Receive(std::string_view) = 0; + + virtual void Send(std::string_view, nf7::Value&&) noexcept = 0; + + // thread-safe + virtual void Finish() noexcept = 0; + + struct Info final { + public: + uint64_t time; + uint64_t begin; + uint64_t end; + }; + virtual const Info& info() const noexcept = 0; +}; + +class Sequencer::Lambda : public nf7::Context { + public: + Lambda(nf7::File& f, const std::shared_ptr& ctx = nullptr) noexcept : + Lambda(f.env(), f.id(), ctx) { + } + Lambda(nf7::Env& env, nf7::File::Id id, + const std::shared_ptr& ctx = nullptr) noexcept : + Context(env, id, ctx) { + } + + virtual void Run(const std::shared_ptr&) noexcept = 0; +}; + } // namespace nf7 diff --git a/file/sequencer_timeline.cc b/file/sequencer_timeline.cc index 9139bb3..560de34 100644 --- a/file/sequencer_timeline.cc +++ b/file/sequencer_timeline.cc @@ -1,9 +1,13 @@ #include +#include #include +#include #include #include #include #include +#include +#include #include #include @@ -20,6 +24,7 @@ #include "common/dir_item.hh" #include "common/generic_context.hh" #include "common/generic_type_info.hh" +#include "common/gui_context.hh" #include "common/gui_file.hh" #include "common/gui_popup.hh" #include "common/gui_timeline.hh" @@ -30,6 +35,10 @@ #include "common/squashed_history.hh" #include "common/yas_nf7.hh" +#include + + +using namespace std::literals; namespace nf7 { namespace { @@ -52,8 +61,20 @@ class Null final : public nf7::File, public nf7::Sequencer { } std::shared_ptr CreateLambda( - const std::shared_ptr&) noexcept override { - return nullptr; + const std::shared_ptr& parent) noexcept override { + class Emitter final : public Sequencer::Lambda, + public std::enable_shared_from_this { + public: + using Sequencer::Lambda::Lambda; + + void Run(const std::shared_ptr& ss) noexcept override { + env().ExecAsync(shared_from_this(), [ss]() { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + ss->Finish(); + }); + } + }; + return std::make_shared(*this, parent); } File::Interface* interface(const std::type_info& t) noexcept override { return nf7::InterfaceSelector(t).Select(this); @@ -78,25 +99,31 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node { class Item; class Layer; - class Lambda; class Editor; + class Session; + class Lambda; + + using ItemId = uint64_t; + TL(Env& env, uint64_t length = 1000, std::vector>&& layers = {}, - uint64_t next = 1, + ItemId next = 1, const nf7::gui::Window* win = nullptr) noexcept : nf7::File(kType, env), nf7::DirItem(nf7::DirItem::kMenu), length_(length), layers_(std::move(layers)), next_(next), win_(*this, "Timeline Editor", win), tl_("timeline"), - popup_add_item_(*this) { + popup_add_item_(*this), popup_config_(*this) { } + void SetUpAfterDeserialize(); TL(Env& env, Deserializer& ar) : TL(env) { - ar(length_, layers_, next_, win_, tl_); + ar(length_, layers_, win_, tl_); + SetUpAfterDeserialize(); } void Serialize(Serializer& ar) const noexcept override { - ar(length_, layers_, next_, win_, tl_); + ar(length_, layers_, win_, tl_); } std::unique_ptr Clone(Env& env) const noexcept override; @@ -107,7 +134,8 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node { void Update() noexcept override; void UpdateMenu() noexcept override; - void UpdateEditor() noexcept; + void UpdateEditorWindow() noexcept; + void UpdateLambdaSelector() noexcept; void HandleTimelineAction() noexcept; File::Interface* interface(const std::type_info& t) noexcept override { @@ -117,11 +145,18 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node { private: nf7::SquashedHistory history_; + std::shared_ptr lambda_; + std::vector> lambdas_running_; + // permanentized params uint64_t length_; + uint64_t cursor_; std::vector> layers_; - uint64_t next_; + std::vector seq_inputs_; + std::vector seq_outputs_; + + ItemId next_; nf7::gui::Window win_; nf7::gui::Timeline tl_; @@ -149,6 +184,27 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node { nf7::gui::FileFactory<0> factory_; } popup_add_item_; + struct ConfigPopup final : nf7::gui::Popup { + public: + ConfigPopup(TL& f) noexcept : + Popup("ConfigPopup"), owner_(&f) { + } + + void Open() noexcept { + inputs_ = StringifySocketList(owner_->seq_inputs_); + outputs_ = StringifySocketList(owner_->seq_outputs_); + error_ = ""; + Popup::Open(); + } + void Update() noexcept; + + private: + TL* const owner_; + + std::string inputs_; + std::string outputs_; + std::string error_; + } popup_config_; // GUI temporary params @@ -168,7 +224,6 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node { void ExecApplyLayerOfSelected() noexcept; void MoveDisplayLayerOfSelected(int64_t diff) noexcept; - // history void ExecUnDo() { env().ExecMain( @@ -180,6 +235,55 @@ class TL final : public nf7::File, public nf7::DirItem, public nf7::Node { std::make_shared(*this, "applying commands to redo"), [this]() { history_.ReDo(); }); } + + + // instant running + void MoveCursorTo(uint64_t t) noexcept; + void AttachLambda(const std::shared_ptr&) noexcept; + + // socket operation + void ApplySeqSocketChanges() noexcept { + input_ = seq_inputs_; + output_ = seq_outputs_; + output_.push_back("_cursor"); + Touch(); + } + static std::vector ParseSocketSpecifier(std::string_view names) { + const auto n = names.size(); + + std::vector ret; + size_t begin = 0; + + for (size_t i = 0; i <= n; ++i) { + if (i == n || names[i] == '\n') { + const auto name = names.substr(begin, i-begin); + if (name.size() > 0) { + ret.push_back(std::string {name}); + } + begin = i+1; + continue; + } + } + ValidateSocketList(ret); + return ret; + } + static void ValidateSocketList(const std::vector& a) { + for (size_t i = 0; i < a.size(); ++i) { + File::Path::ValidateTerm(a[i]); + for (size_t j = i+1; j < a.size(); ++j) { + if (a[i] == a[j]) { + throw nf7::Exception {"name duplication: "+a[i]}; + } + } + } + } + static std::string StringifySocketList(const std::vector& a) noexcept { + std::stringstream st; + for (auto& name : a) { + st << name << '\n'; + } + return st.str(); + } }; @@ -228,7 +332,7 @@ struct TL::Timing { class TL::Item final { public: Item() = delete; - Item(uint64_t id, std::unique_ptr&& f, const Timing& t) : + Item(ItemId id, std::unique_ptr&& f, const Timing& t) : id_(id), file_(std::move(f)), seq_(&file_->interfaceOrThrow()), timing_(t), display_timing_(t) { @@ -242,13 +346,13 @@ class TL::Item final { ar(id_, file_, timing_); } static std::unique_ptr Load(auto& ar) noexcept { - uint64_t id; + ItemId id; std::unique_ptr file; Timing timing; ar(id, file, timing); return std::make_unique(id, std::move(file), timing); } - std::unique_ptr Clone(nf7::Env& env, uint64_t id) const { + std::unique_ptr Clone(nf7::Env& env, ItemId id) const { return std::make_unique(id, file_->Clone(env), timing_); } @@ -287,12 +391,8 @@ class TL::Item final { } void Update() noexcept; - bool UpdateResizer(uint64_t& target, float offset, - const std::function& min, - const std::function& max) noexcept; - void UpdateMover(float offset_x) noexcept; - uint64_t id() const noexcept { return id_; } + ItemId id() const noexcept { return id_; } nf7::File& file() const noexcept { return *file_; } TL::Layer& layer() const noexcept { return *layer_; } nf7::Sequencer& seq() const noexcept { return *seq_; } @@ -304,7 +404,7 @@ class TL::Item final { TL* owner_ = nullptr; TL::Layer* layer_ = nullptr; - uint64_t id_; + ItemId id_; std::unique_ptr file_; nf7::Sequencer* const seq_; @@ -343,7 +443,7 @@ class TL::Layer final { ar(items, enabled, height); return std::make_unique(std::move(items), enabled, height); } - std::unique_ptr Clone(Env& env, uint64_t& id) const { + std::unique_ptr Clone(Env& env, ItemId& id) const { std::vector> items; items.reserve(items_.size()); for (auto& item : items_) items.push_back(item->Clone(env, id++)); @@ -474,22 +574,254 @@ class TL::Layer final { size_t index_; float offset_y_; }; +void TL::SetUpAfterDeserialize() { + next_ = 1; + std::unordered_set ids; + for (auto& layer : layers_) { + for (auto& item : layer->items()) { + if (item->id() == 0) { + throw nf7::DeserializeException {"item id cannot be zero"}; + } + if (ids.contains(item->id())) { + throw nf7::DeserializeException {"id duplication"}; + } + ids.insert(item->id()); + next_ = std::max(next_, item->id()+1); + } + } +} -class TL::Lambda final : public Node::Lambda { +class TL::Lambda final : public Node::Lambda, + public std::enable_shared_from_this { public: Lambda() = delete; Lambda(TL& f, const std::shared_ptr& parent) noexcept : - Node::Lambda(f, parent) { + Node::Lambda(f, parent), owner_(&f) { } void Handle(std::string_view, const nf7::Value&, - const std::shared_ptr&) noexcept override { + const std::shared_ptr&) noexcept override; + + std::shared_ptr CreateSession(uint64_t t) noexcept { + auto ss = std::make_shared(shared_from_this(), last_session_, t, vars_); + last_session_ = ss; + sessions_.erase( + std::remove_if( + sessions_.begin(), sessions_.end(), + [](auto& x) { return x.expired(); }), + sessions_.end()); + sessions_.push_back(ss); + return ss; } + + std::pair> GetNext( + uint64_t& layer_idx, uint64_t layer_until, uint64_t t) noexcept { + if (aborted_ || !env().GetFile(initiator())) { + return {nullptr, nullptr}; + } + layer_until = std::min(layer_until, owner_->layers_.size()); + for (; layer_idx < layer_until; ++layer_idx) { + auto& layer = *owner_->layers_[layer_idx]; + if (!layer.enabled()) { + continue; + } + if (auto item = layer.GetAt(t)) { + auto itr = lambdas_.find(item->id()); + if (itr == lambdas_.end()) { + auto la = item->seq().CreateLambda(shared_from_this()); + lambdas_.emplace(item->id(), la); + return {item, la}; + } else { + return {item, itr->second}; + } + } + } + return {nullptr, nullptr}; + } + + void EmitCursor(uint64_t t) noexcept { + if (auto p = parent()) { + p->Handle("_cursor", static_cast(t), shared_from_this()); + } + } + void EmitResults(const std::unordered_map& vars) noexcept { + if (!env().GetFile(initiator())) { + return; + } + auto caller = parent(); + for (const auto& name : owner_->seq_outputs_) { + auto itr = vars.find(name); + if (itr == vars.end()) continue; + caller->Handle(name, itr->second, shared_from_this()); + } + } + + void Abort() noexcept { + aborted_ = true; + } + + std::span> sessions() const noexcept { + return sessions_; + } + + private: + TL* const owner_; + + std::atomic aborted_ = false; + + std::unordered_map vars_; + std::unordered_map> lambdas_; + + std::weak_ptr last_session_; + std::vector> sessions_; }; std::shared_ptr TL::CreateLambda( const std::shared_ptr& parent) noexcept { - return std::make_shared(*this, parent); + auto la = std::make_shared(*this, parent); + + lambdas_running_.erase( + std::remove_if(lambdas_running_.begin(), lambdas_running_.end(), + [](auto& x) { return x.expired(); }), + lambdas_running_.end());; + lambdas_running_.emplace_back(la); + return la; +} + + +class TL::Session final : public Sequencer::Session, + public std::enable_shared_from_this { + public: + Session(const std::shared_ptr& initiator, + const std::weak_ptr& leader, + uint64_t time, const std::unordered_map& vars) noexcept : + env_(&initiator->env()), last_active_(std::chrono::system_clock::now()), + initiator_(initiator), leader_(leader), + time_(time), vars_(vars) { + } + + const nf7::Value& Peek(std::string_view name) override { + auto itr = vars_.find(std::string {name}); + if (itr == vars_.end()) { + throw UnknownNameException {std::string {name}+" is unknown"}; + } + return itr->second; + } + nf7::Value Receive(std::string_view name) override { + auto itr = vars_.find(std::string {name}); + if (itr == vars_.end()) { + throw UnknownNameException {std::string {name}+" is unknown"}; + } + auto ret = std::move(itr->second); + vars_.erase(itr); + return std::move(itr->second); + } + + void Send(std::string_view name, nf7::Value&& v) noexcept override { + vars_[std::string {name}] = std::move(v); + } + + void StartNext() noexcept { + auto leader = leader_.lock(); + + uint64_t layer_until = UINT64_MAX; + if (leader && !leader->done_) { + layer_until = leader->layer_; + } else { + leader = nullptr; + } + + auto [item, lambda] = initiator_->GetNext(layer_, layer_until, time_); + if (item) { + assert(lambda); + + const auto& t = item->timing(); + info_.time = time_; + info_.begin = t.begin(); + info_.end = t.end(); + lambda->Run(shared_from_this()); + + last_active_ = std::chrono::system_clock::now(); + ++layer_; + + } else if (leader) { + assert(!leader->follower_); + leader->follower_ = shared_from_this(); + + } else { + done_ = true; + initiator_->EmitResults(vars_); + } + + if (auto follower = std::exchange(follower_, nullptr)) { + follower->StartNext(); + } + } + void Finish() noexcept override { + auto self = shared_from_this(); + env_->ExecSub(initiator_, [self]() { self->StartNext(); }); + } + + const Info& info() const noexcept override { return info_; } + + std::chrono::system_clock::time_point lastActive() const noexcept { return last_active_; } + bool done() const noexcept { return done_; } + uint64_t time() const noexcept { return time_; } + uint64_t layer() const noexcept { return layer_; } + + private: + nf7::Env* const env_; + std::chrono::system_clock::time_point last_active_; + + std::shared_ptr initiator_; + + std::weak_ptr leader_; + std::shared_ptr follower_; + + const uint64_t time_; + std::unordered_map vars_; + + bool done_ = false; + uint64_t layer_ = 0; + Info info_; +}; +void TL::Lambda::Handle(std::string_view name, const nf7::Value& v, + const std::shared_ptr&) noexcept { + if (name == "exec") { + if (!env().GetFile(initiator())) return; + const auto t_max = owner_->length_-1; + + uint64_t t; + if (v.isInteger()) { + const auto ti = std::clamp(v.integer(), int64_t{0}, static_cast(t_max)); + t = static_cast(ti); + } else if (v.isScalar()) { + const auto tf = std::clamp(v.scalar(), 0., 1.)*static_cast(t_max); + t = static_cast(tf); + } else { + // TODO: error + return; + } + CreateSession(t)->StartNext(); + } else { + vars_[std::string {name}] = v; + } +} +void TL::MoveCursorTo(uint64_t time) noexcept { + cursor_ = std::min(time, length_-1); + + if (!lambda_) { + AttachLambda(std::make_shared(*this, nullptr)); + } + lambda_->CreateSession(time)->StartNext(); + lambda_->EmitCursor(time); +} +void TL::AttachLambda(const std::shared_ptr& la) noexcept { + if (la == lambda_) return; + if (lambda_ && lambda_->depth() == 0) { + lambda_->Abort(); + } + lambda_ = la; } @@ -825,7 +1157,7 @@ void TL::MoveDisplayLayerOfSelected(int64_t diff) noexcept { std::unique_ptr TL::Clone(nf7::Env& env) const noexcept { std::vector> layers; layers.reserve(layers_.size()); - uint64_t next = 1; + ItemId next = 1; for (const auto& layer : layers_) layers.push_back(layer->Clone(env, next)); return std::make_unique(env, length_, std::move(layers), next, &win_); } @@ -861,19 +1193,25 @@ void TL::Handle(const Event& ev) noexcept { void TL::Update() noexcept { popup_add_item_.Update(); - UpdateEditor(); + popup_config_.Update(); + + UpdateEditorWindow(); + history_.Squash(); } void TL::UpdateMenu() noexcept { ImGui::MenuItem("Editor", nullptr, &win_.shown()); } -void TL::UpdateEditor() noexcept { +void TL::UpdateEditorWindow() noexcept { const auto kInit = []() { const auto em = ImGui::GetFontSize(); ImGui::SetNextWindowSizeConstraints({24*em, 8*em}, {1e8, 1e8}); }; if (win_.Begin(kInit)) { + UpdateLambdaSelector(); + + // timeline if (tl_.Begin(length_)) { // layer headers for (size_t i = 0; i < layers_.size(); ++i) { @@ -899,6 +1237,10 @@ void TL::UpdateEditor() noexcept { if (ImGui::MenuItem("redo", nullptr, false, !!history_.next())) { ExecReDo(); } + ImGui::Separator(); + if (ImGui::MenuItem("config")) { + popup_config_.Open(); + } ImGui::EndPopup(); } @@ -922,56 +1264,136 @@ void TL::UpdateEditor() noexcept { ImGuiHoveredFlags_AllowWhenBlockedByPopup; if (ImGui::IsWindowHovered(kFlags)) { tl_.Cursor( - "cursor", + "mouse", std::min(length_-1, tl_.GetTimeFromScreenX(ImGui::GetMousePos().x)), ImGui::GetColorU32(ImGuiCol_TextDisabled, .5f)); } + // frame cursor + tl_.Cursor("cursor", cursor_, ImGui::GetColorU32(ImGuiCol_Text, .5f)); + // end of timeline tl_.Cursor("END", length_, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + // running sessions + if (lambda_) { + const auto now = std::chrono::system_clock::now(); + for (auto& wss : lambda_->sessions()) { + auto ss = wss.lock(); + if (!ss || ss->done()) continue; + + const auto elapsed = + static_cast( + std::chrono::duration_cast( + now - ss->lastActive()).count()) / 1000; + + const auto alpha = 1.f - std::clamp(elapsed, 0.f, 1.f)*0.6f; + const auto color = IM_COL32(255, 0, 0, static_cast(alpha*255)); + + tl_.Cursor("S", ss->time(), color); + if (ss->layer() > 0) { + tl_.Arrow(ss->time(), ss->layer()-1, color); + } + } + } + HandleTimelineAction(); } tl_.End(); + + // key bindings + const bool focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); + if (focused && !ImGui::IsAnyItemFocused()) { + if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) { + if (cursor_ > 0) MoveCursorTo(cursor_-1); + } else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) { + MoveCursorTo(cursor_+1); + } + } } win_.End(); } -void TL::HandleTimelineAction() noexcept { - auto item = reinterpret_cast(tl_.actionTarget()); - if (!item) return; +void TL::UpdateLambdaSelector() noexcept { + const auto current_lambda = + lambda_? nf7::gui::GetParentContextDisplayName(*lambda_): "(unselected)"; + if (ImGui::BeginCombo("##lambda", current_lambda.c_str())) { + if (lambda_) { + if (ImGui::Selectable("detach from current lambda")) { + AttachLambda(nullptr); + } + ImGui::Separator(); + } + for (const auto& wptr : lambdas_running_) { + auto ptr = wptr.lock(); + if (!ptr) continue; + + const auto name = nf7::gui::GetParentContextDisplayName(*ptr); + if (ImGui::Selectable(name.c_str(), ptr == lambda_)) { + AttachLambda(ptr); + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted("call stack:"); + ImGui::Indent(); + nf7::gui::ContextStack(*ptr); + ImGui::Unindent(); + ImGui::EndTooltip(); + } + } + ImGui::EndCombo(); + } +} +void TL::HandleTimelineAction() noexcept { + auto item = reinterpret_cast(tl_.actionTarget()); + const auto action_time = tl_.actionTime(); + const auto action_time_i = static_cast(action_time); - const auto& t = item->displayTiming(); switch (tl_.action()) { case tl_.kSelect: + assert(item); item->Select(); break; case tl_.kResizeBegin: + assert(item); ResizeDisplayTimingOfSelected( - static_cast(tl_.gripTime()) - static_cast(t.begin()), 0); + action_time_i - static_cast(item->displayTiming().begin()), + 0); break; case tl_.kResizeEnd: + assert(item); ResizeDisplayTimingOfSelected( - 0, static_cast(tl_.gripTime()+t.dur()) - static_cast(t.end())); + 0, + action_time_i + + static_cast(item->displayTiming().dur()) - + static_cast(item->displayTiming().end())); break; case tl_.kResizeBeginDone: case tl_.kResizeEndDone: + assert(item); ExecApplyTimingOfSelected(); break; case tl_.kMove: + assert(item); MoveDisplayTimingOfSelected( - static_cast(tl_.gripTime()) - static_cast(t.begin())); + action_time_i - static_cast(item->displayTiming().begin())); if (auto layer = reinterpret_cast(tl_.mouseLayer())) { MoveDisplayLayerOfSelected( - static_cast(layer->index()) - static_cast(item->displayLayer().index())); + static_cast(layer->index()) - + static_cast(item->displayLayer().index())); } break; case tl_.kMoveDone: + assert(item); ExecApplyTimingOfSelected(); ExecApplyLayerOfSelected(); break; + case tl_.kSetTime: + MoveCursorTo(action_time); + break; + case tl_.kNone: break; } @@ -1101,6 +1523,36 @@ void TL::AddItemPopup::Update() noexcept { ImGui::EndPopup(); } } +void TL::ConfigPopup::Update() noexcept { + if (Popup::Begin()) { + ImGui::TextUnformatted("Sequencer/Timeline: updating config..."); + ImGui::InputTextMultiline("inputs", &inputs_); + ImGui::InputTextMultiline("outputs", &outputs_); + + if (ImGui::Button("ok")) { + try { + auto i = ParseSocketSpecifier(inputs_); + auto o = ParseSocketSpecifier(outputs_); + ImGui::CloseCurrentPopup(); + + auto ctx = std::make_shared( + *owner_, "updating Sequencer/Timeline config"); + owner_->env().ExecMain(ctx, [f = owner_, i = std::move(i), o = std::move(o)]() { + f->seq_inputs_ = std::move(i); + f->seq_outputs_ = std::move(o); + f->ApplySeqSocketChanges(); + }); + } catch (nf7::Exception& e) { + error_ = e.msg(); + } + } + if (error_.size() > 0) { + ImGui::Bullet(); + ImGui::TextUnformatted(error_.c_str()); + } + ImGui::EndPopup(); + } +} } } // namespace nf7 diff --git a/main.cc b/main.cc index 596d7e9..397edb9 100644 --- a/main.cc +++ b/main.cc @@ -59,8 +59,8 @@ class Env final : public nf7::Env { try { yas::load("root.nf7", root_); root_->MakeAsRoot(); - } catch (yas::io_exception&) { - throw Exception("failed to read: "s+kFileName); + } catch (std::exception& e) { + throw Exception("failed to read: "s+kFileName+" ("+e.what()+")"); } } catch (Exception&) { Panic();