There's definitely room for improvement.
First, refactor for readability: instead of enumerating the function calls for each display, pack them all into a single call, e.g.: drawSoundLoaderInfoPanes() replacing all the various drawSoundLoaderInfoPanesX() calls.
void drawSoundLoaderInfoPanes()
{
drawSoundLoaderInfoPane();
drawSoundLoaderInfoPane2(gSavedState);
drawSoundLoaderInfoPane3(gSavedState);
drawSoundLoaderInfoPane4(gSavedState);
drawSoundLoaderInfoPane5(gSavedState);
}
void updateDisplays(void *) {
while (!Bela_stopRequested()) {
if (gDisplaysInitialized == 1) {
if (gHasDisplayUpdatedForModeChange == 0 && gHasDisplayUpdatedForParameterChange == 0 && gHasDisplayUpdatedForSelectedStepChange == 0 && gHasDisplayUpdatedForBlinkChange == 0) {
usleep(gTaskSleepTime);
continue;
}
std::vector < Sample * > samples = gSequence.getSamples();
std::vector < Step * > steps = gSequence.getSteps();
Step * currentStep = gSequence.getCurrentStep();
int currentStepDisplay = currentStep -> getDisplayIndex();
int currentPlayheadDisplay = currentStep -> getPlayheadDisplayIndex();
int stepBoxToDraw = 0;
if (gMode == kSoundLoader) {
if (!gHasDisplayUpdatedForModeChange) {
drawSoundLoaderInfoPanes(gSavedState);
gHasDisplayUpdatedForModeChange = 1;
}
} else if (gMode == kSoundSaver) {
if (!gHasDisplayUpdatedForModeChange) {
drawSoundSaverInfoPanes(gSaveState);
gHasDisplayUpdatedForModeChange = 1;
} else if (!gHasDisplayUpdatedForBlinkChange) {
drawSoundSaverInfoPane();
gHasDisplayUpdatedForBlinkChange = 1;
}
} else if (gMode == kLooper) {
if (!gHasDisplayUpdatedForModeChange) {
drawLooperInfoPanes(gSavedState);
gHasDisplayUpdatedForModeChange = 1;
} else if (!gHasDisplayUpdatedForBlinkChange) {
drawLooperInfoPanes();
gHasDisplayUpdatedForBlinkChange = 1;
}
} else if (gMode == kSequencer) {
if (gSequencerMode == kSequencerMenu) {
if (!gHasDisplayUpdatedForModeChange) {
drawSequencerMenuPanes();
gHasDisplayUpdatedForModeChange = 1;
}
} else if (gSequencerMode == kSequencerPattern) {
if (!gHasDisplayUpdatedForSelectedStepChange) {
drawSequencerPatternForSelectedStepChange(gSequence);
} else if (!gHasDisplayUpdatedForModeChange) {
drawSequencerInfoPanel(gSequence);
drawAllSequencerDisplays(&stepBoxToDraw, currentStep->getIndex(), gSequence, steps, samples);
} else if (gTransportState == kPlaying) {
drawSequencerPatternForPlayback(currentPlayheadDisplay, currentStepDisplay, currentStep -> getIndex(), &stepBoxToDraw, steps, gSequence.getNumberOfSteps(), gSequence.getNumberOfSamples());
}
gLastCurrentStepDisplay = currentStepDisplay;
} else if (gSequencerMode == kSequencerSampler) {
drawSequencerSamplerMenuPane();
drawAllSequencerDisplays(&stepBoxToDraw, currentStep->getIndex(), gSequence, steps, samples);
} else if (gSequencerMode == kSequencerParameter) {
if (gPatternParameter == kPatternParameterNone) {
drawSequencerParametersMenu();
} else {
drawSequencerParameter(gSong, gSequence);
}
}
} else if (gMode == kPatternLoader) {
if (!gHasDisplayUpdatedForModeChange) {
drawSoundLoaderInfoPanes();
gHasDisplayUpdatedForModeChange = 1;
}
} else if (gMode == kPatternSaver) {
if (!gHasDisplayUpdatedForModeChange) {
drawSoundSaverInfoPane();
gHasDisplayUpdatedForModeChange = 1;
} else if (!gHasDisplayUpdatedForBlinkChange) {
drawSoundSaverInfoPane();
gHasDisplayUpdatedForBlinkChange = 1;
}
}
}
}
}
gHasDisplayUpdatedForParameterChange is not used anywhere else in the loop, so probably not worth checking for it in the initial condition.
This looks like a bug:
if (!gHasDisplayUpdatedForSelectedStepChange) {
drawSequencerPatternForSelectedStepChange(gSequence);
} else if (!gHasDisplayUpdatedForModeChange) {
drawSequencerInfoPanel(gSequence);
drawAllSequencerDisplays(&stepBoxToDraw, currentStep->getIndex(), gSequence, steps, samples);
}
these flags are checked but not reset, this means that while in this mode, after the display gets first updated, this thread will spin without sleeping until the mode is changed.
This one also looks fishy:
} else if (gMode == kPatternSaver) {
if (!gHasDisplayUpdatedForModeChange) {
drawSoundSaverInfoPane();
gHasDisplayUpdatedForModeChange = 1;
} else if (!gHasDisplayUpdatedForBlinkChange) {
drawSoundSaverInfoPane();
gHasDisplayUpdatedForBlinkChange = 1;
}
}
you are checking for two different conditions but calling the same function.
In a few places you are using if() { } else if() { } where you could actually use if(){}; if() {}. For instance:
if (!gHasDisplayUpdatedForModeChange) {
drawSoundSaverInfoPanes(gSaveState);
gHasDisplayUpdatedForModeChange = 1;
} else if (!gHasDisplayUpdatedForBlinkChange) {
drawSoundSaverInfoPane();
gHasDisplayUpdatedForBlinkChange = 1;
}
but I see no reason why the second block should start with and else. Ultimately, you want your displays to be updated if the corresponding flag has been set. If multiple flags are found to be set at once, just service all of them:
if (!gHasDisplayUpdatedForModeChange) {
drawSoundSaverInfoPanes(gSaveState);
gHasDisplayUpdatedForModeChange = 1;
}
if (!gHasDisplayUpdatedForBlinkChange) {
drawSoundSaverInfoPane();
gHasDisplayUpdatedForBlinkChange = 1;
}
From an execution stand point, not much changes and your code will run pretty much the same. With the current code, when at least on flag is found to be set at the beginning of the loop, the thread doesn't sleep and if at the next iteration of the loop there is still one flag set (because only one was reset during the previous iteration), again it won't sleep. However, servicing all outstanding flag in a single iteration allows to simplify your code greatly by resetting them all unconditionally at the end of the loop. This also fixes the suspect bug above I pointed out above (flag read and not reset):
ovoid updateDisplays(void *) {
while (!Bela_stopRequested()) {
if (gDisplaysInitialized == 1) {
if (gHasDisplayUpdatedForModeChange == 0 && gHasDisplayUpdatedForSelectedStepChange == 0 && gHasDisplayUpdatedForBlinkChange == 0) {
usleep(gTaskSleepTime);
continue;
}
std::vector < Sample * > samples = gSequence.getSamples();
std::vector < Step * > steps = gSequence.getSteps();
Step * currentStep = gSequence.getCurrentStep();
int currentStepDisplay = currentStep -> getDisplayIndex();
int currentPlayheadDisplay = currentStep -> getPlayheadDisplayIndex();
int stepBoxToDraw = 0;
if (gMode == kSoundLoader) {
if (!gHasDisplayUpdatedForModeChange)
drawSoundLoaderInfoPanes(gSavedState);
} else if (gMode == kSoundSaver) {
if (!gHasDisplayUpdatedForModeChange)
drawSoundSaverInfoPanes(gSaveState);
if (!gHasDisplayUpdatedForBlinkChange)
drawSoundSaverInfoPane();
} else if (gMode == kLooper) {
if (!gHasDisplayUpdatedForModeChange)
drawLooperInfoPanes(gSavedState);
if (!gHasDisplayUpdatedForBlinkChange)
drawLooperInfoPanes();
} else if (gMode == kSequencer) {
if (gSequencerMode == kSequencerMenu) {
if (!gHasDisplayUpdatedForModeChange) {
drawSequencerMenuPanes();
gHasDisplayUpdatedForModeChange = 1;
}
} else if (gSequencerMode == kSequencerPattern) {
if (!gHasDisplayUpdatedForSelectedStepChange)
drawSequencerPatternForSelectedStepChange(gSequence);
if (!gHasDisplayUpdatedForModeChange) {
drawSequencerInfoPanel(gSequence);
drawAllSequencerDisplays(&stepBoxToDraw, currentStep->getIndex(), gSequence, steps, samples);
}
// TODO: double check that I didn't change the desired behaviour here by removing else if
if (gTransportState == kPlaying)
drawSequencerPatternForPlayback(currentPlayheadDisplay, currentStepDisplay, currentStep -> getIndex(), &stepBoxToDraw, steps, gSequence.getNumberOfSteps(), gSequence.getNumberOfSamples());
gLastCurrentStepDisplay = currentStepDisplay;
} else if (gSequencerMode == kSequencerSampler) {
drawSequencerSamplerMenuPane();
drawAllSequencerDisplays(&stepBoxToDraw, currentStep->getIndex(), gSequence, steps, samples);
} else if (gSequencerMode == kSequencerParameter) {
if (gPatternParameter == kPatternParameterNone) {
drawSequencerParametersMenu();
} else {
drawSequencerParameter(gSong, gSequence);
}
}
} else if (gMode == kPatternLoader) {
if (!gHasDisplayUpdatedForModeChange)
drawSoundLoaderInfoPanes();
} else if (gMode == kPatternSaver) {
if (!gHasDisplayUpdatedForModeChange)
drawSoundSaverInfoPane();
if (!gHasDisplayUpdatedForBlinkChange)
drawSoundSaverInfoPane();
}
// reset all flags unconditionally
gHasDisplayUpdatedForModeChange = 1;
gHasDisplayUpdatedForSelectedStepChange = 1;
gHasDisplayUpdatedForBlinkChange = 1;
}
}
}
Note that here you have the same pathological race conditions outlined in the previous post:
check flag
do something that takes a lot of flag
reset flag
In order to minimise chances of this being a problem, flags can be reset immediately after the check by caching their values in local variables. E.g.:
void updateDisplays(void *) {
while (!Bela_stopRequested()) {
if (gDisplaysInitialized == 1) {
if (gHasDisplayUpdatedForModeChange == 0 && gHasDisplayUpdatedForSelectedStepChange == 0 && gHasDisplayUpdatedForBlinkChange == 0) {
usleep(gTaskSleepTime);
continue;
}
// cache globals to local
bool hasDisplayUpdatedForModeChange = gHasDisplayUpdatedForModeChange;
bool hasDisplayUpdatedForSelectedStepChange = gHasDisplayUpdatedForSelectedStepChange;
bool hasDisplayUpdatedForBlinkChange = gHasDisplayUpdatedForBlinkChange;
// reset all global flags unconditionally
gHasDisplayUpdatedForModeChange = 1;
gHasDisplayUpdatedForSelectedStepChange = 1;
gHasDisplayUpdatedForBlinkChange = 1;
// .... more code here
// now only check against local and not against global
if (gMode == kSoundLoader) {
if (!hasDisplayUpdatedForModeChange)
drawSoundLoaderInfoPanes(gSavedState);
} else if (gMode == kSoundSaver) {
if (!hasDisplayUpdatedForModeChange)
drawSoundSaverInfoPanes(gSaveState);
if (!hasDisplayUpdatedForBlinkChange)
drawSoundSaverInfoPane();
//.....rest of code here
}
}
again, similar caveats to the previous post apply. An added advantage of using a local flag is that you can implement any thread-safety improvements right there without scattering them all over the condition checking.
As for more feedback on your code, I don't know enough about your application to know whether you could simplify the flag mechanism to a single flag and then inferring the rest from gMode. You could be using a switch statement instead of the various else if chain for gMode (although that's just a stylistic change that I don't feel strongly about).
Patterns like this :
drawSoundLoaderInfoPane();
drawSoundLoaderInfoPane2(gSavedState);
drawSoundLoaderInfoPane3(gSavedState);
drawSoundLoaderInfoPane4(gSavedState);
drawSoundLoaderInfoPane5(gSavedState);
are usually red flags for me: what is different between the various functions ? would you be able to refactor the implementation of the various drawSoundLoaderInfoPaneX() functions by having a single function that gets passed an index, e.g.:
for(unsigned int n = 0; n < displays.size(); ++n)
drawSoundLoaderInfoPane(n, gSavedState);
would this simplify the code, removing (quasi-)duplicated lines or would it make it more complicated?