diff --git a/adapters/standalone.cpp b/adapters/standalone.cpp index a9940505..9e5dc2aa 100644 --- a/adapters/standalone.cpp +++ b/adapters/standalone.cpp @@ -214,6 +214,8 @@ int main(int argc, char* argv[]) { APP->patch->launch(patchPath); } + APP->engine->startFallbackThread(); + // Run context if (settings::headless) { printf("Press enter to exit.\n"); diff --git a/include/engine/Engine.hpp b/include/engine/Engine.hpp index 548d5e92..eadb9863 100644 --- a/include/engine/Engine.hpp +++ b/include/engine/Engine.hpp @@ -40,6 +40,7 @@ struct Engine { Write-locks. */ void setPrimaryModule(Module* module); + INTERNAL void setPrimaryModule_NoLock(Module* module); Module* getPrimaryModule(); /** Returns the sample rate used by the engine for stepping each module. @@ -223,6 +224,10 @@ struct Engine { Write-locks. */ void fromJson(json_t* rootJ); + + /** If no primary module is set, the fallback Engine thread will step blocks, using the CPU clock for timing. + */ + void startFallbackThread(); }; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 46269b3f..9b820cb8 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -279,7 +279,13 @@ struct Engine::Internal { 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; }; @@ -514,7 +520,19 @@ Engine::Engine() { 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. @@ -617,6 +635,13 @@ void Engine::setPrimaryModule(Module* module) { if (module == internal->primaryModule) return; WriteLock lock(internal->mutex); + setPrimaryModule_NoLock(module); +} + + +void Engine::setPrimaryModule_NoLock(Module* module) { + if (module == internal->primaryModule) + return; if (internal->primaryModule) { // Dispatch UnsetPrimaryEvent @@ -631,6 +656,11 @@ void Engine::setPrimaryModule(Module* module) { Module::SetPrimaryEvent e; internal->primaryModule->onSetPrimary(e); } + + // Wake up fallback thread if primary module was unset + if (!internal->primaryModule) { + internal->fallbackCv.notify_all(); + } } @@ -795,15 +825,6 @@ void Engine::removeModule_NoLock(Module* module) { // Check that the module actually exists auto it = std::find(internal->modules.begin(), internal->modules.end(), module); assert(it != internal->modules.end()); - // 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); - } // Dispatch RemoveEvent Module::RemoveEvent eRemove; module->onRemove(eRemove); @@ -813,8 +834,17 @@ void Engine::removeModule_NoLock(Module* module) { paramHandle->module = NULL; } // Unset primary module - if (internal->primaryModule == module) { - internal->primaryModule = NULL; + if (getPrimaryModule() == module) { + setPrimaryModule_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); } // Update expanders of other modules for (Module* m : internal->modules) { @@ -1309,5 +1339,42 @@ void EngineWorker::run() { } +static void Engine_fallbackRun(Engine* that) { + system::setThreadName("Engine fallback"); + contextSet(that->internal->context); + + while (that->internal->fallbackRunning) { + if (!that->getPrimaryModule()) { + // 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 primary 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->getPrimaryModule(); + }); + } + } +} + + +void Engine::startFallbackThread() { + if (internal->fallbackThread.joinable()) + return; + + internal->fallbackRunning = true; + internal->fallbackThread = std::thread(Engine_fallbackRun, this); +} + + } // namespace engine } // namespace rack