diff --git a/include/engine/Engine.hpp b/include/engine/Engine.hpp index 80b10e85..68aa3ada 100644 --- a/include/engine/Engine.hpp +++ b/include/engine/Engine.hpp @@ -15,9 +15,9 @@ namespace engine { /** Manages Modules and Cables and steps them in time. -Engine contains a read/write mutex that locks when the Engine state is being read or written (manipulated). -Methods that read-lock (stated in their documentation) can be called simultaneously with other read-locking methods. -Methods that write-lock cannot be called simultaneously or recursively with another read-locking or write-locking method. +Engine contains a shared mutex that locks when the Engine state is being read or written (manipulated). +Methods that share-lock (stated in their documentation) can be called simultaneously with other share-locking methods. +Methods that exclusively lock cannot be called simultaneously or recursively with another share-locking or exclusive-locking method. */ struct Engine { struct Internal; @@ -27,19 +27,19 @@ struct Engine { PRIVATE ~Engine(); /** Removes all modules and cables. - Write-locks. + Exclusively locks. */ void clear(); PRIVATE void clear_NoLock(); /** Advances the engine by `frames` frames. Only call this method from the master module. - Read-locks. Also locks so only one stepBlock() can be called simultaneously or recursively. + Share-locks. Also locks so only one stepBlock() can be called simultaneously or recursively. */ void stepBlock(int frames); /** Module does not need to belong to the Engine. However, Engine will unset the master module when it is removed from the Engine. NULL will unset the master module. - Write-locks. + Exclusively locks. */ void setMasterModule(Module* module); void setMasterModule_NoLock(Module* module); @@ -49,11 +49,11 @@ struct Engine { */ float getSampleRate(); /** Sets the sample rate to step the modules. - Write-locks. + Exclusively locks. */ PRIVATE void setSampleRate(float sampleRate); /** Sets the sample rate if the sample rate in the settings is "Auto". - Write-locks. + Exclusively locks. */ void setSuggestedSampleRate(float suggestedSampleRate); /** Returns the inverse of the current sample rate. @@ -98,60 +98,60 @@ struct Engine { /** Fills `moduleIds` with up to `len` module IDs in the rack. Returns the number of IDs written. This C-like method does no allocations. The vector C++ version below does. - Read-locks. + Share-locks. */ size_t getModuleIds(int64_t* moduleIds, size_t len); /** Returns a vector of module IDs in the rack. - Read-locks. + Share-locks. */ std::vector getModuleIds(); /** Adds a Module to the rack. The module ID must not be taken by another Module. If the module ID is -1, an ID is automatically assigned. Does not transfer pointer ownership. - Write-locks. + Exclusively locks. */ void addModule(Module* module); /** Removes a Module from the rack. - Write-locks. + Exclusively locks. */ void removeModule(Module* module); PRIVATE void removeModule_NoLock(Module* module); /** Checks whether a Module is in the rack. - Read-locks. + Share-locks. */ bool hasModule(Module* module); /** Returns the Module with the given ID in the rack. - Read-locks. + Share-locks. */ Module* getModule(int64_t moduleId); Module* getModule_NoLock(int64_t moduleId); /** Triggers a ResetEvent for the given Module. - Write-locks. + Exclusively locks. */ void resetModule(Module* module); /** Triggers a RandomizeEvent for the given Module. - Write-locks. + Exclusively locks. */ void randomizeModule(Module* module); /** Sets the bypassed state and triggers a BypassEvent or UnBypassEvent of the given Module. - Write-locks. + Exclusively locks. */ void bypassModule(Module* module, bool bypassed); /** Serializes the given Module with locking, ensuring that Module::process() is not called simultaneously. - Read-locks. + Share-locks. */ json_t* moduleToJson(Module* module); /** Serializes the given Module with locking, ensuring that Module::process() is not called simultaneously. - Write-locks. + Exclusively locks. */ void moduleFromJson(Module* module, json_t* rootJ); /** Dispatches Save event to a module. - Read-locks. + Share-locks. */ void prepareSaveModule(Module* module); /** Dispatches Save event to all modules. - Read-locks. + Share-locks. */ void prepareSave(); @@ -160,31 +160,31 @@ struct Engine { /** Fills `cableIds` with up to `len` cable IDs in the rack. Returns the number of IDs written. This C-like method does no allocations. The vector C++ version below does. - Read-locks. + Share-locks. */ size_t getCableIds(int64_t* cableIds, size_t len); /** Returns a vector of cable IDs in the rack. - Read-locks. + Share-locks. */ std::vector getCableIds(); /** Adds a Cable to the rack. The cable ID must not be taken by another cable. If the cable ID is -1, an ID is automatically assigned. Does not transfer pointer ownership. - Write-locks. + Exclusively locks. */ void addCable(Cable* cable); /** Removes a Cable from the rack. - Write-locks. + Exclusively locks. */ void removeCable(Cable* cable); PRIVATE void removeCable_NoLock(Cable* cable); /** Checks whether a Cable is in the rack. - Read-locks. + Share-locks. */ bool hasCable(Cable* cable); /** Returns the Cable with the given ID in the rack. - Read-locks. + Share-locks. */ Cable* getCable(int64_t cableId); @@ -201,36 +201,36 @@ struct Engine { // ParamHandles /** Adds a ParamHandle to the rack. Does not automatically update the ParamHandle. - Write-locks. + Exclusively locks. */ void addParamHandle(ParamHandle* paramHandle); /** - Write-locks. + Exclusively locks. */ void removeParamHandle(ParamHandle* paramHandle); PRIVATE void removeParamHandle_NoLock(ParamHandle* paramHandle); /** Returns the unique ParamHandle for the given paramId - Read-locks. + Share-locks. */ ParamHandle* getParamHandle(int64_t moduleId, int paramId); ParamHandle* getParamHandle_NoLock(int64_t moduleId, int paramId); /** Use getParamHandle(moduleId, paramId) instead. - Read-locks. + Share-locks. */ DEPRECATED ParamHandle* getParamHandle(Module* module, int paramId); /** Sets the ParamHandle IDs and module pointer. If `overwrite` is true and another ParamHandle points to the same param, unsets that one and replaces it with the given handle. - Write-locks. + Exclusively locks. */ void updateParamHandle(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite = true); void updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite = true); /** Serializes the rack. - Read-locks. + Share-locks. */ json_t* toJson(); /** Deserializes the rack. - Write-locks. + Exclusively locks. */ void fromJson(json_t* rootJ); diff --git a/include/mutex.hpp b/include/mutex.hpp index aa9811c6..1955b028 100644 --- a/include/mutex.hpp +++ b/include/mutex.hpp @@ -10,65 +10,54 @@ namespace rack { This implementation is currently just a wrapper for pthreads, which works on Linux/Mac/. This is available in C++17 as std::shared_mutex, but unfortunately we're using C++11. */ -struct ReadWriteMutex { +struct SharedMutex { pthread_rwlock_t rwlock; - ReadWriteMutex() { + SharedMutex() { if (pthread_rwlock_init(&rwlock, NULL)) throw Exception("pthread_rwlock_init failed"); } - ~ReadWriteMutex() { + ~SharedMutex() { pthread_rwlock_destroy(&rwlock); } - void lockReader() { + void lock() { if (pthread_rwlock_rdlock(&rwlock)) throw Exception("pthread_rwlock_rdlock failed"); } /** Returns whether the lock was acquired. */ - bool tryLockReader() { + bool try_lock() { return pthread_rwlock_tryrdlock(&rwlock) == 0; } - void unlockReader() { + void unlock() { if (pthread_rwlock_unlock(&rwlock)) throw Exception("pthread_rwlock_unlock failed"); } - void lockWriter() { + void lock_shared() { if (pthread_rwlock_wrlock(&rwlock)) throw Exception("pthread_rwlock_wrlock failed"); } /** Returns whether the lock was acquired. */ - bool tryLockWriter() { + bool try_lock_shared() { return pthread_rwlock_trywrlock(&rwlock) == 0; } - void unlockWriter() { + void unlock_shared() { if (pthread_rwlock_unlock(&rwlock)) throw Exception("pthread_rwlock_unlock failed"); } }; -struct ReadLock { - ReadWriteMutex& m; +template +struct SharedLock { + TMutex& m; - ReadLock(ReadWriteMutex& m) : m(m) { - m.lockReader(); + SharedLock(TMutex& m) : m(m) { + m.lock_shared(); } - ~ReadLock() { - m.unlockReader(); - } -}; - - -struct WriteLock { - ReadWriteMutex& m; - - WriteLock(ReadWriteMutex& m) : m(m) { - m.lockWriter(); - } - ~WriteLock() { - m.unlockWriter(); + ~SharedLock() { + m.unlock_shared(); } }; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 30b75b0c..78c019fd 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -216,7 +216,7 @@ struct Engine::Internal { Writers lock when mutating the engine's state or stepping the block. Readers lock when using the engine's state. */ - ReadWriteMutex mutex; + SharedMutex mutex; /** Mutex that guards stepBlock() so it's not called simultaneously. */ std::mutex blockMutex; @@ -497,7 +497,7 @@ Engine::~Engine() { void Engine::clear() { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); clear_NoLock(); } @@ -527,7 +527,7 @@ void Engine::stepBlock(int frames) { double startTime = system::getTime(); std::lock_guard stepLock(internal->blockMutex); - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); // Configure thread uint32_t csr = _mm_getcsr(); initMXCSR(); @@ -581,7 +581,7 @@ void Engine::stepBlock(int frames) { void Engine::setMasterModule(Module* module) { if (module == internal->masterModule) return; - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); setMasterModule_NoLock(module); } @@ -624,7 +624,7 @@ float Engine::getSampleRate() { void Engine::setSampleRate(float sampleRate) { if (sampleRate == internal->sampleRate) return; - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); internal->sampleRate = sampleRate; internal->sampleTime = 1.f / sampleRate; @@ -713,7 +713,7 @@ size_t Engine::getNumModules() { size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); size_t i = 0; for (Module* m : internal->modules) { if (i >= len) @@ -726,7 +726,7 @@ size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) { std::vector Engine::getModuleIds() { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); std::vector moduleIds; moduleIds.reserve(internal->modules.size()); for (Module* m : internal->modules) { @@ -737,7 +737,7 @@ std::vector Engine::getModuleIds() { void Engine::addModule(Module* module) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); assert(module); // Check that the module is not already added auto it = std::find(internal->modules.begin(), internal->modules.end(), module); @@ -767,7 +767,7 @@ void Engine::addModule(Module* module) { void Engine::removeModule(Module* module) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); removeModule_NoLock(module); } @@ -821,7 +821,7 @@ void Engine::removeModule_NoLock(Module* module) { bool Engine::hasModule(Module* module) { - ReadLock lock(internal->mutex); + 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(); @@ -829,7 +829,7 @@ bool Engine::hasModule(Module* module) { Module* Engine::getModule(int64_t moduleId) { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); return getModule_NoLock(moduleId); } @@ -843,7 +843,7 @@ Module* Engine::getModule_NoLock(int64_t moduleId) { void Engine::resetModule(Module* module) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); assert(module); Module::ResetEvent eReset; @@ -852,7 +852,7 @@ void Engine::resetModule(Module* module) { void Engine::randomizeModule(Module* module) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); assert(module); Module::RandomizeEvent eRandomize; @@ -865,7 +865,7 @@ void Engine::bypassModule(Module* module, bool bypassed) { if (module->isBypassed() == bypassed) return; - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); // Clear outputs and set to 1 channel for (Output& output : module->outputs) { @@ -888,26 +888,26 @@ void Engine::bypassModule(Module* module, bool bypassed) { json_t* Engine::moduleToJson(Module* module) { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); return module->toJson(); } void Engine::moduleFromJson(Module* module, json_t* rootJ) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); module->fromJson(rootJ); } void Engine::prepareSaveModule(Module* module) { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); Module::SaveEvent e; module->onSave(e); } void Engine::prepareSave() { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); for (Module* module : internal->modules) { Module::SaveEvent e; module->onSave(e); @@ -921,7 +921,7 @@ size_t Engine::getNumCables() { size_t Engine::getCableIds(int64_t* cableIds, size_t len) { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); size_t i = 0; for (Cable* c : internal->cables) { if (i >= len) @@ -934,7 +934,7 @@ size_t Engine::getCableIds(int64_t* cableIds, size_t len) { std::vector Engine::getCableIds() { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); std::vector cableIds; cableIds.reserve(internal->cables.size()); for (Cable* c : internal->cables) { @@ -945,7 +945,7 @@ std::vector Engine::getCableIds() { void Engine::addCable(Cable* cable) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); assert(cable); // Check cable properties assert(cable->inputModule); @@ -990,7 +990,7 @@ void Engine::addCable(Cable* cable) { void Engine::removeCable(Cable* cable) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); removeCable_NoLock(cable); } @@ -1031,7 +1031,7 @@ void Engine::removeCable_NoLock(Cable* cable) { bool Engine::hasCable(Cable* cable) { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); // TODO Performance could be improved by searching cablesCache, but more testing would be needed to make sure it's always valid. auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); return it != internal->cables.end(); @@ -1039,7 +1039,7 @@ bool Engine::hasCable(Cable* cable) { Cable* Engine::getCable(int64_t cableId) { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); auto it = internal->cablesCache.find(cableId); if (it == internal->cablesCache.end()) return NULL; @@ -1082,7 +1082,7 @@ float Engine::getParamSmoothValue(Module* module, int paramId) { void Engine::addParamHandle(ParamHandle* paramHandle) { - WriteLock lock(internal->mutex); + 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); @@ -1098,7 +1098,7 @@ void Engine::addParamHandle(ParamHandle* paramHandle) { void Engine::removeParamHandle(ParamHandle* paramHandle) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); removeParamHandle_NoLock(paramHandle); } @@ -1116,7 +1116,7 @@ void Engine::removeParamHandle_NoLock(ParamHandle* paramHandle) { ParamHandle* Engine::getParamHandle(int64_t moduleId, int paramId) { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); return getParamHandle_NoLock(moduleId, paramId); } @@ -1135,7 +1135,7 @@ ParamHandle* Engine::getParamHandle(Module* module, int paramId) { void Engine::updateParamHandle(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) { - WriteLock lock(internal->mutex); + std::lock_guard lock(internal->mutex); updateParamHandle_NoLock(paramHandle, moduleId, paramId, overwrite); } @@ -1178,7 +1178,7 @@ void Engine::updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId json_t* Engine::toJson() { - ReadLock lock(internal->mutex); + SharedLock lock(internal->mutex); json_t* rootJ = json_object(); // modules