| @@ -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<int64_t> 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<int64_t> 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); | |||
| @@ -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 <class TMutex> | |||
| 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(); | |||
| } | |||
| }; | |||
| @@ -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<SharedMutex> lock(internal->mutex); | |||
| clear_NoLock(); | |||
| } | |||
| @@ -527,7 +527,7 @@ void Engine::stepBlock(int frames) { | |||
| double startTime = system::getTime(); | |||
| std::lock_guard<std::mutex> stepLock(internal->blockMutex); | |||
| ReadLock lock(internal->mutex); | |||
| SharedLock<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<int64_t> Engine::getModuleIds() { | |||
| ReadLock lock(internal->mutex); | |||
| SharedLock<SharedMutex> lock(internal->mutex); | |||
| std::vector<int64_t> moduleIds; | |||
| moduleIds.reserve(internal->modules.size()); | |||
| for (Module* m : internal->modules) { | |||
| @@ -737,7 +737,7 @@ std::vector<int64_t> Engine::getModuleIds() { | |||
| void Engine::addModule(Module* module) { | |||
| WriteLock lock(internal->mutex); | |||
| std::lock_guard<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> lock(internal->mutex); | |||
| return module->toJson(); | |||
| } | |||
| void Engine::moduleFromJson(Module* module, json_t* rootJ) { | |||
| WriteLock lock(internal->mutex); | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| module->fromJson(rootJ); | |||
| } | |||
| void Engine::prepareSaveModule(Module* module) { | |||
| ReadLock lock(internal->mutex); | |||
| SharedLock<SharedMutex> lock(internal->mutex); | |||
| Module::SaveEvent e; | |||
| module->onSave(e); | |||
| } | |||
| void Engine::prepareSave() { | |||
| ReadLock lock(internal->mutex); | |||
| SharedLock<SharedMutex> 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<SharedMutex> 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<int64_t> Engine::getCableIds() { | |||
| ReadLock lock(internal->mutex); | |||
| SharedLock<SharedMutex> lock(internal->mutex); | |||
| std::vector<int64_t> cableIds; | |||
| cableIds.reserve(internal->cables.size()); | |||
| for (Cable* c : internal->cables) { | |||
| @@ -945,7 +945,7 @@ std::vector<int64_t> Engine::getCableIds() { | |||
| void Engine::addCable(Cable* cable) { | |||
| WriteLock lock(internal->mutex); | |||
| std::lock_guard<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> 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<SharedMutex> lock(internal->mutex); | |||
| json_t* rootJ = json_object(); | |||
| // modules | |||