From e3569b6469525d6d43cab8ae03694ce213145e41 Mon Sep 17 00:00:00 2001 From: Luciano Iam Date: Sun, 13 Jun 2021 20:54:43 +0200 Subject: [PATCH] WebSockets: notify server there are pending client updates This is an update to the surface ArdourFeedback class that is needed to support the new event loop integration method. The various session event callbacks cannot be queued in the surface event loop because that would create a delay between the time such events are fired and the time for writing to clients arrive, due to lws_service() blocking while it waits to read. To solve this issue a helper AbstractUI is created for catching events as soon as possible and issuing a call to lws_cancel_service(). See WebsocketsServer::glib_idle_callback() --- libs/surfaces/websockets/ardour_websockets.cc | 2 +- libs/surfaces/websockets/ardour_websockets.h | 2 +- libs/surfaces/websockets/feedback.cc | 59 ++++++++++++++++++- libs/surfaces/websockets/feedback.h | 18 +++++- libs/surfaces/websockets/server.cc | 1 + libs/surfaces/websockets/server.h | 7 ++- 6 files changed, 83 insertions(+), 6 deletions(-) diff --git a/libs/surfaces/websockets/ardour_websockets.cc b/libs/surfaces/websockets/ardour_websockets.cc index 61129dc1e8..adefb26174 100644 --- a/libs/surfaces/websockets/ardour_websockets.cc +++ b/libs/surfaces/websockets/ardour_websockets.cc @@ -39,8 +39,8 @@ ArdourWebsockets::ArdourWebsockets (Session& s) , AbstractUI (name ()) , _mixer (*this) , _transport (*this) - , _feedback (*this) , _server (*this) + , _feedback (*this) , _dispatcher (*this) { _components.push_back (&_mixer); diff --git a/libs/surfaces/websockets/ardour_websockets.h b/libs/surfaces/websockets/ardour_websockets.h index 53f64e10f5..2c71d09ed4 100644 --- a/libs/surfaces/websockets/ardour_websockets.h +++ b/libs/surfaces/websockets/ardour_websockets.h @@ -95,8 +95,8 @@ protected: private: ArdourMixer _mixer; ArdourTransport _transport; - ArdourFeedback _feedback; WebsocketsServer _server; + ArdourFeedback _feedback; WebsocketsDispatcher _dispatcher; std::vector _components; diff --git a/libs/surfaces/websockets/feedback.cc b/libs/surfaces/websockets/feedback.cc index 5fa42a7cca..9542c1beff 100644 --- a/libs/surfaces/websockets/feedback.cc +++ b/libs/surfaces/websockets/feedback.cc @@ -20,6 +20,8 @@ #include "ardour/session.h" #include "ardour/tempo.h" +#include "pbd/abstract_ui.cc" // instantiate template + #include "feedback.h" #include "transport.h" #include "server.h" @@ -28,9 +30,20 @@ // TO DO: make this configurable #define POLL_INTERVAL_MS 100 +#define OPTIONAL_CONNECT_HELPER(s,c) if (server ().should_request_write ()) \ + s.connect (c, MISSING_INVALIDATOR, boost::bind \ + (ServerWriteObserver (), &server ()), &_helper); + using namespace ARDOUR; using namespace ArdourSurface; +struct ServerWriteObserver { + void operator() (WebsocketsServer *server) + { + server->request_write (); + } +}; + struct TransportObserver { void operator() (ArdourFeedback* p) { @@ -97,6 +110,24 @@ struct PluginParamValueObserver { } }; +FeedbackHelperUI::FeedbackHelperUI() + : AbstractUI ("feedback_helper") +{ + pthread_set_name ("test_ui_thread"); // FIXME - needed? + run_loop_thread = Glib::Threads::Thread::self (); + set_event_loop_for_thread (this); + ARDOUR::SessionEvent::create_per_thread_pool ("test", 512); // FIXME - needed? +} + +void +FeedbackHelperUI::do_request (BaseUI::BaseRequestObject* req) { + if (req->type == CallSlot) { + call_slot (MISSING_INVALIDATOR, req->the_slot); + } else if (req->type == Quit) { + quit (); + } +}; + int ArdourFeedback::start () { @@ -107,7 +138,15 @@ ArdourFeedback::start () Glib::RefPtr periodic_timeout = Glib::TimeoutSource::create (POLL_INTERVAL_MS); _periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &ArdourFeedback::poll)); - periodic_timeout->attach (main_loop ()->get_context ()); + + // server must be started before feedback otherwise + // should_request_write() will always return false + if (!server ().should_request_write ()) { + periodic_timeout->attach (main_loop ()->get_context ()); + } else { + _helper.run(); + periodic_timeout->attach (_helper.main_loop()->get_context ()); + } return 0; } @@ -115,6 +154,10 @@ ArdourFeedback::start () int ArdourFeedback::stop () { + if (server ().should_request_write ()) { + _helper.quit(); + } + _periodic_connection.disconnect (); _transport_connections.drop_connections (); @@ -176,6 +219,10 @@ ArdourFeedback::poll () const update_all (Node::strip_meter, it->first, db); } + if (server ().should_request_write ()) { + server ().request_write (); + } + return true; } @@ -185,10 +232,15 @@ ArdourFeedback::observe_transport () ARDOUR::Session& sess = session (); sess.TransportStateChange.connect (_transport_connections, MISSING_INVALIDATOR, boost::bind (TransportObserver (), this), event_loop ()); + OPTIONAL_CONNECT_HELPER(sess.TransportStateChange, _transport_connections); + sess.RecordStateChanged.connect (_transport_connections, MISSING_INVALIDATOR, boost::bind (RecordStateObserver (), this), event_loop ()); + OPTIONAL_CONNECT_HELPER(sess.RecordStateChanged, _transport_connections); + sess.tempo_map ().PropertyChanged.connect (_transport_connections, MISSING_INVALIDATOR, boost::bind (TempoObserver (), this), event_loop ()); + OPTIONAL_CONNECT_HELPER(sess.tempo_map ().PropertyChanged, _transport_connections); } void @@ -202,14 +254,17 @@ ArdourFeedback::observe_mixer () stripable->gain_control ()->Changed.connect (*it->second, MISSING_INVALIDATOR, boost::bind (StripGainObserver (), this, strip_id), event_loop ()); + OPTIONAL_CONNECT_HELPER(stripable->gain_control ()->Changed, *it->second); if (stripable->pan_azimuth_control ()) { stripable->pan_azimuth_control ()->Changed.connect (*it->second, MISSING_INVALIDATOR, boost::bind (StripPanObserver (), this, strip_id), event_loop ()); + OPTIONAL_CONNECT_HELPER(stripable->pan_azimuth_control ()->Changed, *it->second); } stripable->mute_control ()->Changed.connect (*it->second, MISSING_INVALIDATOR, boost::bind (StripMuteObserver (), this, strip_id), event_loop ()); + OPTIONAL_CONNECT_HELPER(stripable->mute_control ()->Changed, *it->second); observe_strip_plugins (strip_id, strip->plugins ()); } @@ -229,6 +284,7 @@ ArdourFeedback::observe_strip_plugins (uint32_t strip_id, ArdourMixerStrip::Plug if (control) { control->Changed.connect (*plugin, MISSING_INVALIDATOR, boost::bind (PluginBypassObserver (), this, strip_id, plugin_id), event_loop ()); + OPTIONAL_CONNECT_HELPER(control->Changed, *plugin); } for (uint32_t param_id = 0; param_id < plugin->param_count (); ++param_id) { @@ -239,6 +295,7 @@ ArdourFeedback::observe_strip_plugins (uint32_t strip_id, ArdourMixerStrip::Plug boost::bind (PluginParamValueObserver (), this, strip_id, plugin_id, param_id, boost::weak_ptr(control)), event_loop ()); + OPTIONAL_CONNECT_HELPER(control->Changed, *plugin); } catch (ArdourMixerNotFoundException& e) { /* ignore */ } diff --git a/libs/surfaces/websockets/feedback.h b/libs/surfaces/websockets/feedback.h index 6cac0debdc..fdf600d3ee 100644 --- a/libs/surfaces/websockets/feedback.h +++ b/libs/surfaces/websockets/feedback.h @@ -21,7 +21,9 @@ #include #include -#include +#include + +#include "pbd/abstract_ui.h" #include "component.h" #include "typed_value.h" @@ -29,6 +31,17 @@ namespace ArdourSurface { +class FeedbackHelperUI : public AbstractUI +{ +public: + FeedbackHelperUI (); + ~FeedbackHelperUI () {}; + +protected: + virtual void do_request (BaseUI::BaseRequestObject*); + +}; + class ArdourFeedback : public SurfaceComponent { public: @@ -49,6 +62,9 @@ private: PBD::ScopedConnectionList _transport_connections; sigc::connection _periodic_connection; + // Only needed for server event loop integration method 3 + FeedbackHelperUI _helper; + bool poll () const; void observe_transport (); diff --git a/libs/surfaces/websockets/server.cc b/libs/surfaces/websockets/server.cc index 6c4c51fd4a..7dc6497a06 100644 --- a/libs/surfaces/websockets/server.cc +++ b/libs/surfaces/websockets/server.cc @@ -195,6 +195,7 @@ WebsocketsServer::stop () if (_g_source) { // Method 3 g_source_destroy (_g_source); + lws_cancel_service (_lws_context); } if (_lws_context) { diff --git a/libs/surfaces/websockets/server.h b/libs/surfaces/websockets/server.h index 204a58ec9b..2986914b5f 100644 --- a/libs/surfaces/websockets/server.h +++ b/libs/surfaces/websockets/server.h @@ -112,8 +112,11 @@ private: GSource* _g_source; - void request_write (); - static gboolean glib_idle_callback (void *data); + static gboolean glib_idle_callback (void *); + +public: + bool should_request_write () { return _g_source != 0; } + void request_write (); };