Merge pull request #986 from jpcima/runloop-timers

Fix attempt for X11 runloop problems
This commit is contained in:
JP Cimalando 2021-09-20 07:59:58 +02:00 committed by GitHub
commit 2bb268a0ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 187 additions and 93 deletions

View file

@ -59,11 +59,8 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p
config = &x11config; config = &x11config;
#endif #endif
Editor* editor = editor_.get(); Editor* editor = new Editor(*this);
if (!editor) { editor_.reset(editor);
editor = new Editor(*this);
editor_.reset(editor);
}
if (!frame->open(parent, platformType, config)) { if (!frame->open(parent, platformType, config)) {
fprintf(stderr, "[sfizz] error opening frame\n"); fprintf(stderr, "[sfizz] error opening frame\n");
@ -122,12 +119,21 @@ void PLUGIN_API SfizzVstEditor::close()
for (FObject* update : updates_) for (FObject* update : updates_)
update->removeDependent(this); update->removeDependent(this);
if (editor_) if (editor_) {
editor_->close(); editor_->close();
editor_ = nullptr;
}
if (frame->getNbReference() != 1) if (frame->getNbReference() != 1)
frame->forget(); frame->forget();
else else {
frame->close(); frame->close();
#if !defined(__APPLE__) && !defined(_WIN32)
// if vstgui is done using the runloop, destroy it
if (!RunLoop::get())
_runLoop = nullptr;
#endif
}
this->frame = nullptr; this->frame = nullptr;
} }
@ -158,8 +164,6 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message)
// notifier of X11 events is working. If there is, remove this and // notifier of X11 events is working. If there is, remove this and
// avoid polluting Linux hosts which implement the loop correctly. // avoid polluting Linux hosts which implement the loop correctly.
runLoop->processSomeEvents(); runLoop->processSomeEvents();
runLoop->cleanupDeadHandlers();
} }
} }
#endif #endif

View file

@ -1,33 +1,38 @@
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#if !defined(__APPLE__) && !defined(_WIN32) #if !defined(__APPLE__) && !defined(_WIN32)
#include "X11RunLoop.h" #include "X11RunLoop.h"
#include "vstgui/lib/platform/linux/x11platform.h" #include "vstgui/lib/platform/linux/x11platform.h"
#include "base/source/fobject.h" #include "base/source/fobject.h"
#include <vector>
#include <typeinfo>
#include <cstdio>
#include <cassert>
namespace VSTGUI { namespace VSTGUI {
RunLoop::RunLoop(Steinberg::FUnknown* runLoop) struct RunLoop::Impl {
: runLoop(runLoop) struct EventHandler;
{ struct TimerHandler;
}
RunLoop::~RunLoop() {} using EventHandlers = std::vector<Steinberg::IPtr<EventHandler>>;
using TimerHandlers = std::vector<Steinberg::IPtr<TimerHandler>>;
SharedPointer<RunLoop> RunLoop::get() EventHandlers eventHandlers;
{ TimerHandlers timerHandlers;
return X11::RunLoop::get().cast<VSTGUI::RunLoop>(); Steinberg::FUnknownPtr<Steinberg::Linux::IRunLoop> runLoop;
} };
struct RunLoop::EventHandler final : Steinberg::Linux::IEventHandler, public Steinberg::FObject { //------------------------------------------------------------------------------
struct RunLoop::Impl::EventHandler final : Steinberg::Linux::IEventHandler, public Steinberg::FObject {
X11::IEventHandler* handler { nullptr }; X11::IEventHandler* handler { nullptr };
bool alive { false }; bool alive { false };
void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor) override void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor) override;
{
if (alive && handler)
handler->onEvent();
}
DELEGATE_REFCOUNT(Steinberg::FObject) DELEGATE_REFCOUNT(Steinberg::FObject)
DEFINE_INTERFACES DEFINE_INTERFACES
@ -35,15 +40,11 @@ struct RunLoop::EventHandler final : Steinberg::Linux::IEventHandler, public Ste
END_DEFINE_INTERFACES(Steinberg::FObject) END_DEFINE_INTERFACES(Steinberg::FObject)
}; };
struct RunLoop::TimerHandler final : Steinberg::Linux::ITimerHandler, public Steinberg::FObject { struct RunLoop::Impl::TimerHandler final : Steinberg::Linux::ITimerHandler, public Steinberg::FObject {
X11::ITimerHandler* handler { nullptr }; X11::ITimerHandler* handler { nullptr };
bool alive { false }; bool alive { false };
void PLUGIN_API onTimer() override void PLUGIN_API onTimer() override;
{
if (alive && handler)
handler->onTimer();
}
DELEGATE_REFCOUNT(Steinberg::FObject) DELEGATE_REFCOUNT(Steinberg::FObject)
DEFINE_INTERFACES DEFINE_INTERFACES
@ -51,45 +52,126 @@ struct RunLoop::TimerHandler final : Steinberg::Linux::ITimerHandler, public Ste
END_DEFINE_INTERFACES(Steinberg::FObject) END_DEFINE_INTERFACES(Steinberg::FObject)
}; };
//------------------------------------------------------------------------------
void PLUGIN_API RunLoop::Impl::EventHandler::onFDIsSet(Steinberg::Linux::FileDescriptor)
{
SharedPointer<RunLoop> runLoop = RunLoop::get();
if (!runLoop) {
fprintf(stderr, "[x11] event has fired without active runloop\n");
return;
}
if (alive && handler)
handler->onEvent();
}
void PLUGIN_API RunLoop::Impl::TimerHandler::onTimer()
{
SharedPointer<RunLoop> runLoop = RunLoop::get();
if (!runLoop) {
fprintf(stderr, "[x11] timer has fired without active runloop\n");
return;
}
if (alive && handler)
handler->onTimer();
}
//------------------------------------------------------------------------------
RunLoop::RunLoop(Steinberg::FUnknown* runLoop)
: impl(new Impl)
{
impl->runLoop = runLoop;
}
RunLoop::~RunLoop()
{
//dumpCurrentState();
if (0) {
// remove any leftover handlers
for (size_t i = 0; i < impl->eventHandlers.size(); ++i) {
const auto& eh = impl->eventHandlers[i];
if (eh->alive && eh->handler) {
impl->runLoop->unregisterEventHandler(eh.get());
}
}
for (size_t i = 0; i < impl->timerHandlers.size(); ++i) {
const auto& th = impl->timerHandlers[i];
if (th->alive && th->handler) {
impl->runLoop->unregisterTimer(th.get());
}
}
}
}
SharedPointer<RunLoop> RunLoop::get()
{
return X11::RunLoop::get().cast<VSTGUI::RunLoop>();
}
void RunLoop::processSomeEvents() void RunLoop::processSomeEvents()
{ {
for (size_t i = 0; i < eventHandlers.size(); ++i) { for (size_t i = 0; i < impl->eventHandlers.size(); ++i) {
const auto& eh = eventHandlers[i]; const auto& eh = impl->eventHandlers[i];
if (eh->alive && eh->handler) { if (eh->alive && eh->handler) {
eh->handler->onEvent(); eh->handler->onEvent();
} }
} }
} }
void RunLoop::cleanupDeadHandlers() void RunLoop::dumpCurrentState()
{ {
for (size_t i = 0; i < eventHandlers.size(); ++i) { fprintf(stderr, "=== X11 runloop ===\n");
const auto& eh = eventHandlers[i];
if (!eh->alive) { fprintf(stderr, "\t" "Event slots:\n");
runLoop->unregisterEventHandler(eh); for (size_t i = 0, n = impl->eventHandlers.size(); i < n; ++i) {
eventHandlers.erase(eventHandlers.begin() + i--); Impl::EventHandler *eh = impl->eventHandlers[i].get();
} fprintf(stderr, "\t\t" "(%lu) alive=%d handler=%p type=%s\n", i, eh->alive, eh->handler, (eh->alive && eh->handler) ? typeid(*eh->handler).name() : "");
} }
for (size_t i = 0; i < timerHandlers.size(); ++i) {
const auto& th = timerHandlers[i]; fprintf(stderr, "\t" "Timer slots:\n");
if (!th->alive) { for (size_t i = 0, n = impl->timerHandlers.size(); i < n; ++i) {
runLoop->unregisterTimer(th); Impl::TimerHandler *th = impl->timerHandlers[i].get();
timerHandlers.erase(timerHandlers.begin() + i--); fprintf(stderr, "\t\t" "(%lu) alive=%d handler=%p type=%s\n", i, th->alive, th->handler, (th->alive && th->handler) ? typeid(*th->handler).name() : "");
}
} }
fprintf(stderr, "===/X11 runloop ===\n");
}
template <class T>
static void insertHandler(std::vector<Steinberg::IPtr<T>>& list, Steinberg::IPtr<T> handler)
{
size_t i = 0;
size_t n = list.size();
while (i < n && list[i]->alive)
++i;
if (i < n)
list[i] = handler;
else
list.emplace_back(handler);
}
template <class T, class U>
static size_t findHandler(const std::vector<Steinberg::IPtr<T>>& list, U* handler)
{
for (size_t i = 0, n = list.size(); i < n; ++i) {
if (list[i]->alive && list[i]->handler == handler)
return i;
}
return ~size_t(0);
} }
bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler) bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler)
{ {
if (!runLoop) if (!impl->runLoop)
return false; return false;
auto smtgHandler = Steinberg::owned(new EventHandler()); auto smtgHandler = Steinberg::owned(new Impl::EventHandler);
smtgHandler->handler = handler; smtgHandler->handler = handler;
smtgHandler->alive = true; smtgHandler->alive = true;
if (runLoop->registerEventHandler(smtgHandler, fd) == Steinberg::kResultTrue) { if (impl->runLoop->registerEventHandler(smtgHandler, fd) == Steinberg::kResultTrue) {
eventHandlers.push_back(smtgHandler); insertHandler(impl->eventHandlers, smtgHandler);
return true; return true;
} }
return false; return false;
@ -97,29 +179,31 @@ bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler)
bool RunLoop::unregisterEventHandler(X11::IEventHandler* handler) bool RunLoop::unregisterEventHandler(X11::IEventHandler* handler)
{ {
if (!runLoop) if (!impl->runLoop)
return false; return false;
for (size_t i = 0; i < eventHandlers.size(); ++i) { size_t index = findHandler(impl->eventHandlers, handler);
const auto& eh = eventHandlers[i]; if (index == ~size_t(0))
if (eh->alive && eh->handler == handler) { return false;
eh->alive = false;
return true; Impl::EventHandler *eh = impl->eventHandlers[index].get();
} if (!impl->runLoop->unregisterEventHandler(eh))
} return false;
return false;
eh->alive = false;
return true;
} }
bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler) bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler)
{ {
if (!runLoop) if (!impl->runLoop)
return false; return false;
auto smtgHandler = Steinberg::owned(new TimerHandler()); auto smtgHandler = Steinberg::owned(new Impl::TimerHandler);
smtgHandler->handler = handler; smtgHandler->handler = handler;
smtgHandler->alive = true; smtgHandler->alive = true;
if (runLoop->registerTimer(smtgHandler, interval) == Steinberg::kResultTrue) { if (impl->runLoop->registerTimer(smtgHandler, interval) == Steinberg::kResultTrue) {
timerHandlers.push_back(smtgHandler); insertHandler(impl->timerHandlers, smtgHandler);
return true; return true;
} }
return false; return false;
@ -127,17 +211,19 @@ bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler)
bool RunLoop::unregisterTimer(X11::ITimerHandler* handler) bool RunLoop::unregisterTimer(X11::ITimerHandler* handler)
{ {
if (!runLoop) if (!impl->runLoop)
return false; return false;
for (size_t i = 0; i < timerHandlers.size(); ++i) { size_t index = findHandler(impl->timerHandlers, handler);
const auto& th = timerHandlers[i]; if (index == ~size_t(0))
if (th->alive && th->handler == handler) { return false;
th->alive = false;
return true; Impl::TimerHandler *th = impl->timerHandlers[index].get();
} if (!impl->runLoop->unregisterTimer(th))
} return false;
return false;
th->alive = false;
return true;
} }
} // namespace VSTGUI } // namespace VSTGUI

View file

@ -1,16 +1,28 @@
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: BSD-2-Clause
/*
This is a modified version the X11 run loop from vst3editor.cpp.
This version is edited to add more safeguards to protect against host bugs. // This code is part of the sfizz library and is licensed under a BSD 2-clause
It also permits to call event processing externally in case the host has a // license. You should have receive a LICENSE.md file along with the code.
defective X11 event loop notifier. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
/*
This runloop connects to X11 VSTGUI, it connects VST3 and VSTGUI together.
The Windows and macOS runloops do not need this, the OS-provided
functionality is used instead.
Previously, this was based on VSTGUI code provided by Steinberg.
This is replaced with a rewrite, because the original code has too many
issues. For example, it has no robustness in case handlers get added or
removed within the execution of the handler.
This version allows to call event processing externally, in case the host
has a defective X11 event loop notifier. (some versions of Bitwig do)
*/ */
#pragma once #pragma once
#if !defined(__APPLE__) && !defined(_WIN32) #if !defined(__APPLE__) && !defined(_WIN32)
#include "vstgui/lib/platform/linux/x11frame.h" #include "vstgui/lib/platform/linux/x11frame.h"
#include "pluginterfaces/gui/iplugview.h" #include "pluginterfaces/gui/iplugview.h"
#include <memory>
namespace VSTGUI { namespace VSTGUI {
@ -22,24 +34,16 @@ public:
static SharedPointer<RunLoop> get(); static SharedPointer<RunLoop> get();
void processSomeEvents(); void processSomeEvents();
void cleanupDeadHandlers(); void dumpCurrentState();
// X11::IRunLoop // X11::IRunLoop
bool registerEventHandler(int fd, X11::IEventHandler* handler); bool registerEventHandler(int fd, X11::IEventHandler* handler) override;
bool unregisterEventHandler(X11::IEventHandler* handler); bool unregisterEventHandler(X11::IEventHandler* handler) override;
bool registerTimer(uint64_t interval, X11::ITimerHandler* handler); bool registerTimer(uint64_t interval, X11::ITimerHandler* handler) override;
bool unregisterTimer(X11::ITimerHandler* handler); bool unregisterTimer(X11::ITimerHandler* handler) override;
private: struct Impl;
struct EventHandler; std::unique_ptr<Impl> impl;
struct TimerHandler;
private:
using EventHandlers = std::vector<Steinberg::IPtr<EventHandler>>;
using TimerHandlers = std::vector<Steinberg::IPtr<TimerHandler>>;
EventHandlers eventHandlers;
TimerHandlers timerHandlers;
Steinberg::FUnknownPtr<Steinberg::Linux::IRunLoop> runLoop;
}; };
} // namespace VSTGUI } // namespace VSTGUI