diff --git a/src/CloneSource.cpp b/src/CloneSource.cpp index 22d5de6..49b235f 100644 --- a/src/CloneSource.cpp +++ b/src/CloneSource.cpp @@ -61,6 +61,12 @@ CloneSource::~CloneSource() delete filter_; } +void CloneSource::detach() +{ + Log::Info("Source '%s' detached from '%s'.", name().c_str(), origin_->name().c_str() ); + origin_ = nullptr; +} + void CloneSource::init() { if (origin_ && origin_->ready_ && origin_->mode_ > Source::UNINITIALIZED && origin_->renderbuffer_) { @@ -225,6 +231,11 @@ uint CloneSource::texture() const return Resource::getTextureBlack(); } +Source::Failure CloneSource::failed() const +{ + return (origin_ == nullptr || origin_->failed()) ? FAIL_FATAL : FAIL_NONE; +} + void CloneSource::accept(Visitor& v) { Source::accept(v); diff --git a/src/CloneSource.h b/src/CloneSource.h index 6585cd2..5fab548 100644 --- a/src/CloneSource.h +++ b/src/CloneSource.h @@ -22,14 +22,14 @@ public: void replay () override; guint64 playtime () const override; uint texture() const override; - bool failed() const override { return origin_ == nullptr || origin_->failed(); } + Failure failed() const override; void accept (Visitor& v) override; void render() override; glm::ivec2 icon() const override; std::string info() const override; // implementation of cloning mechanism - inline void detach() { origin_ = nullptr; } + void detach(); inline Source *origin() const { return origin_; } // Filtering diff --git a/src/DeviceSource.cpp b/src/DeviceSource.cpp index f460b56..85b063f 100644 --- a/src/DeviceSource.cpp +++ b/src/DeviceSource.cpp @@ -26,15 +26,10 @@ #include -#include "defines.h" #include "Log.h" -#include "ImageShader.h" -#include "ImageProcessingShader.h" -#include "Resource.h" #include "Decorations.h" #include "Stream.h" #include "Visitor.h" -#include "CloneSource.h" #include "DeviceSource.h" @@ -525,9 +520,9 @@ void DeviceSource::accept(Visitor& v) v.visit(*this); } -bool DeviceSource::failed() const +Source::Failure DeviceSource::failed() const { - return unplugged_ || StreamSource::failed(); + return (unplugged_ || StreamSource::failed()) ? FAIL_CRITICAL : FAIL_NONE; } DeviceConfigSet Device::getDeviceConfigs(const std::string &src_description) diff --git a/src/DeviceSource.h b/src/DeviceSource.h index 59181e8..ff70148 100644 --- a/src/DeviceSource.h +++ b/src/DeviceSource.h @@ -15,7 +15,7 @@ public: ~DeviceSource(); // Source interface - bool failed() const override; + Failure failed() const override; void accept (Visitor& v) override; void setActive (bool on) override; diff --git a/src/ImGuiVisitor.cpp b/src/ImGuiVisitor.cpp index 5d0576b..793afa5 100644 --- a/src/ImGuiVisitor.cpp +++ b/src/ImGuiVisitor.cpp @@ -443,9 +443,9 @@ void ImGuiVisitor::visit (Source& s) ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y ) ); if (s.active()) { if (s.blendingShader()->color.a > 0.f) - ImGuiToolkit::Indication("Visible", ICON_FA_EYE); + ImGuiToolkit::Indication("Visible", ICON_FA_SUN); else - ImGuiToolkit::Indication("Not visible", ICON_FA_EYE_SLASH); + ImGuiToolkit::Indication("Not visible", ICON_FA_CLOUD_SUN); } else ImGuiToolkit::Indication("Inactive", ICON_FA_SNOWFLAKE); @@ -1142,8 +1142,9 @@ void ImGuiVisitor::visit (CloneSource& s) ImGui::Text("%s", info.str().c_str()); ImGui::PopTextWrapPos(); - // link to origin source if ( !s.failed() ) { + + // link to origin source std::string label = std::string(s.origin()->initials()) + " - " + s.origin()->name(); if (ImGui::Button(label.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) )) { Mixer::manager().setCurrentSource(s.origin()); @@ -1153,49 +1154,46 @@ void ImGuiVisitor::visit (CloneSource& s) } ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::Text("Origin"); - } - else { - ImGuiToolkit::ButtonDisabled("No source", ImVec2(IMGUI_RIGHT_ALIGN, 0) ); + + // filter selection + std::ostringstream oss; + oss << s.name(); + int type = (int) s.filter()->type(); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGuiToolkit::ComboIcon("##SelectFilter", &type, FrameBufferFilter::Types)) { + s.setFilter( FrameBufferFilter::Type(type) ); + oss << ": Filter " << std::get<2>(FrameBufferFilter::Types[type]); + Action::manager().store(oss.str()); + info.reset(); + } ImGui::SameLine(0, IMGUI_SAME_LINE); - ImGui::Text("Origin"); - } + if (ImGuiToolkit::TextButton("Filter")) { + s.setFilter( FrameBufferFilter::FILTER_PASSTHROUGH ); + oss << ": Filter None"; + Action::manager().store(oss.str()); + info.reset(); + } - // filter selection - std::ostringstream oss; - oss << s.name(); - int type = (int) s.filter()->type(); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGuiToolkit::ComboIcon("##SelectFilter", &type, FrameBufferFilter::Types)) { - s.setFilter( FrameBufferFilter::Type(type) ); - oss << ": Filter " << std::get<2>(FrameBufferFilter::Types[type]); - Action::manager().store(oss.str()); - info.reset(); - } - ImGui::SameLine(0, IMGUI_SAME_LINE); - if (ImGuiToolkit::TextButton("Filter")) { - s.setFilter( FrameBufferFilter::FILTER_PASSTHROUGH ); - oss << ": Filter None"; - Action::manager().store(oss.str()); - info.reset(); - } + // filter options + s.filter()->accept(*this); - // filter options - s.filter()->accept(*this); + ImVec2 botom = ImGui::GetCursorPos(); - ImVec2 botom = ImGui::GetCursorPos(); - - if ( !s.failed() ) { // icon (>) to open player if ( s.playable() ) { ImGui::SetCursorPos(top); if (ImGuiToolkit::IconButton(ICON_FA_PLAY_CIRCLE, "Open in Player")) UserInterface::manager().showSourceEditor(&s); } - } - else - info.reset(); - ImGui::SetCursorPos(botom); + ImGui::SetCursorPos(botom); + } + else { + ImGuiToolkit::ButtonDisabled("No source", ImVec2(IMGUI_RIGHT_ALIGN, 0) ); + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGui::Text("Origin"); + info.reset(); + } } void ImGuiVisitor::visit (PatternSource& s) @@ -1254,25 +1252,26 @@ void ImGuiVisitor::visit (DeviceSource& s) ImGui::Text("%s", info.str().c_str()); ImGui::PopTextWrapPos(); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("Device", s.device().c_str())) - { - for (int d = 0; d < Device::manager().numDevices(); ++d){ - std::string namedev = Device::manager().name(d); - if (ImGui::Selectable( namedev.c_str() )) { - s.setDevice(namedev); - info.reset(); - std::ostringstream oss; - oss << s.name() << " Device " << namedev; - Action::manager().store(oss.str()); - } - } - ImGui::EndCombo(); - } - ImVec2 botom = ImGui::GetCursorPos(); if ( !s.failed() ) { + + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::BeginCombo("Device", s.device().c_str())) + { + for (int d = 0; d < Device::manager().numDevices(); ++d){ + std::string namedev = Device::manager().name(d); + if (ImGui::Selectable( namedev.c_str() )) { + s.setDevice(namedev); + info.reset(); + std::ostringstream oss; + oss << s.name() << " Device " << namedev; + Action::manager().store(oss.str()); + } + } + ImGui::EndCombo(); + } + // icon (>) to open player if ( s.playable() ) { ImGui::SetCursorPos(top); @@ -1427,21 +1426,22 @@ void ImGuiVisitor::visit (GenericStreamSource& s) ImGui::Text("%s", info.str().c_str()); ImGui::PopTextWrapPos(); - // Prepare display pipeline text - static int numlines = 0; - const ImGuiContext& g = *GImGui; - ImVec2 fieldsize(w, MAX(3, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y); - - // Editor - std::string _description = s.description(); - if ( ImGuiToolkit::InputCodeMultiline("Pipeline", &_description, fieldsize, &numlines) ) { - s.setDescription(_description); - Action::manager().store( s.name() + ": Change pipeline"); - } - ImVec2 botom = ImGui::GetCursorPos(); if ( !s.failed() ) { + + // Prepare display pipeline text + static int numlines = 0; + const ImGuiContext& g = *GImGui; + ImVec2 fieldsize(w, MAX(3, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y); + + // Editor + std::string _description = s.description(); + if ( ImGuiToolkit::InputCodeMultiline("Pipeline", &_description, fieldsize, &numlines) ) { + s.setDescription(_description); + Action::manager().store( s.name() + ": Change pipeline"); + } + // icon (>) to open player if ( s.playable() ) { ImGui::SetCursorPos(top); diff --git a/src/MediaSource.cpp b/src/MediaSource.cpp index 1a6ee83..5dfa020 100644 --- a/src/MediaSource.cpp +++ b/src/MediaSource.cpp @@ -78,9 +78,9 @@ std::string MediaSource::info() const return "Video File"; } -bool MediaSource::failed() const +Source::Failure MediaSource::failed() const { - return mediaplayer_->failed(); + return mediaplayer_->failed() ? FAIL_CRITICAL : FAIL_NONE; } uint MediaSource::texture() const diff --git a/src/MediaSource.h b/src/MediaSource.h index a7bc64a..40ad483 100644 --- a/src/MediaSource.h +++ b/src/MediaSource.h @@ -20,7 +20,7 @@ public: void replay () override; guint64 playtime () const override; void render() override; - bool failed() const override; + Failure failed() const override; uint texture() const override; void accept (Visitor& v) override; diff --git a/src/Mixer.cpp b/src/Mixer.cpp index 50a5ab0..413c0bf 100644 --- a/src/Mixer.cpp +++ b/src/Mixer.cpp @@ -205,25 +205,37 @@ void Mixer::update() FrameGrabbing::manager().grabFrame(session_->frame()); // manage sources which failed update - SourceListUnique failures = session()->failedSources(); - for(auto it = failures.begin(); it != failures.end(); ++it) { - // if the failed source is still attached to the mixer - if ( attached( *it ) ) { - // special case of failed Render loopback : - // it fails when resolution change, and we can fix it by - // recreating it in the current session - RenderSource *failedRender = dynamic_cast(*it); - if (failedRender != nullptr) { - // try to recreate the failed render source - if ( !recreateSource(failedRender) ) - // delete the source if could not - deleteSource(failedRender); + if (session_->ready()) { + // go through all failed sources + SourceListUnique _failedsources = session_->failedSources(); + for(auto it = _failedsources.begin(); it != _failedsources.end(); ++it) { + // only deal with sources that are still attached to mixer + if ( attached( *it ) ) { + // intervention depends on the severity of the failure + Source::Failure fail = (*it)->failed(); + // Attempt to repair BAD failed sources + // (can be automatically repaired without user intervention) + if (fail == Source::FAIL_BAD) { + if ( !recreateSource( *it ) ) { + Log::Warning("Source '%s' failed and was deleted.", (*it)->name().c_str()); + // delete failed source if could not recreate it + deleteSource( *it ); + } + } + // Detatch CRITICAL failed sources from the mixer + // (not deleted in the session; user can replace it) + else if (fail == Source::FAIL_CRITICAL) { + detach( *it ); + } + // Delete FATAL failed sources from the mixer + // (nothing can be done by the user) + else { + Log::Warning("Source '%s' failed and was deleted.", (*it)->name().c_str()); + deleteSource( *it ); + } + // needs refresh after intervention + ++View::need_deep_update_; } - // general case: - // detatch failed sources from the mixer - // (not deleted in the session; user can replace it) - else - detach( *it ); } } diff --git a/src/RenderSource.cpp b/src/RenderSource.cpp index c296d9d..be5f7be 100644 --- a/src/RenderSource.cpp +++ b/src/RenderSource.cpp @@ -47,12 +47,12 @@ RenderSource::~RenderSource() delete rendered_output_; } -bool RenderSource::failed() const +Source::Failure RenderSource::failed() const { if ( rendered_output_ != nullptr && session_ != nullptr ) - return rendered_output_->resolution() != session_->frame()->resolution(); + return rendered_output_->resolution() != session_->frame()->resolution() ? FAIL_BAD : FAIL_NONE; - return false; + return FAIL_NONE; } uint RenderSource::texture() const diff --git a/src/RenderSource.h b/src/RenderSource.h index 8899a22..b9a9987 100644 --- a/src/RenderSource.h +++ b/src/RenderSource.h @@ -18,7 +18,7 @@ public: void replay () override {} bool playable () const override { return true; } guint64 playtime () const override { return runtime_; } - bool failed () const override; + Failure failed () const override; uint texture() const override; void accept (Visitor& v) override; diff --git a/src/Session.cpp b/src/Session.cpp index b8536d2..edcca81 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -44,7 +44,7 @@ SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_str } Session::Session(uint64_t id) : id_(id), active_(true), activation_threshold_(MIXING_MIN_THRESHOLD), - filename_(""), thumbnail_(nullptr) + filename_(""), thumbnail_(nullptr), ready_(false) { // create unique id if (id_ == 0) @@ -138,6 +138,14 @@ void Session::setActive (bool on) } } + +void Session::deleteFailedSources () +{ + while (!failed_.empty()) { + deleteSource( *(failed_.begin()) ); + } +} + // update all sources void Session::update(float dt) { @@ -234,7 +242,7 @@ void Session::update(float dt) } // pre-render all sources - bool ready = true; + ready_ = true; for( SourceList::iterator it = sources_.begin(); it != sources_.end(); ++it){ // ensure the RenderSource is rendering *this* session @@ -244,17 +252,20 @@ void Session::update(float dt) // discard failed source if ( (*it)->failed() ) { - // insert source in list of failed - // (NB: insert in set fails if source is already listed) - failed_.insert( *it ); - // do not render - render_.scene.ws()->detach( (*it)->group(View::RENDERING) ); + // if source is already attached + if ((*it)->group(View::RENDERING)->refcount_ > 0) { + // insert source in list of failed + // (NB: insert in set fails if source is already listed) + failed_.insert( *it ); + // detatch from rendering (do not render) + render_.scene.ws()->detach( (*it)->group(View::RENDERING) ); + } } // render normally else { // session is not ready if one source is not ready if ( !(*it)->ready() ) - ready = false; + ready_ = false; // update the source (*it)->setActive(activation_threshold_); (*it)->update(dt); @@ -307,7 +318,7 @@ void Session::update(float dt) render_.draw(); // draw the thumbnail only after all sources are ready - if (ready) + if (ready_) render_.drawThumbnail(); } @@ -589,9 +600,9 @@ bool Session::canlink (SourceList sources) validate(sources); for (auto it = sources.begin(); it != sources.end(); ++it) { - // this source is linked + // if this source is linked if ( (*it)->mixingGroup() != nullptr ) { - // askt its group to detach it + // ask its group to detach it canlink = false; } } diff --git a/src/Session.h b/src/Session.h index 7797eaa..f7ac14d 100644 --- a/src/Session.h +++ b/src/Session.h @@ -88,6 +88,7 @@ public: uint numSources() const; // update all sources and mark sources which failed + inline bool ready () const { return ready_; } void update (float dt); uint64_t runtime() const; @@ -97,6 +98,7 @@ public: // return the list of sources which failed SourceListUnique failedSources () const { return failed_; } + void deleteFailedSources (); // get frame result of render inline FrameBuffer *frame () const { return render_.frame(); } @@ -195,6 +197,7 @@ protected: std::mutex access_; FrameBufferImage *thumbnail_; uint64_t start_time_; + bool ready_; struct Fading { diff --git a/src/SessionSource.cpp b/src/SessionSource.cpp index 0213a12..3694dc8 100644 --- a/src/SessionSource.cpp +++ b/src/SessionSource.cpp @@ -113,9 +113,9 @@ Session *SessionSource::detach() return giveaway; } -bool SessionSource::failed() const +Source::Failure SessionSource::failed() const { - return failed_; + return failed_ ? FAIL_CRITICAL : FAIL_NONE; } uint SessionSource::texture() const @@ -149,15 +149,9 @@ void SessionSource::update(float dt) timer_ += guint64(dt * 1000.f) * GST_USECOND; } - // delete source which failed - if ( !session_->failedSources().empty() ) { - Source *failure = *(session_->failedSources().cbegin()); - Log::Info("Source '%s' deleted from Child Session %s.", failure->name().c_str(), std::to_string(session_->id()).c_str()); - session_->deleteSource( failure ); - // fail session if all sources failed - if ( session_->size() < 1) - failed_ = true; - } + // fail session if all its sources failed + if ( session_->failedSources().size() == session_->size() ) + failed_ = true; } diff --git a/src/SessionSource.h b/src/SessionSource.h index 3f8bc42..86eec92 100644 --- a/src/SessionSource.h +++ b/src/SessionSource.h @@ -19,7 +19,7 @@ public: bool playable () const override; guint64 playtime () const override { return timer_; } void replay () override; - bool failed () const override; + Failure failed () const override; uint texture () const override; Session *detach(); diff --git a/src/Source.h b/src/Source.h index d166d99..5214842 100644 --- a/src/Source.h +++ b/src/Source.h @@ -177,7 +177,13 @@ public: virtual guint64 playtime () const { return 0; } // a Source shall informs if the source failed (i.e. shall be deleted) - virtual bool failed () const = 0; + typedef enum { + FAIL_NONE = 0, + FAIL_BAD= 1, + FAIL_CRITICAL = 2, + FAIL_FATAL = 3 + } Failure; + virtual Failure failed () const = 0; // a Source shall define a way to get a texture virtual uint texture () const = 0; @@ -251,7 +257,7 @@ public: } static bool isInitialized (const Source* elem) { - return (elem && elem->mode_ > Source::UNINITIALIZED); + return (elem && ( elem->mode_ > Source::UNINITIALIZED || elem->failed() ) ); } // class-dependent icon diff --git a/src/SourceList.cpp b/src/SourceList.cpp index 6d10308..69c9815 100644 --- a/src/SourceList.cpp +++ b/src/SourceList.cpp @@ -46,7 +46,7 @@ SourceList playable_only (const SourceList &list) } -bool isfailed (const Source *s) { return s->failed(); } +bool isfailed (const Source *s) { return s->failed() != Source::FAIL_NONE; } SourceList valid_only (const SourceList &list) { diff --git a/src/StreamSource.cpp b/src/StreamSource.cpp index aca0d35..c1471bc 100644 --- a/src/StreamSource.cpp +++ b/src/StreamSource.cpp @@ -93,9 +93,9 @@ StreamSource::~StreamSource() delete stream_; } -bool StreamSource::failed() const +Source::Failure StreamSource::failed() const { - return (stream_ != nullptr && stream_->failed() ); + return (stream_ != nullptr && stream_->failed()) ? FAIL_CRITICAL : FAIL_NONE; } uint StreamSource::texture() const diff --git a/src/StreamSource.h b/src/StreamSource.h index 870e476..18ed873 100644 --- a/src/StreamSource.h +++ b/src/StreamSource.h @@ -35,7 +35,7 @@ public: bool playable () const override; void replay () override; guint64 playtime () const override; - bool failed() const override; + Failure failed() const override; uint texture() const override; // pure virtual interface