Browse Source

Rename ReadWriteMutex to SharedMutex. Use shared/exclusive terminology instead of read/write.

tags/v2.0.5
Andrew Belt 2 years ago
parent
commit
e88c39c426
3 changed files with 79 additions and 90 deletions
  1. +34
    -34
      include/engine/Engine.hpp
  2. +16
    -27
      include/mutex.hpp
  3. +29
    -29
      src/engine/Engine.cpp

+ 34
- 34
include/engine/Engine.hpp View File

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



+ 16
- 27
include/mutex.hpp View File

@@ -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();
}
};



+ 29
- 29
src/engine/Engine.cpp View File

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


Loading…
Cancel
Save