Introducing multiple levels of Source Failure

This allows Mixer manager to deal with failed sources with the appropriate action: try to repair, leave for user to recreate, or delete.
This commit is contained in:
Bruno Herbelin
2023-03-22 22:50:08 +01:00
parent 9b4ef00278
commit f91522fc14
18 changed files with 159 additions and 127 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -26,15 +26,10 @@
#include <gst/gst.h>
#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)

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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<RenderSource *>(*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 );
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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
{

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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