/*
- Copyright (C) 2012 Paul Davis
+ Copyright (C) 2012-2016 Paul Davis
Author: David Robillard
This program is free software; you can redistribute it and/or modify
namespace ARDOUR {
+class Worker;
+
/**
An object that needs to schedule non-RT work in the audio thread.
*/
/**
Do some work in the worker thread.
*/
- virtual int work(uint32_t size, const void* data) = 0;
+ virtual int work(Worker& worker, uint32_t size, const void* data) = 0;
/**
Handle a response from the worker thread in the audio thread.
};
/**
- A worker thread for non-realtime tasks scheduled in the audio thread.
+ A worker for non-realtime tasks scheduled from another thread.
+
+ A worker may be a separate thread that runs to execute scheduled work
+ asynchronously, or unthreaded, in which case work is executed immediately
+ upon scheduling by the calling thread.
*/
class LIBARDOUR_API Worker
{
public:
- Worker(Workee* workee, uint32_t ring_size);
+ Worker(Workee* workee, uint32_t ring_size, bool threaded=true);
~Worker();
/**
*/
void emit_responses();
+ /**
+ Enable or disable synchronous execution.
+
+ If enabled, all work is performed immediately in schedule() regardless
+ of whether or not the worker is threaded. This is used for exporting,
+ where we want to temporarily execute all work synchronously but the
+ worker is typically used threaded for live rolling.
+ */
+ void set_synchronous(bool synchronous) { _synchronous = synchronous; }
+
private:
void run();
/**
Handle the unlikley edge-case, if we're called in between the
responder writing 'size' and 'data'.
- @param rb the ringbuffer to check
- @return true if the message is complete, false otherwise
- */
+ @param rb the ringbuffer to check
+ @return true if the message is complete, false otherwise
+ */
bool verify_message_completeness(RingBuffer<uint8_t>* rb);
Workee* _workee;
RingBuffer<uint8_t>* _requests;
RingBuffer<uint8_t>* _responses;
uint8_t* _response;
- PBD::Semaphore _sem;
- bool _exit;
+ PBD::Semaphore _sem;
Glib::Threads::Thread* _thread;
-
+ bool _exit;
+ bool _synchronous;
};
} // namespace ARDOUR
uint32_t size,
const void* data)
{
- LV2Plugin* plugin = (LV2Plugin*)handle;
- if (plugin->session().engine().freewheeling()) {
- // Freewheeling, do the work immediately in this (audio) thread
- return (LV2_Worker_Status)plugin->work(size, data);
- } else {
- // Enqueue message for the worker thread
- return plugin->worker()->schedule(size, data) ?
- LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN;
- }
+ return (((Worker*)handle)->schedule(size, data)
+ ? LV2_WORKER_SUCCESS
+ : LV2_WORKER_ERR_UNKNOWN);
}
/** Called by the plugin to respond to non-RT work. */
uint32_t size,
const void* data)
{
- LV2Plugin* plugin = (LV2Plugin*)handle;
- if (plugin->session().engine().freewheeling()) {
- // Freewheeling, respond immediately in this (audio) thread
- return (LV2_Worker_Status)plugin->work_response(size, data);
- } else {
- // Enqueue response for the worker
- return plugin->worker()->respond(size, data) ?
- LV2_WORKER_SUCCESS : LV2_WORKER_ERR_UNKNOWN;
- }
+ return (((Worker*)handle)->respond(size, data)
+ ? LV2_WORKER_SUCCESS
+ : LV2_WORKER_ERR_UNKNOWN);
}
#ifdef LV2_EXTENDED
, _impl(new Impl())
, _features(NULL)
, _worker(NULL)
+ , _state_worker(NULL)
, _insert_id("0")
, _patch_port_in_index((uint32_t)-1)
, _patch_port_out_index((uint32_t)-1)
, _impl(new Impl())
, _features(NULL)
, _worker(NULL)
+ , _state_worker(NULL)
, _insert_id(other._insert_id)
, _patch_port_in_index((uint32_t)-1)
, _patch_port_out_index((uint32_t)-1)
log->vprintf = &log_vprintf;
_log_feature.data = log;
+ const size_t ring_size = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS;
LilvNode* worker_schedule = lilv_new_uri(_world.world, LV2_WORKER__schedule);
if (lilv_plugin_has_feature(plugin, worker_schedule)) {
LV2_Worker_Schedule* schedule = (LV2_Worker_Schedule*)malloc(
sizeof(LV2_Worker_Schedule));
- size_t buf_size = _session.engine().raw_buffer_size(DataType::MIDI) * NBUFS;
- _worker = new Worker(this, buf_size);
- schedule->handle = this;
+ _worker = new Worker(this, ring_size);
+ schedule->handle = _worker;
schedule->schedule_work = work_schedule;
_work_schedule_feature.data = schedule;
_features[n_features++] = &_work_schedule_feature;
}
lilv_node_free(worker_schedule);
+ if (_has_state_interface) {
+ // Create a non-threaded worker for use by state restore
+ _state_worker = new Worker(this, ring_size, false);
+ }
+
_impl->instance = lilv_plugin_instantiate(plugin, rate, _features);
_impl->name = lilv_plugin_get_name(plugin);
_impl->author = lilv_plugin_get_author_name(plugin);
delete _to_ui;
delete _from_ui;
delete _worker;
+ delete _state_worker;
if (_atom_ev_buffers) {
LV2_Evbuf** b = _atom_ev_buffers;
LilvNode* pset = lilv_new_uri(world, r.uri.c_str());
LilvState* state = lilv_state_new_from_world(world, _uri_map.urid_map(), pset);
+ const LV2_Feature* state_features[2] = { NULL, NULL };
+ LV2_Worker_Schedule schedule = { _state_worker, work_schedule };
+ const LV2_Feature state_sched_feature = { LV2_WORKER__schedule, &schedule };
+ if (_state_worker) {
+ state_features[1] = &state_sched_feature;
+ }
+
if (state) {
- lilv_state_restore(state, _impl->instance, set_port_value, this, 0, NULL);
+ lilv_state_restore(state, _impl->instance, set_port_value, this, 0, state_features);
lilv_state_free(state);
Plugin::load_preset(r);
}
}
int
-LV2Plugin::work(uint32_t size, const void* data)
+LV2Plugin::work(Worker& worker, uint32_t size, const void* data)
{
+ Glib::Threads::Mutex::Lock lm(_work_mutex);
return _impl->work_iface->work(
- _impl->instance->lv2_handle, work_respond, this, size, data);
+ _impl->instance->lv2_handle, work_respond, &worker, size, data);
}
int
}
}
+ if (_worker) {
+ // Execute work synchronously if we're freewheeling (export)
+ _worker->set_synchronous(session().engine().freewheeling());
+ }
+
+ // Run the plugin for this cycle
lilv_instance_run(_impl->instance, nframes);
- if (_impl->work_iface) {
+ // Emit any queued worker responses (calls a plugin callback)
+ if (_state_worker) {
+ _state_worker->emit_responses();
+ }
+ if (_worker) {
_worker->emit_responses();
+ }
+
+ // Notify the plugin that a work run cycle is complete
+ if (_impl->work_iface) {
if (_impl->work_iface->end_run) {
_impl->work_iface->end_run(_impl->instance->lv2_handle);
}
/*
- Copyright (C) 2012 Paul Davis
+ Copyright (C) 2012-2016 Paul Davis
Author: David Robillard
This program is free software; you can redistribute it and/or modify
namespace ARDOUR {
-Worker::Worker(Workee* workee, uint32_t ring_size)
+Worker::Worker(Workee* workee, uint32_t ring_size, bool threaded)
: _workee(workee)
- , _requests(new RingBuffer<uint8_t>(ring_size))
+ , _requests(threaded ? new RingBuffer<uint8_t>(ring_size) : NULL)
, _responses(new RingBuffer<uint8_t>(ring_size))
, _response((uint8_t*)malloc(ring_size))
- , _sem ("worker_semaphore", 0)
+ , _sem("worker_semaphore", 0)
+ , _thread(NULL)
, _exit(false)
- , _thread (Glib::Threads::Thread::create(sigc::mem_fun(*this, &Worker::run)))
-{}
+ , _synchronous(!threaded)
+{
+ if (threaded) {
+ _thread = Glib::Threads::Thread::create(
+ sigc::mem_fun(*this, &Worker::run));
+ }
+}
Worker::~Worker()
{
_exit = true;
_sem.signal();
- _thread->join();
+ if (_thread) {
+ _thread->join();
+ }
}
bool
Worker::schedule(uint32_t size, const void* data)
{
+ if (_synchronous || !_requests) {
+ _workee->work(*this, size, data);
+ return true;
+ }
if (_requests->write_space() < size + sizeof(size)) {
return false;
}
while (true) {
_sem.wait();
if (_exit) {
- if (buf) free(buf);
+ free(buf);
return;
}
continue; // TODO: This is probably fatal
}
- _workee->work(size, buf);
+ _workee->work(*this, size, buf);
}
}