refactor: move render() to Activity super class, use freeRTOS notification (#774)
## Summary Currently, each activity has to manage their own `displayTaskLoop` which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the `updateRequested` boolean is not protected by a mutex. In this PR: - Move `displayTaskLoop` to the super `Activity` class - Replace `updateRequested` with freeRTOS's [direct to task notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications) - For `ActivityWithSubactivity`, whenever a sub-activity is present, the parent's `render()` automatically goes inactive With this change, activities now only need to expose `render()` function, and anywhere in the code base can call `requestUpdate()` to request a new rendering pass. ## Additional Context In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject. Update: I managed to hack [a small piece of code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage) that allow tracking CPU idle time. The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run. ``` PR: [40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%) [60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes [60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%) master: [20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%) [40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes [40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%) ``` --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency. * Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering. * **Bug Fixes** * Enhanced rendering stability through improved locking mechanisms and explicit update requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: znelson <znelson@users.noreply.github.com>
This commit is contained in:
@@ -57,11 +57,6 @@ void applyReaderOrientation(GfxRenderer& renderer, const uint8_t orientation) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void EpubReaderActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<EpubReaderActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void EpubReaderActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
@@ -73,8 +68,6 @@ void EpubReaderActivity::onEnter() {
|
||||
// NOTE: This affects layout math and must be applied before any render calls.
|
||||
applyReaderOrientation(renderer, SETTINGS.orientation);
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
epub->setupCacheDir();
|
||||
|
||||
FsFile f;
|
||||
@@ -108,14 +101,7 @@ void EpubReaderActivity::onEnter() {
|
||||
RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor(), epub->getThumbBmpPath());
|
||||
|
||||
// Trigger first update
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&EpubReaderActivity::taskTrampoline, "EpubReaderActivityTask",
|
||||
8192, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void EpubReaderActivity::onExit() {
|
||||
@@ -124,14 +110,6 @@ void EpubReaderActivity::onExit() {
|
||||
// Reset orientation back to portrait for the rest of the UI
|
||||
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
|
||||
|
||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
APP_STATE.readerActivityLoadCount = 0;
|
||||
APP_STATE.saveToFile();
|
||||
section.reset();
|
||||
@@ -146,7 +124,7 @@ void EpubReaderActivity::loop() {
|
||||
if (pendingSubactivityExit) {
|
||||
pendingSubactivityExit = false;
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
skipNextButtonCheck = true; // Skip button processing to ignore stale events
|
||||
}
|
||||
// Deferred go home: process after subActivity->loop() returns to avoid race condition
|
||||
@@ -186,8 +164,6 @@ void EpubReaderActivity::loop() {
|
||||
|
||||
// Enter reader menu activity.
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
// Don't start activity transition while rendering
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
const int currentPage = section ? section->currentPage + 1 : 0;
|
||||
const int totalPages = section ? section->pageCount : 0;
|
||||
float bookProgress = 0.0f;
|
||||
@@ -201,7 +177,6 @@ void EpubReaderActivity::loop() {
|
||||
this->renderer, this->mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
|
||||
SETTINGS.orientation, [this](const uint8_t orientation) { onReaderMenuBack(orientation); },
|
||||
[this](EpubReaderMenuActivity::MenuAction action) { onReaderMenuConfirm(action); }));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
|
||||
// Long press BACK (1s+) goes to file selection
|
||||
@@ -238,7 +213,7 @@ void EpubReaderActivity::loop() {
|
||||
if (currentSpineIndex > 0 && currentSpineIndex >= epub->getSpineItemsCount()) {
|
||||
currentSpineIndex = epub->getSpineItemsCount() - 1;
|
||||
nextPageNumber = UINT16_MAX;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -251,13 +226,13 @@ void EpubReaderActivity::loop() {
|
||||
currentSpineIndex = nextTriggered ? currentSpineIndex + 1 : currentSpineIndex - 1;
|
||||
section.reset();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
// No current section, attempt to rerender the book
|
||||
if (!section) {
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,7 +247,7 @@ void EpubReaderActivity::loop() {
|
||||
section.reset();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
} else {
|
||||
if (section->currentPage < section->pageCount - 1) {
|
||||
section->currentPage++;
|
||||
@@ -284,7 +259,7 @@ void EpubReaderActivity::loop() {
|
||||
section.reset();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +268,7 @@ void EpubReaderActivity::onReaderMenuBack(const uint8_t orientation) {
|
||||
// Apply the user-selected orientation when the menu is dismissed.
|
||||
// This ensures the menu can be navigated without immediately rotating the screen.
|
||||
applyOrientation(orientation);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
// Translate an absolute percent into a spine index plus a normalized position
|
||||
@@ -349,7 +324,7 @@ void EpubReaderActivity::jumpToPercent(int percent) {
|
||||
pendingSpineProgress = 1.0f;
|
||||
}
|
||||
|
||||
// Reset state so renderScreen() reloads and repositions on the target spine.
|
||||
// Reset state so render() reloads and repositions on the target spine.
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
currentSpineIndex = targetSpineIndex;
|
||||
nextPageNumber = 0;
|
||||
@@ -367,8 +342,6 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
const int spineIdx = currentSpineIndex;
|
||||
const std::string path = epub->getPath();
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
|
||||
// 1. Close the menu
|
||||
exitActivity();
|
||||
|
||||
@@ -377,7 +350,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
this->renderer, this->mappedInput, epub, path, spineIdx, currentP, totalP,
|
||||
[this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
},
|
||||
[this](const int newSpineIndex) {
|
||||
if (currentSpineIndex != newSpineIndex) {
|
||||
@@ -386,7 +359,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
section.reset();
|
||||
}
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
},
|
||||
[this](const int newSpineIndex, const int newPage) {
|
||||
if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) {
|
||||
@@ -395,10 +368,9 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
section.reset();
|
||||
}
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}));
|
||||
|
||||
xSemaphoreGive(renderingMutex);
|
||||
break;
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::GO_TO_PERCENT: {
|
||||
@@ -409,7 +381,6 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f;
|
||||
}
|
||||
const int initialPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(new EpubReaderPercentSelectionActivity(
|
||||
renderer, mappedInput, initialPercent,
|
||||
@@ -417,14 +388,13 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
// Apply the new position and exit back to the reader.
|
||||
jumpToPercent(percent);
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
},
|
||||
[this]() {
|
||||
// Cancel selection and return to the reader.
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
break;
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::GO_HOME: {
|
||||
@@ -457,7 +427,6 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
}
|
||||
case EpubReaderMenuActivity::MenuAction::SYNC: {
|
||||
if (KOREADER_STORE.hasCredentials()) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
const int currentPage = section ? section->currentPage : 0;
|
||||
const int totalPages = section ? section->pageCount : 0;
|
||||
exitActivity();
|
||||
@@ -476,7 +445,6 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
}
|
||||
pendingSubactivityExit = true;
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -509,20 +477,8 @@ void EpubReaderActivity::applyOrientation(const uint8_t orientation) {
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
|
||||
void EpubReaderActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Failure handling
|
||||
void EpubReaderActivity::renderScreen() {
|
||||
void EpubReaderActivity::render(Activity::RenderLock&& lock) {
|
||||
if (!epub) {
|
||||
return;
|
||||
}
|
||||
@@ -643,7 +599,9 @@ void EpubReaderActivity::renderScreen() {
|
||||
LOG_ERR("ERS", "Failed to load page from SD - clearing section cache");
|
||||
section->clearCache();
|
||||
section.reset();
|
||||
return renderScreen();
|
||||
requestUpdate(); // Try again after clearing cache
|
||||
// TODO: prevent infinite loop if the page keeps failing to load for some reason
|
||||
return;
|
||||
}
|
||||
const auto start = millis();
|
||||
renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#pragma once
|
||||
#include <Epub.h>
|
||||
#include <Epub/Section.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include "EpubReaderMenuActivity.h"
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
@@ -11,8 +8,6 @@
|
||||
class EpubReaderActivity final : public ActivityWithSubactivity {
|
||||
std::shared_ptr<Epub> epub;
|
||||
std::unique_ptr<Section> section = nullptr;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
int currentSpineIndex = 0;
|
||||
int nextPageNumber = 0;
|
||||
int pagesUntilFullRefresh = 0;
|
||||
@@ -23,16 +18,12 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
|
||||
bool pendingPercentJump = false;
|
||||
// Normalized 0.0-1.0 progress within the target spine item, computed from book percentage.
|
||||
float pendingSpineProgress = 0.0f;
|
||||
bool updateRequired = false;
|
||||
bool pendingSubactivityExit = false; // Defer subactivity exit to avoid use-after-free
|
||||
bool pendingGoHome = false; // Defer go home to avoid race condition with display task
|
||||
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
|
||||
const std::function<void()> onGoBack;
|
||||
const std::function<void()> onGoHome;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void renderScreen();
|
||||
void renderContents(std::unique_ptr<Page> page, int orientedMarginTop, int orientedMarginRight,
|
||||
int orientedMarginBottom, int orientedMarginLeft);
|
||||
void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const;
|
||||
@@ -53,4 +44,5 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&& lock) override;
|
||||
};
|
||||
|
||||
@@ -24,11 +24,6 @@ int EpubReaderChapterSelectionActivity::getPageItems() const {
|
||||
return std::max(1, availableHeight / lineHeight);
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<EpubReaderChapterSelectionActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
@@ -36,35 +31,16 @@ void EpubReaderChapterSelectionActivity::onEnter() {
|
||||
return;
|
||||
}
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
selectorIndex = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||
if (selectorIndex == -1) {
|
||||
selectorIndex = 0;
|
||||
}
|
||||
|
||||
// Trigger first update
|
||||
updateRequired = true;
|
||||
xTaskCreate(&EpubReaderChapterSelectionActivity::taskTrampoline, "EpubReaderChapterSelectionActivityTask",
|
||||
4096, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
void EpubReaderChapterSelectionActivity::onExit() { ActivityWithSubactivity::onExit(); }
|
||||
|
||||
void EpubReaderChapterSelectionActivity::loop() {
|
||||
if (subActivity) {
|
||||
@@ -88,38 +64,26 @@ void EpubReaderChapterSelectionActivity::loop() {
|
||||
|
||||
buttonNavigator.onNextRelease([this, totalItems] {
|
||||
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPreviousRelease([this, totalItems] {
|
||||
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
|
||||
selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
|
||||
selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired && !subActivity) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::renderScreen() {
|
||||
void EpubReaderChapterSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
#include <Epub.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -12,14 +9,12 @@
|
||||
class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity {
|
||||
std::shared_ptr<Epub> epub;
|
||||
std::string epubPath;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
ButtonNavigator buttonNavigator;
|
||||
int currentSpineIndex = 0;
|
||||
int currentPage = 0;
|
||||
int totalPagesInSpine = 0;
|
||||
int selectorIndex = 0;
|
||||
bool updateRequired = false;
|
||||
|
||||
const std::function<void()> onGoBack;
|
||||
const std::function<void(int newSpineIndex)> onSelectSpineIndex;
|
||||
const std::function<void(int newSpineIndex, int newPage)> onSyncPosition;
|
||||
@@ -31,10 +26,6 @@ class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity
|
||||
// Total TOC items count
|
||||
int getTotalItems() const;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void renderScreen();
|
||||
|
||||
public:
|
||||
explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::shared_ptr<Epub>& epub, const std::string& epubPath,
|
||||
@@ -54,4 +45,5 @@ class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
};
|
||||
|
||||
@@ -8,39 +8,10 @@
|
||||
|
||||
void EpubReaderMenuActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&EpubReaderMenuActivity::taskTrampoline, "EpubMenuTask", 4096, this, 1, &displayTaskHandle);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void EpubReaderMenuActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void EpubReaderMenuActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<EpubReaderMenuActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void EpubReaderMenuActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired && !subActivity) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
void EpubReaderMenuActivity::onExit() { ActivityWithSubactivity::onExit(); }
|
||||
|
||||
void EpubReaderMenuActivity::loop() {
|
||||
if (subActivity) {
|
||||
@@ -51,12 +22,12 @@ void EpubReaderMenuActivity::loop() {
|
||||
// Handle navigation
|
||||
buttonNavigator.onNext([this] {
|
||||
selectedIndex = ButtonNavigator::nextIndex(selectedIndex, static_cast<int>(menuItems.size()));
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPrevious([this] {
|
||||
selectedIndex = ButtonNavigator::previousIndex(selectedIndex, static_cast<int>(menuItems.size()));
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
// Use local variables for items we need to check after potential deletion
|
||||
@@ -65,7 +36,7 @@ void EpubReaderMenuActivity::loop() {
|
||||
if (selectedAction == MenuAction::ROTATE_SCREEN) {
|
||||
// Cycle orientation preview locally; actual rotation happens on menu exit.
|
||||
pendingOrientation = (pendingOrientation + 1) % orientationLabels.size();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,7 +55,7 @@ void EpubReaderMenuActivity::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void EpubReaderMenuActivity::renderScreen() {
|
||||
void EpubReaderMenuActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto orientation = renderer.getOrientation();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
#include <Epub.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@@ -32,6 +29,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
|
||||
private:
|
||||
struct MenuItem {
|
||||
@@ -46,9 +44,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
||||
{MenuAction::SYNC, "Sync Progress"}, {MenuAction::DELETE_CACHE, "Delete Book Cache"}};
|
||||
|
||||
int selectedIndex = 0;
|
||||
bool updateRequired = false;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
|
||||
ButtonNavigator buttonNavigator;
|
||||
std::string title = "Reader Menu";
|
||||
uint8_t pendingOrientation = 0;
|
||||
@@ -59,8 +55,4 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
|
||||
|
||||
const std::function<void(uint8_t)> onBack;
|
||||
const std::function<void(MenuAction)> onAction;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void renderScreen();
|
||||
};
|
||||
|
||||
@@ -15,41 +15,10 @@ constexpr int kLargeStep = 10;
|
||||
void EpubReaderPercentSelectionActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
// Set up rendering task and mark first frame dirty.
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
updateRequired = true;
|
||||
xTaskCreate(&EpubReaderPercentSelectionActivity::taskTrampoline, "EpubPercentSlider", 4096, this, 1,
|
||||
&displayTaskHandle);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void EpubReaderPercentSelectionActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
// Ensure the render task is stopped before freeing the mutex.
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void EpubReaderPercentSelectionActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<EpubReaderPercentSelectionActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void EpubReaderPercentSelectionActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
// Render only when the view is dirty and no subactivity is running.
|
||||
if (updateRequired && !subActivity) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
void EpubReaderPercentSelectionActivity::onExit() { ActivityWithSubactivity::onExit(); }
|
||||
|
||||
void EpubReaderPercentSelectionActivity::adjustPercent(const int delta) {
|
||||
// Apply delta and clamp within 0-100.
|
||||
@@ -59,7 +28,7 @@ void EpubReaderPercentSelectionActivity::adjustPercent(const int delta) {
|
||||
} else if (percent > 100) {
|
||||
percent = 100;
|
||||
}
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void EpubReaderPercentSelectionActivity::loop() {
|
||||
@@ -86,7 +55,7 @@ void EpubReaderPercentSelectionActivity::loop() {
|
||||
buttonNavigator.onPressAndContinuous({MappedInputManager::Button::Down}, [this] { adjustPercent(-kLargeStep); });
|
||||
}
|
||||
|
||||
void EpubReaderPercentSelectionActivity::renderScreen() {
|
||||
void EpubReaderPercentSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
// Title and numeric percent value.
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#pragma once
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -23,15 +20,12 @@ class EpubReaderPercentSelectionActivity final : public ActivityWithSubactivity
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
|
||||
private:
|
||||
// Current percent value (0-100) shown on the slider.
|
||||
int percent = 0;
|
||||
// Render dirty flag for the task loop.
|
||||
bool updateRequired = false;
|
||||
// FreeRTOS task and mutex for rendering.
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
// Callback invoked when the user confirms a percent.
|
||||
@@ -39,10 +33,6 @@ class EpubReaderPercentSelectionActivity final : public ActivityWithSubactivity
|
||||
// Callback invoked when the user cancels the slider.
|
||||
const std::function<void()> onCancel;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
// Render the slider UI.
|
||||
void renderScreen();
|
||||
// Change the current percent by a delta and clamp within bounds.
|
||||
void adjustPercent(int delta);
|
||||
};
|
||||
|
||||
@@ -40,11 +40,6 @@ void syncTimeWithNTP() {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void KOReaderSyncActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<KOReaderSyncActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
exitActivity();
|
||||
|
||||
@@ -60,7 +55,7 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
state = SYNCING;
|
||||
statusMessage = "Syncing time...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
|
||||
// Sync time with NTP before making API requests
|
||||
syncTimeWithNTP();
|
||||
@@ -68,7 +63,7 @@ void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
statusMessage = "Calculating document hash...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
|
||||
performSync();
|
||||
}
|
||||
@@ -85,7 +80,7 @@ void KOReaderSyncActivity::performSync() {
|
||||
state = SYNC_FAILED;
|
||||
statusMessage = "Failed to calculate document hash";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,8 +89,7 @@ void KOReaderSyncActivity::performSync() {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
statusMessage = "Fetching remote progress...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
requestUpdateAndWait();
|
||||
|
||||
// Fetch remote progress
|
||||
const auto result = KOReaderSyncClient::getProgress(documentHash, remoteProgress);
|
||||
@@ -106,7 +100,7 @@ void KOReaderSyncActivity::performSync() {
|
||||
state = NO_REMOTE_PROGRESS;
|
||||
hasRemoteProgress = false;
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,7 +109,7 @@ void KOReaderSyncActivity::performSync() {
|
||||
state = SYNC_FAILED;
|
||||
statusMessage = KOReaderSyncClient::errorString(result);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,7 +126,7 @@ void KOReaderSyncActivity::performSync() {
|
||||
state = SHOWING_RESULT;
|
||||
selectedOption = 0; // Default to "Apply"
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::performUpload() {
|
||||
@@ -140,8 +134,8 @@ void KOReaderSyncActivity::performUpload() {
|
||||
state = UPLOADING;
|
||||
statusMessage = "Uploading progress...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
requestUpdate();
|
||||
requestUpdateAndWait();
|
||||
|
||||
// Convert current position to KOReader format
|
||||
CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPagesInSpine};
|
||||
@@ -159,32 +153,23 @@ void KOReaderSyncActivity::performUpload() {
|
||||
state = SYNC_FAILED;
|
||||
statusMessage = KOReaderSyncClient::errorString(result);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = UPLOAD_COMPLETE;
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
xTaskCreate(&KOReaderSyncActivity::taskTrampoline, "KOSyncTask",
|
||||
4096, // Stack size (larger for network operations)
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
|
||||
// Check for credentials first
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
state = NO_CREDENTIALS;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,7 +182,7 @@ void KOReaderSyncActivity::onEnter() {
|
||||
LOG_DBG("KOSync", "Already connected to WiFi");
|
||||
state = SYNCING;
|
||||
statusMessage = "Syncing time...";
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
|
||||
// Perform sync directly (will be handled in loop)
|
||||
xTaskCreate(
|
||||
@@ -208,7 +193,7 @@ void KOReaderSyncActivity::onEnter() {
|
||||
xSemaphoreTake(self->renderingMutex, portMAX_DELAY);
|
||||
self->statusMessage = "Calculating document hash...";
|
||||
xSemaphoreGive(self->renderingMutex);
|
||||
self->updateRequired = true;
|
||||
self->requestUpdate();
|
||||
self->performSync();
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
@@ -230,30 +215,9 @@ void KOReaderSyncActivity::onExit() {
|
||||
delay(100);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(100);
|
||||
|
||||
// Wait until not rendering to delete task
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
render();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::render() {
|
||||
void KOReaderSyncActivity::render(Activity::RenderLock&&) {
|
||||
if (subActivity) {
|
||||
return;
|
||||
}
|
||||
@@ -388,11 +352,11 @@ void KOReaderSyncActivity::loop() {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
selectedOption = (selectedOption + 1) % 2; // Wrap around among 2 options
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||
selectedOption = (selectedOption + 1) % 2; // Wrap around among 2 options
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
#include <Epub.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -45,6 +42,7 @@ class KOReaderSyncActivity final : public ActivityWithSubactivity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
bool preventAutoSleep() override { return state == CONNECTING || state == SYNCING; }
|
||||
|
||||
private:
|
||||
@@ -66,10 +64,6 @@ class KOReaderSyncActivity final : public ActivityWithSubactivity {
|
||||
int currentPage;
|
||||
int totalPagesInSpine;
|
||||
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
bool updateRequired = false;
|
||||
|
||||
State state = WIFI_SELECTION;
|
||||
std::string statusMessage;
|
||||
std::string documentHash;
|
||||
@@ -91,8 +85,4 @@ class KOReaderSyncActivity final : public ActivityWithSubactivity {
|
||||
void onWifiSelectionComplete(bool success);
|
||||
void performSync();
|
||||
void performUpload();
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void render();
|
||||
};
|
||||
|
||||
@@ -23,11 +23,6 @@ constexpr uint32_t CACHE_MAGIC = 0x54585449; // "TXTI"
|
||||
constexpr uint8_t CACHE_VERSION = 2; // Increment when cache format changes
|
||||
} // namespace
|
||||
|
||||
void TxtReaderActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<TxtReaderActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void TxtReaderActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
@@ -53,8 +48,6 @@ void TxtReaderActivity::onEnter() {
|
||||
break;
|
||||
}
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
txt->setupCacheDir();
|
||||
|
||||
// Save current txt as last opened file and add to recent books
|
||||
@@ -65,14 +58,7 @@ void TxtReaderActivity::onEnter() {
|
||||
RECENT_BOOKS.addBook(filePath, fileName, "", "");
|
||||
|
||||
// Trigger first update
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&TxtReaderActivity::taskTrampoline, "TxtReaderActivityTask",
|
||||
6144, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void TxtReaderActivity::onExit() {
|
||||
@@ -81,14 +67,6 @@ void TxtReaderActivity::onExit() {
|
||||
// Reset orientation back to portrait for the rest of the UI
|
||||
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
|
||||
|
||||
// Wait until not rendering to delete task
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
pageOffsets.clear();
|
||||
currentPageLines.clear();
|
||||
APP_STATE.readerActivityLoadCount = 0;
|
||||
@@ -134,22 +112,10 @@ void TxtReaderActivity::loop() {
|
||||
|
||||
if (prevTriggered && currentPage > 0) {
|
||||
currentPage--;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
} else if (nextTriggered && currentPage < totalPages - 1) {
|
||||
currentPage++;
|
||||
updateRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TxtReaderActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +338,7 @@ bool TxtReaderActivity::loadPageAtOffset(size_t offset, std::vector<std::string>
|
||||
return !outLines.empty();
|
||||
}
|
||||
|
||||
void TxtReaderActivity::renderScreen() {
|
||||
void TxtReaderActivity::render(Activity::RenderLock&&) {
|
||||
if (!txt) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <Txt.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -12,12 +9,11 @@
|
||||
|
||||
class TxtReaderActivity final : public ActivityWithSubactivity {
|
||||
std::unique_ptr<Txt> txt;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
|
||||
int currentPage = 0;
|
||||
int totalPages = 1;
|
||||
int pagesUntilFullRefresh = 0;
|
||||
bool updateRequired = false;
|
||||
|
||||
const std::function<void()> onGoBack;
|
||||
const std::function<void()> onGoHome;
|
||||
|
||||
@@ -33,9 +29,6 @@ class TxtReaderActivity final : public ActivityWithSubactivity {
|
||||
int cachedScreenMargin = 0;
|
||||
uint8_t cachedParagraphAlignment = CrossPointSettings::LEFT_ALIGN;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void renderScreen();
|
||||
void renderPage();
|
||||
void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const;
|
||||
|
||||
@@ -57,4 +50,5 @@ class TxtReaderActivity final : public ActivityWithSubactivity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
};
|
||||
|
||||
@@ -24,11 +24,6 @@ constexpr unsigned long skipPageMs = 700;
|
||||
constexpr unsigned long goHomeMs = 1000;
|
||||
} // namespace
|
||||
|
||||
void XtcReaderActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<XtcReaderActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void XtcReaderActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
@@ -36,8 +31,6 @@ void XtcReaderActivity::onEnter() {
|
||||
return;
|
||||
}
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
xtc->setupCacheDir();
|
||||
|
||||
// Load saved progress
|
||||
@@ -49,27 +42,12 @@ void XtcReaderActivity::onEnter() {
|
||||
RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), xtc->getAuthor(), xtc->getThumbBmpPath());
|
||||
|
||||
// Trigger first update
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&XtcReaderActivity::taskTrampoline, "XtcReaderActivityTask",
|
||||
4096, // Stack size (smaller than EPUB since no parsing needed)
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void XtcReaderActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
// Wait until not rendering to delete task
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
APP_STATE.readerActivityLoadCount = 0;
|
||||
APP_STATE.saveToFile();
|
||||
xtc.reset();
|
||||
@@ -85,20 +63,18 @@ void XtcReaderActivity::loop() {
|
||||
// Enter chapter selection activity
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
if (xtc && xtc->hasChapters() && !xtc->getChapters().empty()) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(new XtcReaderChapterSelectionActivity(
|
||||
this->renderer, this->mappedInput, xtc, currentPage,
|
||||
[this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
},
|
||||
[this](const uint32_t newPage) {
|
||||
currentPage = newPage;
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +111,7 @@ void XtcReaderActivity::loop() {
|
||||
// Handle end of book
|
||||
if (currentPage >= xtc->getPageCount()) {
|
||||
currentPage = xtc->getPageCount() - 1;
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,29 +124,17 @@ void XtcReaderActivity::loop() {
|
||||
} else {
|
||||
currentPage = 0;
|
||||
}
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
} else if (nextTriggered) {
|
||||
currentPage += skipAmount;
|
||||
if (currentPage >= xtc->getPageCount()) {
|
||||
currentPage = xtc->getPageCount(); // Allow showing "End of book"
|
||||
}
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void XtcReaderActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void XtcReaderActivity::renderScreen() {
|
||||
void XtcReaderActivity::render(Activity::RenderLock&&) {
|
||||
if (!xtc) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,25 +8,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <Xtc.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
|
||||
class XtcReaderActivity final : public ActivityWithSubactivity {
|
||||
std::shared_ptr<Xtc> xtc;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
|
||||
uint32_t currentPage = 0;
|
||||
int pagesUntilFullRefresh = 0;
|
||||
bool updateRequired = false;
|
||||
|
||||
const std::function<void()> onGoBack;
|
||||
const std::function<void()> onGoHome;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void renderScreen();
|
||||
void renderPage();
|
||||
void saveProgress() const;
|
||||
void loadProgress();
|
||||
@@ -41,4 +34,5 @@ class XtcReaderActivity final : public ActivityWithSubactivity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
};
|
||||
|
||||
@@ -37,11 +37,6 @@ int XtcReaderChapterSelectionActivity::findChapterIndexForPage(uint32_t page) co
|
||||
return 0;
|
||||
}
|
||||
|
||||
void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<XtcReaderChapterSelectionActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void XtcReaderChapterSelectionActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
|
||||
@@ -49,29 +44,12 @@ void XtcReaderChapterSelectionActivity::onEnter() {
|
||||
return;
|
||||
}
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
selectorIndex = findChapterIndexForPage(currentPage);
|
||||
|
||||
updateRequired = true;
|
||||
xTaskCreate(&XtcReaderChapterSelectionActivity::taskTrampoline, "XtcReaderChapterSelectionActivityTask",
|
||||
4096, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void XtcReaderChapterSelectionActivity::onExit() {
|
||||
Activity::onExit();
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
void XtcReaderChapterSelectionActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void XtcReaderChapterSelectionActivity::loop() {
|
||||
const int pageItems = getPageItems();
|
||||
@@ -88,38 +66,26 @@ void XtcReaderChapterSelectionActivity::loop() {
|
||||
|
||||
buttonNavigator.onNextRelease([this, totalItems] {
|
||||
selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPreviousRelease([this, totalItems] {
|
||||
selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onNextContinuous([this, totalItems, pageItems] {
|
||||
selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
|
||||
buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] {
|
||||
selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems);
|
||||
updateRequired = true;
|
||||
requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
void XtcReaderChapterSelectionActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void XtcReaderChapterSelectionActivity::renderScreen() {
|
||||
void XtcReaderChapterSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#pragma once
|
||||
#include <Xtc.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -11,22 +8,16 @@
|
||||
|
||||
class XtcReaderChapterSelectionActivity final : public Activity {
|
||||
std::shared_ptr<Xtc> xtc;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
ButtonNavigator buttonNavigator;
|
||||
uint32_t currentPage = 0;
|
||||
int selectorIndex = 0;
|
||||
bool updateRequired = false;
|
||||
|
||||
const std::function<void()> onGoBack;
|
||||
const std::function<void(uint32_t newPage)> onSelectPage;
|
||||
|
||||
int getPageItems() const;
|
||||
int findChapterIndexForPage(uint32_t page) const;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void renderScreen();
|
||||
|
||||
public:
|
||||
explicit XtcReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::shared_ptr<Xtc>& xtc, uint32_t currentPage,
|
||||
@@ -40,4 +31,5 @@ class XtcReaderChapterSelectionActivity final : public Activity {
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(Activity::RenderLock&&) override;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user