Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
11 kB · 311 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312#include "ActivityManager.h"
#include <HalPowerManager.h>
#include "OpdsServerStore.h"#include "boot_sleep/BootActivity.h"#include "boot_sleep/SleepActivity.h"#include "browser/OpdsBookBrowserActivity.h"#include "home/CrashActivity.h"#include "home/FileBrowserActivity.h"#include "home/HomeActivity.h"#include "home/RecentBooksActivity.h"#include "network/CrossPointWebServerActivity.h"#include "reader/ReaderActivity.h"#include "settings/OpdsServerListActivity.h"#include "settings/SettingsActivity.h"#include "util/FullScreenMessageActivity.h"
void ActivityManager::begin() { xTaskCreate(&renderTaskTrampoline, "ActivityManagerRender", 8192, // Stack size this, // Parameters 1, // Priority &renderTaskHandle // Task handle ); assert(renderTaskHandle != nullptr && "Failed to create render task");}
void ActivityManager::renderTaskTrampoline(void* param) { auto* self = static_cast<ActivityManager*>(param); self->renderTaskLoop();}
void ActivityManager::renderTaskLoop() { while (true) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Acquire the lock before reading currentActivity to avoid a TOCTOU race // where the main task deletes the activity between the null-check and render(). RenderLock lock; if (currentActivity) { 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); } }}
void ActivityManager::loop() { if (currentActivity) { // Note: do not hold a lock here, the loop() method must be responsible for acquire one if needed currentActivity->loop(); }
while (pendingAction != PendingAction::None) { if (pendingAction == PendingAction::Pop) { RenderLock lock;
if (!currentActivity) { // Should never happen in practice LOG_ERR("ACT", "Pop set but currentActivity is null; ignoring pop request"); pendingAction = PendingAction::None; continue; }
ActivityResult pendingResult = std::move(currentActivity->result);
// Destroy the current activity exitActivity(lock); pendingAction = PendingAction::None;
if (stackActivities.empty()) { LOG_DBG("ACT", "No more activities on stack, going home"); lock.unlock(); // goHome may acquire its own lock goHome(); continue; // Will launch goHome immediately
} else { currentActivity = std::move(stackActivities.back()); stackActivities.pop_back(); LOG_DBG("ACT", "Popped from activity stack, new size = %zu", stackActivities.size()); // Handle result if necessary if (currentActivity->resultHandler) { LOG_DBG("ACT", "Handling result for popped activity");
// Move it here to avoid the case where handler calling another startActivityForResult() auto handler = std::move(currentActivity->resultHandler); currentActivity->resultHandler = nullptr; lock.unlock(); // Handler may acquire its own lock handler(pendingResult); }
// Request an update to ensure the popped activity gets re-rendered if (pendingAction == PendingAction::None) { requestUpdate(); }
// Handler may request another pending action, we will handle it in the next loop iteration continue; }
} else if (pendingActivity) { // Current activity has requested a new activity to be launched RenderLock lock;
if (pendingAction == PendingAction::Replace) { // Destroy the current activity exitActivity(lock); // Clear the stack while (!stackActivities.empty()) { stackActivities.back()->onExit(); stackActivities.pop_back(); } } else if (pendingAction == PendingAction::Push) { // Move current activity to stack stackActivities.push_back(std::move(currentActivity)); LOG_DBG("ACT", "Pushed to activity stack, new size = %zu", stackActivities.size()); } pendingAction = PendingAction::None; currentActivity = std::move(pendingActivity);
lock.unlock(); // onEnter may acquire its own lock currentActivity->onEnter();
// onEnter may request another pending action, we will handle it in the next loop iteration continue; } }
if (requestedUpdate) { requestedUpdate = false; // Using direct notification to signal the render task to update // Increment counter so multiple rapid calls won't be lost if (renderTaskHandle) { xTaskNotify(renderTaskHandle, 1, eIncrement); } }}
void ActivityManager::exitActivity(const RenderLock& lock) { // Note: lock must be held by the caller if (currentActivity) { currentActivity->onExit(); currentActivity.reset(); }}
void ActivityManager::replaceActivity(std::unique_ptr<Activity>&& newActivity) { // Note: no lock here, this is usually called by loop() and we may run into deadlock if (currentActivity) { // Defer launch if we're currently in an activity, to avoid deleting the current activity // leading to the "delete this" problem pendingActivity = std::move(newActivity); pendingAction = PendingAction::Replace; } else { // No current activity, safe to launch immediately currentActivity = std::move(newActivity); currentActivity->onEnter(); }}
void ActivityManager::goToFileTransfer() { replaceActivity(std::make_unique<CrossPointWebServerActivity>(renderer, mappedInput));}
void ActivityManager::goToSettings() { replaceActivity(std::make_unique<SettingsActivity>(renderer, mappedInput)); }
void ActivityManager::goToFileBrowser(std::string path) { replaceActivity(std::make_unique<FileBrowserActivity>(renderer, mappedInput, std::move(path)));}
void ActivityManager::goToRecentBooks() { replaceActivity(std::make_unique<RecentBooksActivity>(renderer, mappedInput));}
void ActivityManager::goToBrowser() { const auto& servers = OPDS_STORE.getServers(); // Skip the server picker when there's only one server configured if (servers.size() == 1) { replaceActivity(std::make_unique<OpdsBookBrowserActivity>(renderer, mappedInput, servers[0])); } else { replaceActivity(std::make_unique<OpdsServerListActivity>(renderer, mappedInput, true)); }}
void ActivityManager::goToReader(std::string path) { replaceActivity(std::make_unique<ReaderActivity>(renderer, mappedInput, std::move(path)));}
void ActivityManager::goToSleep() { replaceActivity(std::make_unique<SleepActivity>(renderer, mappedInput)); loop(); // Important: sleep screen must be rendered immediately, the caller will go to sleep right after this returns}
void ActivityManager::goToBoot() { replaceActivity(std::make_unique<BootActivity>(renderer, mappedInput)); }
void ActivityManager::goToFullScreenMessage(std::string message, EpdFontFamily::Style style) { replaceActivity(std::make_unique<FullScreenMessageActivity>(renderer, mappedInput, std::move(message), style));}
void ActivityManager::goToCrashReport() { replaceActivity(std::make_unique<CrashActivity>(renderer, mappedInput)); }
void ActivityManager::goHome() { replaceActivity(std::make_unique<HomeActivity>(renderer, mappedInput)); }
void ActivityManager::pushActivity(std::unique_ptr<Activity>&& activity) { if (pendingActivity) { // Should never happen in practice LOG_ERR("ACT", "pendingActivity while pushActivity is not expected"); pendingActivity.reset(); } pendingActivity = std::move(activity); pendingAction = PendingAction::Push;}
void ActivityManager::popActivity() { if (pendingActivity) { // Should never happen in practice LOG_ERR("ACT", "pendingActivity while popActivity is not expected"); pendingActivity.reset(); } pendingAction = PendingAction::Pop;}
bool ActivityManager::preventAutoSleep() const { return currentActivity && currentActivity->preventAutoSleep(); }
bool ActivityManager::isReaderActivity() const { return currentActivity && currentActivity->isReaderActivity(); }
bool ActivityManager::skipLoopDelay() const { return currentActivity && currentActivity->skipLoopDelay(); }
void ActivityManager::requestUpdate(bool immediate) { if (immediate) { if (renderTaskHandle) { xTaskNotify(renderTaskHandle, 1, eIncrement); } } else { // Deferring the update until current loop is finished // This is to avoid multiple updates being requested in the same loop 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() { xSemaphoreTake(activityManager.renderingMutex, portMAX_DELAY); isLocked = true;}
RenderLock::RenderLock([[maybe_unused]] Activity&) { xSemaphoreTake(activityManager.renderingMutex, portMAX_DELAY); isLocked = true;}
RenderLock::~RenderLock() { if (isLocked) { xSemaphoreGive(activityManager.renderingMutex); isLocked = false; }}
void RenderLock::unlock() { if (isLocked) { xSemaphoreGive(activityManager.renderingMutex); isLocked = false; }}
/** * * Checks if renderingMutex is busy. * * @return true if renderingMutex is busy, otherwise false. * */bool RenderLock::peek() { return xQueuePeek(activityManager.renderingMutex, NULL, 0) != pdTRUE; };