Where is the data sent to the display in your code? I would really appreciate if you were to post your full code, or at least all the relevant parts instead of having to second-guess things ... Not because I am curious, but because it is important in order to understand the issue at hand.
First step simplification: your code has a lot of redundancy, it is checking for the value of gHasDisplayUpdatedForModeChange so many times ... and it could be simplified significantly, e.g."
void updateDisplays(void *) {
while (!Bela_stopRequested()) {
// early condition check, easier to read for me
if (!gDisplaysInitialized || gHasDisplayUpdatedForModeChange)
{
usleep(gTaskSleepTime);
continue;
}
// if we are here, we need to send a refresh
printf("MODE CHANGED\n");
if (gMode == kMode1) {
rt_printf("drawing mode 1\n");
} else if (gMode == kMode2) {
rt_printf("drawing mode 32\n");
}
gHasDisplayUpdatedForModeChange = 1;
}
}
Now if I assume that alongside the rt_printf() statements you are also calling sendBuffer(), one starts to see where the issue could arise: you are checking a flag, doing something that takes time (sending to display) and then resetting the flag. If the audio thread in the meantime has set the flag again, that will go unnoticed, and you will have sent out the last-but-one state thinking you sent out the latest. I am surprised this happens, though, because as you are triggering writes with a button, there should be plenty of time for this thread to run before the next write is triggered.
This is a common pitfall of this simple flagging mechanism. One simple way to dramatically reduce the chances of that happening is to reset the flag immediately as you checked it, before any time-consuming operation:
void updateDisplays(void *) {
while (!Bela_stopRequested()) {
// early condition check, easier to read
if (!gDisplaysInitialized || gHasDisplayUpdatedForModeChange)
{
usleep(gTaskSleepTime);
continue;
}
// immediately reset the flag
gHasDisplayUpdatedForModeChange = 1;
// if we are here, we need to send a refresh
printf("MODE CHANGED\n");
// do the time-intensive stuff
if (gMode == kMode1) {
rt_printf("drawing mode 1\n");
} else if (gMode == kMode2) {
rt_printf("drawing mode 32\n");
}
}
}
again, this is not thread-safe, in that the audio thread could preempt this thread after you checked the flag and before you reset it, but if gMode is updated before gHasDisplayUpdatedForModeChange (which requires special provisions in order to happen in C++ *), then you could be fine. Other options would be to have two flags: one global and one local to the auxiliary thread. The audio thread this time increases the global flag (instead of setting it to a fixed value) and the auxiliary thread "plays catch up" with it, e.g.:
volatile int gMode;
volatile int gFlag;
void updateDisplays(void *) {
int myFlag = gFlag;
while (!Bela_stopRequested()) {
// early condition check, easier to read for me
if (!gDisplaysInitialized || myFlag == gFlag)
{
usleep(gTaskSleepTime);
continue;
}
// if we are here, we need to send a refresh
myFlag = gFlag;
if (gMode == kMode1) {
rt_printf("drawing mode 1\n");
} else if (gMode == kMode2) {
rt_printf("drawing mode 32\n");
}
}
}
void render(BelaContext* context, void*)
{
/// if button pressed ...
gMode = newMode;
gFlag++;
}
Again, because of *, it could still be that gMode is not update by the time you read gFlag ... The proper way of doing this would be to use C++11 atomics, or use a Pipe that is rt- and thread- safe. However, both would add some (however minimal) overhead to both threads. Using a Pipe, has the advantage that the display thread could be awaken exactly when needed (it wouldn't have to sleep and poll a global flag), but on the other hand if you send too many state changes too fast, it will have to read them all before being able to send out the latest, and each of these reads requires a system call to read from a virtual file, so there is a bit of overhead there, too.
In your case, though, if you only need to update the display if gMode has changed, the simplest (and this time foolproof) way of doing it would be to keep a copy of the state in the display thread and only send out if the current state is different from the last state that was sent to the display:
volatile int gMode;
void updateDisplays(void *) {
int myMode = gMode;
while (!Bela_stopRequested()) {
// early condition check, easier to read for me
if (!gDisplaysInitialized || myMode == gMode)
{
usleep(gTaskSleepTime);
continue;
}
// if we are here, we need to send a refresh
myMode = gMode;
// from now on, use the local copy of the variable
if (myMode == kMode1) {
rt_printf("drawing mode 1\n");
} else if (myMode == kMode2) {
rt_printf("drawing mode 32\n");
}
}
}
void render(BelaContext* context, void*)
{
/// if button pressed ...
gMode = newMode;
}
In this case, all previous considerations still apply: it may be that by the time you are done writing to the screen in the display thread, the global variable has changed again, perhaps even more than once and so by the time you reach the last instruction of the while() loop, the display may already be out of sync with the global state. This time, however we can catch that case because our local copy myMode of the global variable will tell us at the next iteration of the loop that we need to refresh again. In this case, if the audio thread sets gMode in rapid succession to, e.g.: 1 0 1 0 1, the display thread may lose some of the transitions and so display e.g.: only 1 0 1 (dropping some frames), but it will always land on the last one that was set, which is probably what you want for this application. This means that after a button press (and/or several repeated ones), once you stop pressing buttons, the display will be up to date in a matter of a few milliseconds. Note that this trick works nicely because the global variable here is 4-byte long and fully represents the state. This method may not scale well when the global state becomes larger as the overhead of memory copy and comparing may become significant and/or the global state as seen by the display thread may become inconsistent while the audio thread is writing to it.
*: I am not an expert here, but keep in mind that in general simply writing
gMode = 1;
gHasDisplayUpdatedForModeChange = 0;
does not guarantee that the first write happens before the second and even less that another thread would see the results appear in that order. Declaring both global variables volatile would help in that sense, but you'd really need a memory barrier.