diff --git a/src/activities/Activity.cpp b/src/activities/Activity.cpp index f95b4dd1..4dd5ada3 100644 --- a/src/activities/Activity.cpp +++ b/src/activities/Activity.cpp @@ -8,11 +8,7 @@ void Activity::onExit() { LOG_DBG("ACT", "Exiting activity: %s", name.c_str()); void Activity::requestUpdate(bool immediate) { activityManager.requestUpdate(immediate); } -void Activity::requestUpdateAndWait() { - // FIXME @ngxson : properly implement this using freeRTOS notification - activityManager.requestUpdate(true); - delay(100); -} +void Activity::requestUpdateAndWait() { activityManager.requestUpdateAndWait(); } void Activity::onGoHome() { activityManager.goHome(); } diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index cf14a5c8..cb8336fa 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -38,6 +38,15 @@ void ActivityManager::renderTaskLoop() { HalPowerManager::Lock powerLock; // Ensure we don't go into low-power mode while rendering currentActivity->render(std::move(lock)); } + // Notify any task blocked in requestUpdateAndWait() that the render is done. + TaskHandle_t waiter = nullptr; + taskENTER_CRITICAL(nullptr); + waiter = waitingTaskHandle; + waitingTaskHandle = nullptr; + taskEXIT_CRITICAL(nullptr); + if (waiter) { + xTaskNotify(waiter, 1, eIncrement); + } } } @@ -225,6 +234,36 @@ void ActivityManager::requestUpdate(bool immediate) { requestedUpdate = true; } } +void ActivityManager::requestUpdateAndWait() { + if (!renderTaskHandle) { + return; + } + + // Atomic section to perform checks + taskENTER_CRITICAL(nullptr); + auto currTaskHandler = xTaskGetCurrentTaskHandle(); + auto mutexHolder = xSemaphoreGetMutexHolder(renderingMutex); + bool isRenderTask = (currTaskHandler == renderTaskHandle); + bool alreadyWaiting = (waitingTaskHandle != nullptr); + bool holdingRenderLock = (mutexHolder == currTaskHandler); + if (!alreadyWaiting && !isRenderTask && !holdingRenderLock) { + waitingTaskHandle = currTaskHandler; + } + taskEXIT_CRITICAL(nullptr); + + // Render task cannot call requestUpdateAndWait() or it will cause a deadlock + assert(!isRenderTask && "Render task cannot call requestUpdateAndWait()"); + + // There should never be the case where 2 tasks are waiting for a render at the same time + assert(!alreadyWaiting && "Already waiting for a render to complete"); + + // Cannot call while holding RenderLock or it will cause a deadlock + assert(!holdingRenderLock && "Cannot call requestUpdateAndWait() while holding RenderLock"); + + xTaskNotify(renderTaskHandle, 1, eIncrement); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); +} + // RenderLock RenderLock::RenderLock() { diff --git a/src/activities/ActivityManager.h b/src/activities/ActivityManager.h index bd206730..047276f0 100644 --- a/src/activities/ActivityManager.h +++ b/src/activities/ActivityManager.h @@ -50,6 +50,10 @@ class ActivityManager { static void renderTaskTrampoline(void* param); [[noreturn]] virtual void renderTaskLoop(); + // Set by requestUpdateAndWait(); read and cleared by the render task after render completes. + // Note: only one waiting task is supported at a time + TaskHandle_t waitingTaskHandle = nullptr; + // Mutex to protect rendering operations from race conditions // Must only be used via RenderLock SemaphoreHandle_t renderingMutex = nullptr; @@ -98,6 +102,10 @@ class ActivityManager { // If immediate is true, the update will be triggered immediately. // Otherwise, it will be deferred until the end of the current loop iteration. void requestUpdate(bool immediate = false); + + // Trigger a render and block until it completes. + // Must NOT be called from the render task or while holding a RenderLock. + void requestUpdateAndWait(); }; extern ActivityManager activityManager; // singleton, to be defined in main.cpp diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index 06ab788f..85650df9 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -201,6 +201,7 @@ void WifiSelectionActivity::selectNetwork(const int index) { state = WifiSelectionState::NETWORK_LIST; } else { enteredPassword = std::get(result.data).text; + // state will be updated in next loop iteration } }); } else { @@ -465,7 +466,6 @@ void WifiSelectionActivity::render(RenderLock&&) { // Don't render if we're in PASSWORD_ENTRY state - we're just transitioning // from the keyboard subactivity back to the main activity if (state == WifiSelectionState::PASSWORD_ENTRY) { - requestUpdateAndWait(); return; }