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
+14 -2
View File
@@ -285,13 +285,17 @@ 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)) {
RenderLock lock; // the page table must not change under the scan
// Re-check under the lock: peek() and acquisition are not atomic, so the render
// task may have reset/replaced the section or moved the page in between.
if (section && !section->isBuilding() &&
(idlePrewarmSpine != currentSpineIndex || idlePrewarmPage != section->currentPage)) { (idlePrewarmSpine != currentSpineIndex || idlePrewarmPage != section->currentPage)) {
idlePrewarmSpine = currentSpineIndex; idlePrewarmSpine = currentSpineIndex;
idlePrewarmPage = section->currentPage; idlePrewarmPage = section->currentPage;
const int nextPage = section->currentPage + 1; const int nextPage = section->currentPage + 1;
if (nextPage < static_cast<int>(section->pageCount)) { if (nextPage < static_cast<int>(section->pageCount)) {
RenderLock lock; // the page table must not change under the scan
if (const auto p = section->loadPage(nextPage)) { if (const auto p = section->loadPage(nextPage)) {
if (auto* fcm = renderer.getFontCacheManager()) { if (auto* fcm = renderer.getFontCacheManager()) {
const auto t0 = millis(); const auto t0 = millis();
@@ -303,6 +307,7 @@ void EpubReaderActivity::loop() {
} }
} }
} }
}
// Lazily resume a partial's extension build once the reader nears its watermark. Far from // Lazily resume a partial's extension build once the reader nears its watermark. Far from
// it the rebuild is all cost (whole-chapter re-layout from page 0) and no benefit this // it the rebuild is all cost (whole-chapter re-layout from page 0) and no benefit this
@@ -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.