From ea134b46a819dec0f03a99d0b0efddc6e58bd17b Mon Sep 17 00:00:00 2001 From: "Robin E. R. Davies" Date: Tue, 22 Jul 2025 16:02:37 -0400 Subject: [PATCH] Worker thread per plugin instance. --- .vscode/settings.json | 2 +- src/IHost.hpp | 1 - src/Lv2Effect.cpp | 12 ++++++++---- src/Lv2Effect.hpp | 7 +++++-- src/PluginHost.cpp | 13 ++++++++----- src/PluginHost.hpp | 4 ++-- src/Worker.cpp | 32 ++++++++++++++++++++++++-------- src/Worker.hpp | 14 ++++++++------ 8 files changed, 56 insertions(+), 29 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c4e2792..d58ac81 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -163,7 +163,7 @@ "cSpell.ignoreWords": [ "nammodel" ], - "cSpell.enabled": true, + "cSpell.enabled": false, // Disable all automatic completion suggestions - only show when Ctrl+Space is pressed "editor.quickSuggestions": { diff --git a/src/IHost.hpp b/src/IHost.hpp index 0cc7eae..ab02649 100644 --- a/src/IHost.hpp +++ b/src/IHost.hpp @@ -46,7 +46,6 @@ namespace pipedal { virtual int GetNumberOfInputAudioChannels() const = 0; virtual int GetNumberOfOutputAudioChannels() const = 0; virtual std::shared_ptr GetPluginInfo(const std::string &uri) const = 0; - virtual std::shared_ptr GetHostWorkerThread() = 0; virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0; diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp index 740a59f..e7dd6cc 100644 --- a/src/Lv2Effect.cpp +++ b/src/Lv2Effect.cpp @@ -41,6 +41,7 @@ #include "AudioHost.hpp" #include #include "RingBufferReader.hpp" +#include "Worker.hpp" using namespace pipedal; namespace fs = std::filesystem; @@ -70,6 +71,11 @@ Lv2Effect::Lv2Effect( this->bypass = pedalboardItem.isEnabled(); + this->workerThread = std::make_unique(); + if (info_->WantsWorkerThread()) + { + workerThread->StartThread(); + } // stash a list of known file properties that we want to keep synced. if (info->piPedalUI()) { @@ -179,10 +185,8 @@ Lv2Effect::Lv2Effect( const LV2_Worker_Interface *worker_interface = (const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance, LV2_WORKER__interface); - if (worker_interface) - { - this->worker = std::make_unique(pHost->GetHostWorkerThread(), pInstance, worker_interface); - } + this->worker = std::make_unique(workerThread, pInstance, worker_interface); + const LV2_State_Interface *state_interface = (const LV2_State_Interface *)lilv_instance_get_extension_data(pInstance, LV2_STATE__interface); diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp index 0dd80e3..9878b4a 100644 --- a/src/Lv2Effect.hpp +++ b/src/Lv2Effect.hpp @@ -40,12 +40,12 @@ #include "StateInterface.hpp" #include "LogFeature.hpp" - namespace pipedal { class RealtimeRingBufferWriter; class IPatchWriterCallback; + class HostWorkerThread; class Lv2Effect : public IEffect, private LogFeature::LogMessageListener { @@ -57,6 +57,10 @@ namespace pipedal private: + + std::shared_ptr workerThread; + std::unique_ptr worker; + std::unordered_map controlIndex; FileBrowserFilesFeature fileBrowserFilesFeature; @@ -70,7 +74,6 @@ namespace pipedal int numberOfOutputs = 0; int numberOfMidiInputs = 0; - std::unique_ptr worker; LilvInstance *pInstance; std::shared_ptr info; std::vector controlValues; diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp index 5a796cb..4ccb463 100644 --- a/src/PluginHost.cpp +++ b/src/PluginHost.cpp @@ -47,6 +47,7 @@ #include "StdErrorCapture.hpp" #include "util.hpp" #include "ModFileTypes.hpp" +#include #include "Locale.hpp" @@ -301,7 +302,6 @@ PluginHost::PluginHost() this->urids = new Urids(mapFeature); - pHostWorkerThread = std::make_shared(); } void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings) @@ -1668,10 +1668,6 @@ bool Lv2PluginInfo::isSplit() const { return uri_ == SPLIT_PEDALBOARD_ITEM_URI; } -std::shared_ptr PluginHost::GetHostWorkerThread() -{ - return pHostWorkerThread; -} class ResourceInfo { @@ -1883,6 +1879,13 @@ Lv2PatchPropertyInfo::Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNod } } +// ffs. +static inline bool contains(const std::vector &vec, const std::string &value) { + return std::find(vec.begin(),vec.end(),value) != vec.end(); +} +bool Lv2PluginInfo::WantsWorkerThread() const { + return contains(this->required_features_,LV2_WORKER__schedule) || contains(this->supported_features_,LV2_WORKER__schedule); +} // void PiPedalHostLogError(const std::string &error) // { // Lv2Log::error("%s",error.c_str()); diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp index 5b9c202..f4c7de1 100644 --- a/src/PluginHost.hpp +++ b/src/PluginHost.hpp @@ -454,6 +454,8 @@ namespace pipedal LV2_PROPERTY_GETSET(patchProperties) LV2_PROPERTY_GETSET(hasDefaultState) + bool WantsWorkerThread() const; + const Lv2PortInfo &getPort(const std::string &symbol) { for (size_t i = 0; i < ports_.size(); ++i) @@ -913,7 +915,6 @@ namespace pipedal } private: - std::shared_ptr pHostWorkerThread; // IHost implementation. virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; } virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; } @@ -922,7 +923,6 @@ namespace pipedal virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; } virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels; } virtual LV2_Feature *const *GetLv2Features() const { return (LV2_Feature *const *)&(this->lv2Features[0]); } - virtual std::shared_ptr GetHostWorkerThread(); public: virtual MapFeature &GetMapFeature() override { return this->mapFeature; } diff --git a/src/Worker.cpp b/src/Worker.cpp index f94b72d..7fcca63 100644 --- a/src/Worker.cpp +++ b/src/Worker.cpp @@ -15,7 +15,7 @@ */ // (Borrows heavily from worker.c by David Robillard.) -// Copyright (c) 2022 Robin Davies +// Copyright (c) 2024 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in @@ -242,8 +242,19 @@ void HostWorkerThread::ThreadProc() noexcept HostWorkerThread::HostWorkerThread() { this->dataBuffer.resize(16*1024); - pThread = std::make_unique([this]() - { this->ThreadProc(); }); +} + +bool HostWorkerThread::StartThread() +{ + if (!pThread) + { + if (closed) { + return false; + } + pThread = std::make_unique([this]() + { this->ThreadProc(); }); + } + return true; } void HostWorkerThread::Close() @@ -252,10 +263,12 @@ void HostWorkerThread::Close() bool sendClose = false; { std::lock_guard lock{submitMutex}; - if (!closed) - { - closed = true; - sendClose = true; + if (pThread) { + if (!closed) + { + closed = true; + sendClose = true; + } } } if (sendClose) @@ -278,7 +291,10 @@ LV2_Worker_Status HostWorkerThread::ScheduleWork(Worker *worker, size_t size, co { std::lock_guard lock(submitMutex); - if (exiting) + if (!StartThread()) { // ensures thread is running, when plugins haven't declared they want a worker thread. + return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE; + } + if (exiting ) { return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE; } diff --git a/src/Worker.hpp b/src/Worker.hpp index 8c54507..9cf38aa 100644 --- a/src/Worker.hpp +++ b/src/Worker.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Robin Davies +// Copyright (c) 2025 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in @@ -32,6 +32,7 @@ #include "lv2/atom/atom.h" #include "lv2/worker/worker.h" #include "condition_variable" +#include #include #include @@ -51,10 +52,12 @@ namespace pipedal { HostWorkerThread(); ~HostWorkerThread(); + bool StartThread(); void Close(); + bool Closed() const { return closed || pThread == nullptr; } LV2_Worker_Status ScheduleWork(Worker*worker, size_t size, const void*data); private: - bool closed = false; + std::atomic closed = false; std::unique_ptr pThread; void ThreadProc() noexcept; @@ -64,8 +67,6 @@ namespace pipedal { std::vector dataBuffer; - - }; class Worker { @@ -75,8 +76,8 @@ namespace pipedal { LilvInstance*lilvInstance; const LV2_Worker_Interface*workerInterface; - bool closed = false; - bool exiting = false; + std::atomic closed = false; + std::atomic exiting = false; RingBuffer responseRingBuffer; std::vector responseBuffer; @@ -94,6 +95,7 @@ namespace pipedal { public: Worker(const std::shared_ptr& pHostWorker,LilvInstance *instance, const LV2_Worker_Interface *iface); ~Worker(); + void Close(); LV2_Worker_Status ScheduleWork(