13
0

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()
This commit is contained in:
Luciano Iam 2021-06-13 20:54:43 +02:00 committed by Robin Gareus
parent b3661af92a
commit e3569b6469
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
6 changed files with 83 additions and 6 deletions

View File

@ -39,8 +39,8 @@ ArdourWebsockets::ArdourWebsockets (Session& s)
, AbstractUI<ArdourWebsocketsUIRequest> (name ())
, _mixer (*this)
, _transport (*this)
, _feedback (*this)
, _server (*this)
, _feedback (*this)
, _dispatcher (*this)
{
_components.push_back (&_mixer);

View File

@ -95,8 +95,8 @@ protected:
private:
ArdourMixer _mixer;
ArdourTransport _transport;
ArdourFeedback _feedback;
WebsocketsServer _server;
ArdourFeedback _feedback;
WebsocketsDispatcher _dispatcher;
std::vector<SurfaceComponent*> _components;

View File

@ -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<void> \
(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<BaseUI::BaseRequestObject> ("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<Glib::TimeoutSource> 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<void> (TransportObserver (), this), event_loop ());
OPTIONAL_CONNECT_HELPER(sess.TransportStateChange, _transport_connections);
sess.RecordStateChanged.connect (_transport_connections, MISSING_INVALIDATOR,
boost::bind<void> (RecordStateObserver (), this), event_loop ());
OPTIONAL_CONNECT_HELPER(sess.RecordStateChanged, _transport_connections);
sess.tempo_map ().PropertyChanged.connect (_transport_connections, MISSING_INVALIDATOR,
boost::bind<void> (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<void> (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<void> (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<void> (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<void> (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<void> (PluginParamValueObserver (), this, strip_id, plugin_id, param_id,
boost::weak_ptr<AutomationControl>(control)),
event_loop ());
OPTIONAL_CONNECT_HELPER(control->Changed, *plugin);
} catch (ArdourMixerNotFoundException& e) {
/* ignore */
}

View File

@ -21,7 +21,9 @@
#include <boost/shared_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <glibmm/main.h>
#include <glibmm/threads.h>
#include "pbd/abstract_ui.h"
#include "component.h"
#include "typed_value.h"
@ -29,6 +31,17 @@
namespace ArdourSurface {
class FeedbackHelperUI : public AbstractUI<BaseUI::BaseRequestObject>
{
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 ();

View File

@ -195,6 +195,7 @@ WebsocketsServer::stop ()
if (_g_source) { // Method 3
g_source_destroy (_g_source);
lws_cancel_service (_lws_context);
}
if (_lws_context) {

View File

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