Skip event loop with partially initialized plugins

This should prevent T-RackS 5 from potentially stalling indefinitely
when using plugin groups.
This commit is contained in:
Robbert van der Helm
2021-01-27 19:03:28 +01:00
parent 72e29d044a
commit 6e5ea3a4d8
5 changed files with 83 additions and 56 deletions
+26 -29
View File
@@ -41,20 +41,6 @@
* which can be required because in the Win32 model all GUI related operations
* have to be handled from the same thread. This will be run from the
* application's main thread.
*
* TODO: Add some point we might have to add some way to inhibit the event loop
* from running when a plugin is in a partially initialized state. So far
* only the VST2 versions of the T-RackS 5 plugins have this issue (and
* only when running them in a plugin group because with individually
* hosted plugins the event loop and the plugin's initialization will
* align). This will require a fairly significant architectural change to
* do this in a clean way (`HostBridge` would need some function that
* returns a boolean to indicate whether the event loop should be
* inhibited, and then we would need to pass a function object or lambda
* to `MainContext` that checks this for all currently hosted plugins so
* we can integrate that into `async_handle_events()`). Now that I've
* written this out, it's not actually as bad as I thought it would be, so
* we'll probably add this sooner rather than later.
*/
class MainContext {
public:
@@ -112,33 +98,44 @@ class MainContext {
* @param handler The function that should be executed in the IO context
* when the timer ticks. This should be a function that handles both the
* X11 events and the Win32 message loop.
* @param predicate A function returning a boolean to indicate whether
* `handler` should be run. If this returns `false`, then the current
* event loop cycle will be skipped. This is used to prevent the Win32
* message loop from being run when there are partially initialized
* plugins. So far the VST2 versions of T-RackS 5 are the only plugins
* where this has been an issue as those plugins have a race condition
* that will cause them to stall indefinitely in this situation, but who
* knows which other plugins exert similar behaviour.
*/
template <typename F>
void async_handle_events(F handler) {
template <typename F, typename P>
void async_handle_events(F handler, P predicate) {
// Try to keep a steady framerate, but add in delays to let other events
// get handled if the GUI message handling somehow takes very long.
events_timer.expires_at(
std::max(events_timer.expiry() + timer_interval,
std::chrono::steady_clock::now() + timer_interval / 4));
events_timer.async_wait(
[&, handler](const boost::system::error_code& error) {
[&, handler, predicate](const boost::system::error_code& error) {
if (error.failed()) {
return;
}
// NOTE: These periodic callbacks should not be able to
// interrupt other threads that are actively processing
// audio. For me personally having the GUI open makes
// absolutely zero difference on DSP usage (as it should),
// but for some others it does have an impact.
// TODO: Benchmark this further on a properly configured system,
// see if it does not increase average load because of the
// rapid scheduling switching.
set_realtime_priority(false);
handler();
set_realtime_priority(true);
if (predicate()) {
// NOTE: These periodic callbacks should not be able to
// interrupt other threads that are actively
// processing audio. For me personally having the GUI
// open makes absolutely zero difference on DSP usage
// (as it should), but for some others it does have an
// impact.
// TODO: Benchmark this further on a properly configured
// system, see if it does not increase average load
// because of the rapid scheduling switching.
set_realtime_priority(false);
handler();
set_realtime_priority(true);
}
async_handle_events(handler);
async_handle_events(handler, predicate);
});
}