fix: address review findings on prewarm gating and gitignore

Revalidate the section under RenderLock before the idle prewarm scan,
add a largest-block floor to its heap gate, rebuild the differential
baseline when the grayscale scratch allocation fails after an async
refresh, and anchor generated-file gitignore patterns to the repo root.
This commit is contained in:
Justin Mitchell
2026-07-14 16:29:33 -04:00
parent f7f1a6f8bf
commit a3b8897977
2 changed files with 29 additions and 17 deletions
+4 -4
View File
@@ -27,7 +27,7 @@ lib/EpdFont/scripts/output/
!.claude/skills/ !.claude/skills/
/managed_components /managed_components
/.dummy /.dummy
CMakeLists.txt /CMakeLists.txt
dependencies.lock /dependencies.lock
sdkconfig.default /sdkconfig.default
sdkconfig.defaults /sdkconfig.defaults
+25 -13
View File
@@ -285,20 +285,25 @@ void EpubReaderActivity::loop() {
constexpr unsigned long IDLE_PREWARM_DEBOUNCE_MS = 400; constexpr unsigned long IDLE_PREWARM_DEBOUNCE_MS = 400;
if (section && !section->isBuilding() && !RenderLock::peek() && renderer.hasFrameBuffer() && if (section && !section->isBuilding() && !RenderLock::peek() && renderer.hasFrameBuffer() &&
lastRenderCompleteMs != 0 && millis() - lastRenderCompleteMs > IDLE_PREWARM_DEBOUNCE_MS && lastRenderCompleteMs != 0 && millis() - lastRenderCompleteMs > IDLE_PREWARM_DEBOUNCE_MS &&
ESP.getFreeHeap() > RENDER_MIN_FREE_HEAP && ESP.getFreeHeap() > RENDER_MIN_FREE_HEAP && ESP.getMaxAllocHeap() > BACKGROUND_BUILD_MIN_MAX_ALLOC &&
(idlePrewarmSpine != currentSpineIndex || idlePrewarmPage != section->currentPage)) { (idlePrewarmSpine != currentSpineIndex || idlePrewarmPage != section->currentPage)) {
idlePrewarmSpine = currentSpineIndex; RenderLock lock; // the page table must not change under the scan
idlePrewarmPage = section->currentPage; // Re-check under the lock: peek() and acquisition are not atomic, so the render
const int nextPage = section->currentPage + 1; // task may have reset/replaced the section or moved the page in between.
if (nextPage < static_cast<int>(section->pageCount)) { if (section && !section->isBuilding() &&
RenderLock lock; // the page table must not change under the scan (idlePrewarmSpine != currentSpineIndex || idlePrewarmPage != section->currentPage)) {
if (const auto p = section->loadPage(nextPage)) { idlePrewarmSpine = currentSpineIndex;
if (auto* fcm = renderer.getFontCacheManager()) { idlePrewarmPage = section->currentPage;
const auto t0 = millis(); const int nextPage = section->currentPage + 1;
auto scope = fcm->createPrewarmScope(); if (nextPage < static_cast<int>(section->pageCount)) {
p->render(renderer, SETTINGS.getReaderFontId(), 0, 0); // scan only, no pixels if (const auto p = section->loadPage(nextPage)) {
scope.endScanAndPrewarm(); if (auto* fcm = renderer.getFontCacheManager()) {
LOG_DBG("ERS", "Idle prewarm: page %d in %lums", nextPage, millis() - t0); const auto t0 = millis();
auto scope = fcm->createPrewarmScope();
p->render(renderer, SETTINGS.getReaderFontId(), 0, 0); // scan only, no pixels
scope.endScanAndPrewarm();
LOG_DBG("ERS", "Idle prewarm: page %d in %lums", nextPage, millis() - t0);
}
} }
} }
} }
@@ -1526,6 +1531,13 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
renderer.waitRefreshComplete(); renderer.waitRefreshComplete();
if (!scratch) { if (!scratch) {
LOG_ERR("ERS", "OOM: grayscale strip scratch (%d bytes); skipping AA this page", gwBytes * STRIP_ROWS); LOG_ERR("ERS", "OOM: grayscale strip scratch (%d bytes); skipping AA this page", gwBytes * STRIP_ROWS);
if (overlapRefresh) {
// The BW refresh ran the shadow-free async path, so controller RAM's
// differential baseline was never rebuilt. Even with AA skipped it must
// be re-synced from the intact BW framebuffer, or the next differential
// update diffs against stale contents.
renderer.cleanupGrayscaleWithFrameBuffer();
}
} else { } else {
// Bands may be streamed in any order: X4 windows each via setRamArea, // Bands may be streamed in any order: X4 windows each via setRamArea,
// X3 via PTL. // X3 via PTL.