@@ -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 | |||