--- ../Rack/src/engine/Engine.cpp 2023-05-20 17:03:33.006081772 +0200 +++ Engine.cpp 2023-05-20 19:35:00.711346791 +0200 @@ -1,3 +1,30 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2023 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +/** + * This file is an edited version of VCVRack's engine/Engine.cpp + * Copyright (C) 2016-2023 VCV. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + */ + #include #include #include @@ -5,192 +32,47 @@ #include #include #include -#if defined ARCH_X64 - #include -#endif +#include +#include #include +#include #include #include #include -#include #include #include #include +#include - -namespace rack { -namespace engine { - - -#if defined ARCH_X64 -static void initMXCSR() { - // Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode - // https://software.intel.com/en-us/node/682949 - _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); - _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); - // Reset other flags - _MM_SET_ROUNDING_MODE(_MM_ROUND_NEAREST); -} +#ifdef NDEBUG +# undef DEBUG #endif +#include "../CardinalRemote.hpp" +#include "DistrhoUtils.hpp" -/** Barrier based on mutexes. -Not finished or tested, do not use. -*/ -struct Barrier { - int count = 0; - uint8_t step = 0; - int threads = 0; - - std::mutex mutex; - std::condition_variable cv; - - void setThreads(int threads) { - this->threads = threads; - } - - void wait() { - std::unique_lock lock(mutex); - uint8_t s = step; - if (++count >= threads) { - // We're the last thread. Reset next phase. - count = 0; - // Allow other threads to exit wait() - step++; - cv.notify_all(); - return; - } - - cv.wait(lock, [&] { - return step != s; - }); - } -}; - - -/** 2-phase barrier based on spin-locking. -*/ -struct SpinBarrier { - std::atomic count{0}; - std::atomic step{0}; - int threads = 0; - - /** Must be called when no threads are calling wait(). - */ - void setThreads(int threads) { - this->threads = threads; - } - - void wait() { - uint8_t s = step; - if (count.fetch_add(1, std::memory_order_acquire) + 1 >= threads) { - // We're the last thread. Reset next phase. - count = 0; - // Allow other threads to exit wait() - step++; - return; - } - - // Spin until the last thread begins waiting - while (true) { - if (step.load(std::memory_order_relaxed) != s) - return; -#if defined ARCH_X64 - __builtin_ia32_pause(); -#endif - } - } -}; +// known terminal modules +extern std::vector hostTerminalModels; -/** Barrier that spin-locks until yield() is called, and then all threads switch to a mutex. -yield() should be called if it is likely that all threads will block for a while and continuing to spin-lock is unnecessary. -Saves CPU power after yield is called. -*/ -struct HybridBarrier { - std::atomic count{0}; - std::atomic step{0}; - int threads = 0; - - std::atomic yielded{false}; - std::mutex mutex; - std::condition_variable cv; - - void setThreads(int threads) { - this->threads = threads; - } - - void yield() { - yielded = true; - } - - void wait() { - uint8_t s = step; - if (count.fetch_add(1, std::memory_order_acquire) + 1 >= threads) { - // We're the last thread. Reset next phase. - count = 0; - bool wasYielded = yielded; - yielded = false; - // Allow other threads to exit wait() - step++; - if (wasYielded) { - std::unique_lock lock(mutex); - cv.notify_all(); - } - return; - } - // Spin until the last thread begins waiting - while (!yielded.load(std::memory_order_relaxed)) { - if (step.load(std::memory_order_relaxed) != s) - return; -#if defined ARCH_X64 - __builtin_ia32_pause(); -#endif - } - - // Wait on mutex CV - std::unique_lock lock(mutex); - cv.wait(lock, [&] { - return step != s; - }); - } -}; - - -struct EngineWorker { - Engine* engine; - int id; - std::thread thread; - bool running = false; +namespace rack { +namespace engine { - void start() { - assert(!running); - running = true; - thread = std::thread([&] { - run(); - }); - } - void requestStop() { - running = false; - } - - void join() { - assert(thread.joinable()); - thread.join(); - } - - void run(); -}; +static constexpr const int PORT_DIVIDER = 7; +// Arbitrary prime number so it doesn't over- or under-estimate time of buffered processors. +static constexpr const int METER_DIVIDER = 37; +static constexpr const int METER_BUFFER_LEN = 32; +static constexpr const float METER_TIME = 1.f; struct Engine::Internal { std::vector modules; + std::vector terminalModules; std::vector cables; std::set paramHandles; - Module* masterModule = NULL; // moduleId std::map modulesCache; @@ -206,7 +88,9 @@ int64_t blockFrame = 0; double blockTime = 0.0; int blockFrames = 0; + bool aboutToClose = false; +#ifndef HEADLESS // Meter int meterCount = 0; double meterTotal = 0.0; @@ -214,33 +98,32 @@ double meterLastTime = -INFINITY; double meterLastAverage = 0.0; double meterLastMax = 0.0; +#endif // Parameter smoothing Module* smoothModule = NULL; int smoothParamId = 0; float smoothValue = 0.f; + // Remote control + remoteUtils::RemoteDetails* remoteDetails = nullptr; + /** Mutex that guards the Engine state, such as settings, Modules, and Cables. Writers lock when mutating the engine's state or stepping the block. Readers lock when using the engine's state. */ SharedMutex mutex; - /** Mutex that guards stepBlock() so it's not called simultaneously. - */ - std::mutex blockMutex; +}; + - int threadCount = 0; - std::vector workers; - HybridBarrier engineBarrier; - HybridBarrier workerBarrier; - std::atomic workerModuleIndex; - // For worker threads - Context* context; - - bool fallbackRunning = false; - std::thread fallbackThread; - std::mutex fallbackMutex; - std::condition_variable fallbackCv; +struct Module::Internal { + bool bypassed = false; + + int meterSamples = 0; + float meterDurationTotal = 0.f; + + float meterBuffer[METER_BUFFER_LEN] = {}; + int meterIndex = 0; }; @@ -268,89 +151,134 @@ } -static void Engine_relaunchWorkers(Engine* that, int threadCount) { - Engine::Internal* internal = that->internal; - if (threadCount == internal->threadCount) - return; +static void Cable_step(Cable* that) { + Output* output = &that->outputModule->outputs[that->outputId]; + Input* input = &that->inputModule->inputs[that->inputId]; + // Match number of polyphonic channels to output port + const int channels = output->channels; + // Copy all voltages from output to input + for (int c = 0; c < channels; c++) { + if (!std::isfinite(output->voltages[c])) + __builtin_unreachable(); + input->voltages[c] = output->voltages[c]; + } + // Set higher channel voltages to 0 + for (int c = channels; c < input->channels; c++) { + input->voltages[c] = 0.f; + } + input->channels = channels; +} - if (internal->threadCount > 0) { - // Stop engine workers - for (EngineWorker& worker : internal->workers) { - worker.requestStop(); - } - internal->engineBarrier.wait(); - // Join and destroy engine workers - for (EngineWorker& worker : internal->workers) { - worker.join(); - } - internal->workers.resize(0); +#ifndef HEADLESS +static void Port_step(Port* that, float deltaTime) { + // Set plug lights + if (that->channels == 0) { + that->plugLights[0].setBrightness(0.f); + that->plugLights[1].setBrightness(0.f); + that->plugLights[2].setBrightness(0.f); + } + else if (that->channels == 1) { + float v = that->getVoltage() / 10.f; + that->plugLights[0].setSmoothBrightness(-v, deltaTime); + that->plugLights[1].setSmoothBrightness(v, deltaTime); + that->plugLights[2].setBrightness(0.f); } + else { + float v = that->getVoltageRMS() / 10.f; + that->plugLights[0].setBrightness(0.f); + that->plugLights[1].setBrightness(0.f); + that->plugLights[2].setSmoothBrightness(v, deltaTime); + } +} +#endif - // Configure engine - internal->threadCount = threadCount; - - // Set barrier counts - internal->engineBarrier.setThreads(threadCount); - internal->workerBarrier.setThreads(threadCount); - if (threadCount > 0) { - // Create and start engine workers - internal->workers.resize(threadCount - 1); - for (int id = 1; id < threadCount; id++) { - EngineWorker& worker = internal->workers[id - 1]; - worker.id = id; - worker.engine = that; - worker.start(); +static void TerminalModule__doProcess(TerminalModule* const terminalModule, const Module::ProcessArgs& args, bool input) { + // Step module + if (input) { + terminalModule->processTerminalInput(args); + for (Output& output : terminalModule->outputs) { + for (Cable* cable : output.cables) + Cable_step(cable); + } + } else { + terminalModule->processTerminalOutput(args); + } + +#ifndef HEADLESS + // Iterate ports to step plug lights + if (args.frame % PORT_DIVIDER == 0) { + float portTime = args.sampleTime * PORT_DIVIDER; + for (Input& input : terminalModule->inputs) { + Port_step(&input, portTime); + } + for (Output& output : terminalModule->outputs) { + Port_step(&output, portTime); } } +#endif } -static void Engine_stepWorker(Engine* that, int threadId) { - Engine::Internal* internal = that->internal; - - // int threadCount = internal->threadCount; - int modulesLen = internal->modules.size(); +static void Module__doProcess(Module* const module, const Module::ProcessArgs& args) { + Module::Internal* const internal = module->internal; - // Build ProcessArgs - Module::ProcessArgs processArgs; - processArgs.sampleRate = internal->sampleRate; - processArgs.sampleTime = internal->sampleTime; - processArgs.frame = internal->frame; - - // Step each module - while (true) { - // Choose next module - // First-come-first serve module-to-thread allocation algorithm - int i = internal->workerModuleIndex++; - if (i >= modulesLen) - break; - - Module* module = internal->modules[i]; - module->doProcess(processArgs); +#ifndef HEADLESS + // This global setting can change while the function is running, so use a local variable. + bool meterEnabled = settings::cpuMeter && (args.frame % METER_DIVIDER == 0); + + // Start CPU timer + double startTime; + if (meterEnabled) { + startTime = system::getTime(); } -} - +#endif -static void Cable_step(Cable* that) { - Output* output = &that->outputModule->outputs[that->outputId]; - Input* input = &that->inputModule->inputs[that->inputId]; - // Match number of polyphonic channels to output port - int channels = output->channels; - // Copy all voltages from output to input - for (int c = 0; c < channels; c++) { - float v = output->voltages[c]; - // Set 0V if infinite or NaN - if (!std::isfinite(v)) - v = 0.f; - input->voltages[c] = v; + // Step module + if (!internal->bypassed) + module->process(args); + else + module->processBypass(args); + +#ifndef HEADLESS + // Stop CPU timer + if (meterEnabled) { + double endTime = system::getTime(); + // Subtract call time of getTime() itself, since we only want to measure process() time. + double endTime2 = system::getTime(); + float duration = (endTime - startTime) - (endTime2 - endTime); + + internal->meterSamples++; + internal->meterDurationTotal += duration; + + // Seconds we've been measuring + float meterTime = internal->meterSamples * METER_DIVIDER * args.sampleTime; + + if (meterTime >= METER_TIME) { + // Push time to buffer + if (internal->meterSamples > 0) { + internal->meterIndex++; + internal->meterIndex %= METER_BUFFER_LEN; + internal->meterBuffer[internal->meterIndex] = internal->meterDurationTotal / internal->meterSamples; + } + // Reset total + internal->meterSamples = 0; + internal->meterDurationTotal = 0.f; + } } - // Set higher channel voltages to 0 - for (int c = channels; c < input->channels; c++) { - input->voltages[c] = 0.f; + + // Iterate ports to step plug lights + if (args.frame % PORT_DIVIDER == 0) { + float portTime = args.sampleTime * PORT_DIVIDER; + for (Input& input : module->inputs) { + Port_step(&input, portTime); + } + for (Output& output : module->outputs) { + Port_step(&output, portTime); + } } - input->channels = channels; +#endif } @@ -366,10 +294,16 @@ float smoothValue = internal->smoothValue; Param* smoothParam = &smoothModule->params[smoothParamId]; float value = smoothParam->value; - // Use decay rate of roughly 1 graphics frame - const float smoothLambda = 60.f; - float newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime; - if (value == newValue) { + float newValue; + if (internal->remoteDetails != nullptr) { + newValue = value; + sendParamChangeToRemote(internal->remoteDetails, smoothModule->id, smoothParamId, value); + } else { + // Use decay rate of roughly 1 graphics frame + const float smoothLambda = 60.f; + newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime; + } + if (d_isEqual(value, newValue)) { // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats) smoothParam->setValue(smoothValue); internal->smoothModule = NULL; @@ -380,13 +314,8 @@ } } - // Step cables - for (Cable* cable : that->internal->cables) { - Cable_step(cable); - } - // Flip messages for each module - for (Module* module : that->internal->modules) { + for (Module* module : internal->modules) { if (module->leftExpander.messageFlipRequested) { std::swap(module->leftExpander.producerMessage, module->leftExpander.consumerMessage); module->leftExpander.messageFlipRequested = false; @@ -397,13 +326,32 @@ } } - // Step modules along with workers - internal->workerModuleIndex = 0; - internal->engineBarrier.wait(); - Engine_stepWorker(that, 0); - internal->workerBarrier.wait(); + // Build ProcessArgs + Module::ProcessArgs processArgs; + processArgs.sampleRate = internal->sampleRate; + processArgs.sampleTime = internal->sampleTime; + processArgs.frame = internal->frame; + + // Process terminal inputs first + for (TerminalModule* terminalModule : internal->terminalModules) { + TerminalModule__doProcess(terminalModule, processArgs, true); + } - internal->frame++; + // Step each module and cables + for (Module* module : internal->modules) { + Module__doProcess(module, processArgs); + for (Output& output : module->outputs) { + for (Cable* cable : output.cables) + Cable_step(cable); + } + } + + // Process terminal outputs last + for (TerminalModule* terminalModule : internal->terminalModules) { + TerminalModule__doProcess(terminalModule, processArgs, false); + } + + ++internal->frame; } @@ -422,35 +370,119 @@ } +template +using IdentityDictionary = std::unordered_map; + +template +inline bool dictContains(IdentityDictionary& dict, T key) { + return dict.find(key) != dict.end(); +} + +template +inline void dictAdd(IdentityDictionary& dict, T key) { + dict[key] = key; +} + +static void Engine_storeTerminalModulesIDs(std::vector terminalModules, IdentityDictionary& terminalModulesIDs) { + for (TerminalModule* terminalModule : terminalModules) + dictAdd(terminalModulesIDs, terminalModule->id); +} + +static void Engine_orderModule(Module* module, IdentityDictionary& touchedModules, std::vector& orderedModules, IdentityDictionary& terminalModulesIDs) { + if (!dictContains(touchedModules, module) && !dictContains(terminalModulesIDs, module->id)) { // Ignore feedback loops and terminal modules + dictAdd(touchedModules, module); + for (Output& output : module->outputs) { + for (Cable* cable : output.cables) { + Module* receiver = cable->inputModule; // The input to the cable is the receiving module + Engine_orderModule(receiver, touchedModules, orderedModules, terminalModulesIDs); + } + } + orderedModules.push_back(module); + } +} + +static void Engine_assignOrderedModules(std::vector& modules, std::vector& orderedModules) { + std::reverse(orderedModules.begin(), orderedModules.end()); // These are stored bottom up + if (orderedModules.size() == modules.size()) { + for (unsigned int i = 0; i < orderedModules.size(); i++) + modules[i] = orderedModules[i]; + } +} + +#if DEBUG_ORDERED_MODULES +static void Engine_debugOrderedModules(std::vector& modules) { + printf("\n--- Ordered modules ---\n"); + for (unsigned int i = 0; i < modules.size(); i++) + printf("%d) %s - %ld\n", i, modules[i]->model->getFullName().c_str(), modules[i]->id); +} +#endif + +/** Order the modules so that they always read the most recent sample from their inputs +*/ +static void Engine_orderModules(Engine* that) { + Engine::Internal* internal = that->internal; + + IdentityDictionary terminalModulesIDs; + Engine_storeTerminalModulesIDs(internal->terminalModules, terminalModulesIDs); + + IdentityDictionary touchedModules; + std::vector orderedModules; + orderedModules.reserve(internal->modules.size()); + for (Module* module : internal->modules) + Engine_orderModule(module, touchedModules, orderedModules, terminalModulesIDs); + + Engine_assignOrderedModules(internal->modules, orderedModules); + +#if DEBUG_ORDERED_MODULES + Engine_debugOrderedModules(internal->modules); +#endif +} + + static void Engine_updateConnected(Engine* that) { // Find disconnected ports - std::set disconnectedPorts; + std::set disconnectedInputs; + std::set disconnectedOutputs; for (Module* module : that->internal->modules) { for (Input& input : module->inputs) { - disconnectedPorts.insert(&input); + disconnectedInputs.insert(&input); } for (Output& output : module->outputs) { - disconnectedPorts.insert(&output); + disconnectedOutputs.insert(&output); + } + } + for (TerminalModule* terminalModule : that->internal->terminalModules) { + for (Input& input : terminalModule->inputs) { + disconnectedInputs.insert(&input); + } + for (Output& output : terminalModule->outputs) { + disconnectedOutputs.insert(&output); } } for (Cable* cable : that->internal->cables) { // Connect input Input& input = cable->inputModule->inputs[cable->inputId]; - auto inputIt = disconnectedPorts.find(&input); - if (inputIt != disconnectedPorts.end()) - disconnectedPorts.erase(inputIt); + auto inputIt = disconnectedInputs.find(&input); + if (inputIt != disconnectedInputs.end()) + disconnectedInputs.erase(inputIt); Port_setConnected(&input); // Connect output Output& output = cable->outputModule->outputs[cable->outputId]; - auto outputIt = disconnectedPorts.find(&output); - if (outputIt != disconnectedPorts.end()) - disconnectedPorts.erase(outputIt); + auto outputIt = disconnectedOutputs.find(&output); + if (outputIt != disconnectedOutputs.end()) + disconnectedOutputs.erase(outputIt); Port_setConnected(&output); } // Disconnect ports that have no cable - for (Port* port : disconnectedPorts) { - Port_setDisconnected(port); + for (Input* input : disconnectedInputs) { + Port_setDisconnected(input); } + for (Output* output : disconnectedOutputs) { + Port_setDisconnected(output); + DISTRHO_SAFE_ASSERT(output->cables.empty()); + } + // Order the modules according to their connections + Engine_orderModules(that); } @@ -468,37 +500,23 @@ Engine::Engine() { internal = new Internal; - - internal->context = contextGet(); - setSuggestedSampleRate(0.f); } Engine::~Engine() { - // Stop fallback thread if running - { - std::lock_guard lock(internal->fallbackMutex); - internal->fallbackRunning = false; - internal->fallbackCv.notify_all(); - } - if (internal->fallbackThread.joinable()) - internal->fallbackThread.join(); - - // Shut down workers - Engine_relaunchWorkers(this, 0); - // Clear modules, cables, etc clear(); // Make sure there are no cables or modules in the rack on destruction. // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. - assert(internal->cables.empty()); - assert(internal->modules.empty()); - assert(internal->paramHandles.empty()); - - assert(internal->modulesCache.empty()); - assert(internal->cablesCache.empty()); - assert(internal->paramHandlesCache.empty()); + DISTRHO_SAFE_ASSERT(internal->cables.empty()); + DISTRHO_SAFE_ASSERT(internal->modules.empty()); + DISTRHO_SAFE_ASSERT(internal->terminalModules.empty()); + DISTRHO_SAFE_ASSERT(internal->paramHandles.empty()); + + DISTRHO_SAFE_ASSERT(internal->modulesCache.empty()); + DISTRHO_SAFE_ASSERT(internal->cablesCache.empty()); + DISTRHO_SAFE_ASSERT(internal->paramHandlesCache.empty()); delete internal; } @@ -527,20 +545,22 @@ removeModule_NoLock(module); delete module; } + std::vector terminalModules = internal->terminalModules; + for (TerminalModule* terminalModule : terminalModules) { + removeModule_NoLock(terminalModule); + delete terminalModule; + } } void Engine::stepBlock(int frames) { +#ifndef HEADLESS // Start timer before locking double startTime = system::getTime(); +#endif - std::lock_guard stepLock(internal->blockMutex); SharedLock lock(internal->mutex); // Configure thread -#if defined ARCH_X64 - uint32_t csr = _mm_getcsr(); - initMXCSR(); -#endif random::init(); internal->blockFrame = internal->frame; @@ -553,18 +573,14 @@ Engine_updateExpander_NoLock(this, module, true); } - // Launch workers - Engine_relaunchWorkers(this, settings::threadCount); - // Step individual frames for (int i = 0; i < frames; i++) { Engine_stepFrame(this); } - yieldWorkers(); - internal->block++; +#ifndef HEADLESS // Stop timer double endTime = system::getTime(); double meter = (endTime - startTime) / (frames * internal->sampleTime); @@ -582,49 +598,20 @@ internal->meterTotal = 0.0; internal->meterMax = 0.0; } - -#if defined ARCH_X64 - // Reset MXCSR back to original value - _mm_setcsr(csr); #endif } void Engine::setMasterModule(Module* module) { - if (module == internal->masterModule) - return; - std::lock_guard lock(internal->mutex); - setMasterModule_NoLock(module); } void Engine::setMasterModule_NoLock(Module* module) { - if (module == internal->masterModule) - return; - - if (internal->masterModule) { - // Dispatch UnsetMasterEvent - Module::UnsetMasterEvent e; - internal->masterModule->onUnsetMaster(e); - } - - internal->masterModule = module; - - if (internal->masterModule) { - // Dispatch SetMasterEvent - Module::SetMasterEvent e; - internal->masterModule->onSetMaster(e); - } - - // Wake up fallback thread if master module was unset - if (!internal->masterModule) { - internal->fallbackCv.notify_all(); - } } Module* Engine::getMasterModule() { - return internal->masterModule; + return nullptr; } @@ -647,20 +634,13 @@ for (Module* module : internal->modules) { module->onSampleRateChange(e); } + for (TerminalModule* terminalModule : internal->terminalModules) { + terminalModule->onSampleRateChange(e); + } } void Engine::setSuggestedSampleRate(float suggestedSampleRate) { - if (settings::sampleRate > 0) { - setSampleRate(settings::sampleRate); - } - else if (suggestedSampleRate > 0) { - setSampleRate(suggestedSampleRate); - } - else { - // Fallback sample rate - setSampleRate(44100.f); - } } @@ -670,7 +650,6 @@ void Engine::yieldWorkers() { - internal->workerBarrier.yield(); } @@ -705,17 +684,25 @@ double Engine::getMeterAverage() { +#ifndef HEADLESS return internal->meterLastAverage; +#else + return 0.0; +#endif } double Engine::getMeterMax() { +#ifndef HEADLESS return internal->meterLastMax; +#else + return 0.0; +#endif } size_t Engine::getNumModules() { - return internal->modules.size(); + return internal->modules.size() + internal->terminalModules.size(); } @@ -725,8 +712,12 @@ for (Module* m : internal->modules) { if (i >= len) break; - moduleIds[i] = m->id; - i++; + moduleIds[i++] = m->id; + } + for (TerminalModule* m : internal->terminalModules) { + if (i >= len) + break; + moduleIds[i++] = m->id; } return i; } @@ -735,27 +726,43 @@ std::vector Engine::getModuleIds() { SharedLock lock(internal->mutex); std::vector moduleIds; - moduleIds.reserve(internal->modules.size()); + moduleIds.reserve(getNumModules()); for (Module* m : internal->modules) { moduleIds.push_back(m->id); } + for (TerminalModule* tm : internal->terminalModules) { + moduleIds.push_back(tm->id); + } return moduleIds; } +static TerminalModule* asTerminalModule(Module* const module) { + const plugin::Model* const model = module->model; + if (std::find(hostTerminalModels.begin(), hostTerminalModels.end(), model) != hostTerminalModels.end()) + return static_cast(module); + return nullptr; +} + + void Engine::addModule(Module* module) { std::lock_guard lock(internal->mutex); - assert(module); + DISTRHO_SAFE_ASSERT_RETURN(module != nullptr,); // Check that the module is not already added auto it = std::find(internal->modules.begin(), internal->modules.end(), module); - assert(it == internal->modules.end()); + DISTRHO_SAFE_ASSERT_RETURN(it == internal->modules.end(),); + auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), module); + DISTRHO_SAFE_ASSERT_RETURN(tit == internal->terminalModules.end(),); // Set ID if unset or collides with an existing ID while (module->id < 0 || internal->modulesCache.find(module->id) != internal->modulesCache.end()) { // Randomly generate ID module->id = random::u64() % (1ull << 53); } // Add module - internal->modules.push_back(module); + if (TerminalModule* const terminalModule = asTerminalModule(module)) + internal->terminalModules.push_back(terminalModule); + else + internal->modules.push_back(module); internal->modulesCache[module->id] = module; // Dispatch AddEvent Module::AddEvent eAdd; @@ -770,6 +777,9 @@ if (paramHandle->moduleId == module->id) paramHandle->module = module; } +#if DEBUG_ORDERED_MODULES + printf("New module: %s - %ld\n", module->model->getFullName().c_str(), module->id); +#endif } @@ -779,11 +789,11 @@ } -void Engine::removeModule_NoLock(Module* module) { - assert(module); - // Check that the module actually exists - auto it = std::find(internal->modules.begin(), internal->modules.end(), module); - assert(it != internal->modules.end()); +static void removeModule_NoLock_common(Engine::Internal* internal, Module* module) { + // Remove from widgets cache + CardinalPluginModelHelper* const helper = dynamic_cast(module->model); + DISTRHO_SAFE_ASSERT_RETURN(helper != nullptr,); + helper->removeCachedModuleWidget(module); // Dispatch RemoveEvent Module::RemoveEvent eRemove; module->onRemove(eRemove); @@ -792,18 +802,14 @@ if (paramHandle->moduleId == module->id) paramHandle->module = NULL; } - // Unset master module - if (getMasterModule() == module) { - setMasterModule_NoLock(NULL); - } // If a param is being smoothed on this module, stop smoothing it immediately if (module == internal->smoothModule) { internal->smoothModule = NULL; } // Check that all cables are disconnected for (Cable* cable : internal->cables) { - assert(cable->inputModule != module); - assert(cable->outputModule != module); + DISTRHO_SAFE_ASSERT(cable->inputModule != module); + DISTRHO_SAFE_ASSERT(cable->outputModule != module); } // Update expanders of other modules for (Module* m : internal->modules) { @@ -816,14 +822,31 @@ m->rightExpander.module = NULL; } } - // Remove module - internal->modulesCache.erase(module->id); - internal->modules.erase(it); // Reset expanders module->leftExpander.moduleId = -1; module->leftExpander.module = NULL; module->rightExpander.moduleId = -1; module->rightExpander.module = NULL; + // Remove module + internal->modulesCache.erase(module->id); +} + + +void Engine::removeModule_NoLock(Module* module) { + DISTRHO_SAFE_ASSERT_RETURN(module,); + // Check that the module actually exists + if (TerminalModule* const terminalModule = asTerminalModule(module)) { + auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), terminalModule); + DISTRHO_SAFE_ASSERT_RETURN(tit != internal->terminalModules.end(),); + removeModule_NoLock_common(internal, module); + internal->terminalModules.erase(tit); + } + else { + auto it = std::find(internal->modules.begin(), internal->modules.end(), module); + DISTRHO_SAFE_ASSERT_RETURN(it != internal->modules.end(),); + removeModule_NoLock_common(internal, module); + internal->modules.erase(it); + } } @@ -831,7 +854,8 @@ SharedLock lock(internal->mutex); // TODO Performance could be improved by searching modulesCache, but more testing would be needed to make sure it's always valid. auto it = std::find(internal->modules.begin(), internal->modules.end(), module); - return it != internal->modules.end(); + auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), module); + return it != internal->modules.end() && tit != internal->terminalModules.end(); } @@ -851,7 +875,7 @@ void Engine::resetModule(Module* module) { std::lock_guard lock(internal->mutex); - assert(module); + DISTRHO_SAFE_ASSERT_RETURN(module,); Module::ResetEvent eReset; module->onReset(eReset); @@ -860,7 +884,7 @@ void Engine::randomizeModule(Module* module) { std::lock_guard lock(internal->mutex); - assert(module); + DISTRHO_SAFE_ASSERT_RETURN(module,); Module::RandomizeEvent eRandomize; module->onRandomize(eRandomize); @@ -868,7 +892,7 @@ void Engine::bypassModule(Module* module, bool bypassed) { - assert(module); + DISTRHO_SAFE_ASSERT_RETURN(module,); if (module->isBypassed() == bypassed) return; @@ -914,11 +938,17 @@ void Engine::prepareSave() { + if (internal->aboutToClose) + return; SharedLock lock(internal->mutex); for (Module* module : internal->modules) { Module::SaveEvent e; module->onSave(e); } + for (TerminalModule* terminalModule : internal->terminalModules) { + Module::SaveEvent e; + terminalModule->onSave(e); + } } @@ -953,16 +983,16 @@ void Engine::addCable(Cable* cable) { std::lock_guard lock(internal->mutex); - assert(cable); + DISTRHO_SAFE_ASSERT_RETURN(cable,); // Check cable properties - assert(cable->inputModule); - assert(cable->outputModule); + DISTRHO_SAFE_ASSERT_RETURN(cable->inputModule,); + DISTRHO_SAFE_ASSERT_RETURN(cable->outputModule,); bool outputWasConnected = false; for (Cable* cable2 : internal->cables) { // Check that the cable is not already added - assert(cable2 != cable); + DISTRHO_SAFE_ASSERT_RETURN(cable2 != cable,); // Check that the input is not already used by another cable - assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); + DISTRHO_SAFE_ASSERT_RETURN(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId),); // Get connected status of output, to decide whether we need to call a PortChangeEvent. // It's best to not trust `cable->outputModule->outputs[cable->outputId]->isConnected()` if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) @@ -976,6 +1006,8 @@ // Add the cable internal->cables.push_back(cable); internal->cablesCache[cable->id] = cable; + // Add the cable's zero-latency shortcut + cable->outputModule->outputs[cable->outputId].cables.push_back(cable); Engine_updateConnected(this); // Dispatch input port event { @@ -1003,10 +1035,12 @@ void Engine::removeCable_NoLock(Cable* cable) { - assert(cable); + DISTRHO_SAFE_ASSERT_RETURN(cable,); // Check that the cable is already added auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); - assert(it != internal->cables.end()); + DISTRHO_SAFE_ASSERT_RETURN(it != internal->cables.end(),); + // Remove the cable's zero-latency shortcut + cable->outputModule->outputs[cable->outputId].cables.remove(cable); // Remove the cable internal->cablesCache.erase(cable->id); internal->cables.erase(it); @@ -1060,6 +1094,9 @@ internal->smoothModule = NULL; internal->smoothParamId = 0; } + if (internal->remoteDetails != nullptr) { + sendParamChangeToRemote(internal->remoteDetails, module->id, paramId, value); + } module->params[paramId].setValue(value); } @@ -1092,11 +1129,11 @@ std::lock_guard lock(internal->mutex); // New ParamHandles must be blank. // This means we don't have to refresh the cache. - assert(paramHandle->moduleId < 0); + DISTRHO_SAFE_ASSERT_RETURN(paramHandle->moduleId < 0,); // Check that the ParamHandle is not already added auto it = internal->paramHandles.find(paramHandle); - assert(it == internal->paramHandles.end()); + DISTRHO_SAFE_ASSERT_RETURN(it == internal->paramHandles.end(),); // Add it internal->paramHandles.insert(paramHandle); @@ -1113,7 +1150,7 @@ void Engine::removeParamHandle_NoLock(ParamHandle* paramHandle) { // Check that the ParamHandle is already added auto it = internal->paramHandles.find(paramHandle); - assert(it != internal->paramHandles.end()); + DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),); // Remove it paramHandle->module = NULL; @@ -1150,7 +1187,7 @@ void Engine::updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) { // Check that it exists auto it = internal->paramHandles.find(paramHandle); - assert(it != internal->paramHandles.end()); + DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),); // Set IDs paramHandle->moduleId = moduleId; @@ -1194,6 +1231,10 @@ json_t* moduleJ = module->toJson(); json_array_append_new(modulesJ, moduleJ); } + for (TerminalModule* terminalModule : internal->terminalModules) { + json_t* terminalModuleJ = terminalModule->toJson(); + json_array_append_new(modulesJ, terminalModuleJ); + } json_object_set_new(rootJ, "modules", modulesJ); // cables @@ -1204,11 +1245,6 @@ } json_object_set_new(rootJ, "cables", cablesJ); - // masterModule - if (internal->masterModule) { - json_object_set_new(rootJ, "masterModuleId", json_integer(internal->masterModule->id)); - } - return rootJ; } @@ -1232,14 +1268,20 @@ } catch (Exception& e) { WARN("Cannot load model: %s", e.what()); - APP->patch->log(e.what()); + // APP->patch->log(e.what()); continue; } // Create module - INFO("Creating module %s", model->getFullName().c_str()); - Module* module = model->createModule(); - assert(module); + Module* const module = model->createModule(); + DISTRHO_SAFE_ASSERT_CONTINUE(module != nullptr); + + // Create the widget too, needed by a few modules + CardinalPluginModelHelper* const helper = dynamic_cast(model); + DISTRHO_SAFE_ASSERT_CONTINUE(helper != nullptr); + + app::ModuleWidget* const moduleWidget = helper->createModuleWidgetFromEngineLoad(module); + DISTRHO_SAFE_ASSERT_CONTINUE(moduleWidget != nullptr); try { // This doesn't need a lock because the Module is not added to the Engine yet. @@ -1255,7 +1297,8 @@ } catch (Exception& e) { WARN("Cannot load module: %s", e.what()); - APP->patch->log(e.what()); + // APP->patch->log(e.what()); + helper->removeCachedModuleWidget(module); delete module; continue; } @@ -1292,69 +1335,20 @@ continue; } } - - // masterModule - json_t* masterModuleIdJ = json_object_get(rootJ, "masterModuleId"); - if (masterModuleIdJ) { - Module* masterModule = getModule(json_integer_value(masterModuleIdJ)); - setMasterModule(masterModule); - } } -void EngineWorker::run() { - // Configure thread - contextSet(engine->internal->context); - system::setThreadName(string::f("Worker %d", id)); -#if defined ARCH_X64 - initMXCSR(); -#endif - random::init(); - - while (true) { - engine->internal->engineBarrier.wait(); - if (!running) - return; - Engine_stepWorker(engine, id); - engine->internal->workerBarrier.wait(); - } +void Engine::startFallbackThread() { } -static void Engine_fallbackRun(Engine* that) { - system::setThreadName("Engine fallback"); - contextSet(that->internal->context); - - while (that->internal->fallbackRunning) { - if (!that->getMasterModule()) { - // Step blocks and wait - double start = system::getTime(); - int frames = std::floor(that->getSampleRate() / 60); - that->stepBlock(frames); - double end = system::getTime(); - - double duration = frames * that->getSampleTime() - (end - start); - if (duration > 0.0) { - std::this_thread::sleep_for(std::chrono::duration(duration)); - } - } - else { - // Wait for master module to be unset, or for the request to stop running - std::unique_lock lock(that->internal->fallbackMutex); - that->internal->fallbackCv.wait(lock, [&]() { - return !that->internal->fallbackRunning || !that->getMasterModule(); - }); - } - } +void Engine_setAboutToClose(Engine* const engine) { + engine->internal->aboutToClose = true; } -void Engine::startFallbackThread() { - if (internal->fallbackThread.joinable()) - return; - - internal->fallbackRunning = true; - internal->fallbackThread = std::thread(Engine_fallbackRun, this); +void Engine_setRemoteDetails(Engine* const engine, remoteUtils::RemoteDetails* const remoteDetails) { + engine->internal->remoteDetails = remoteDetails; }