From cc46ff7e258596b61a3b2369488f5377a66b2e46 Mon Sep 17 00:00:00 2001 From: Bryan Tan Date: Tue, 29 Dec 2020 13:53:50 -0800 Subject: [PATCH] Rewrite page loading cache algorithm * Refactor the page cache algorithm * Add a page preloading feature * Add settings for number of pages to preload * Add option to evict from page cache on page scroll (enabled by default) In practice, evict on page scroll reduces memory consumption much more than when having it off, so it has been enabled by default. --- src/control/settings/Settings.cpp | 42 ++++++++++++ src/control/settings/Settings.h | 23 +++++++ src/gui/XournalView.cpp | 64 +++++++++-------- src/gui/XournalView.h | 4 +- src/gui/dialog/SettingsDialog.cpp | 16 +++++ ui/settings.glade | 110 ++++++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 30 deletions(-) diff --git a/src/control/settings/Settings.cpp b/src/control/settings/Settings.cpp index 259cf5db..b4dd41f2 100644 --- a/src/control/settings/Settings.cpp +++ b/src/control/settings/Settings.cpp @@ -139,6 +139,9 @@ void Settings::loadDefault() { this->pageRerenderThreshold = 5.0; this->pdfPageCacheSize = 10; + this->preloadPagesBefore = 3U; + this->preloadPagesAfter = 5U; + this->eagerPageCleanup = true; this->selectionBorderColor = 0xff0000U; // red this->selectionMarkerColor = 0x729fcfU; // light blue @@ -398,6 +401,12 @@ void Settings::parseItem(xmlDocPtr doc, xmlNodePtr cur) { this->pageRerenderThreshold = g_ascii_strtod(reinterpret_cast(value), nullptr); } else if (xmlStrcmp(name, reinterpret_cast("pdfPageCacheSize")) == 0) { this->pdfPageCacheSize = g_ascii_strtoll(reinterpret_cast(value), nullptr, 10); + } else if (xmlStrcmp(name, reinterpret_cast("preloadPagesBefore")) == 0) { + this->preloadPagesBefore = g_ascii_strtoull(reinterpret_cast(value), nullptr, 10); + } else if (xmlStrcmp(name, reinterpret_cast("preloadPagesAfter")) == 0) { + this->preloadPagesAfter = g_ascii_strtoull(reinterpret_cast(value), nullptr, 10); + } else if (xmlStrcmp(name, reinterpret_cast("eagerPageCleanup")) == 0) { + this->eagerPageCleanup = xmlStrcmp(value, reinterpret_cast("true")) == 0; } else if (xmlStrcmp(name, reinterpret_cast("selectionBorderColor")) == 0) { this->selectionBorderColor = Color(g_ascii_strtoull(reinterpret_cast(value), nullptr, 10)); } else if (xmlStrcmp(name, reinterpret_cast("selectionMarkerColor")) == 0) { @@ -843,6 +852,9 @@ void Settings::save() { WRITE_INT_PROP(pdfPageCacheSize); WRITE_COMMENT("The count of rendered PDF pages which will be cached."); + WRITE_UINT_PROP(preloadPagesBefore); + WRITE_UINT_PROP(preloadPagesAfter); + WRITE_BOOL_PROP(eagerPageCleanup); WRITE_COMMENT("Config for new pages"); WRITE_STRING_PROP(pageTemplate); @@ -1624,6 +1636,36 @@ void Settings::setPdfPageCacheSize(int size) { save(); } +auto Settings::getPreloadPagesBefore() const -> unsigned int { return this->preloadPagesBefore; } + +void Settings::setPreloadPagesBefore(unsigned int n) { + if (this->preloadPagesBefore == n) { + return; + } + this->preloadPagesBefore = n; + save(); +} + +auto Settings::getPreloadPagesAfter() const -> unsigned int { return this->preloadPagesAfter; } + +void Settings::setPreloadPagesAfter(unsigned int n) { + if (this->preloadPagesAfter == n) { + return; + } + this->preloadPagesAfter = n; + save(); +} + +auto Settings::isEagerPageCleanup() const -> bool { return this->eagerPageCleanup; } + +void Settings::setEagerPageCleanup(bool b) { + if (this->eagerPageCleanup == b) { + return; + } + this->eagerPageCleanup = b; + save(); +} + auto Settings::getBorderColor() const -> Color { return this->selectionBorderColor; } void Settings::setBorderColor(Color color) { diff --git a/src/control/settings/Settings.h b/src/control/settings/Settings.h index af90659a..21f2ca51 100644 --- a/src/control/settings/Settings.h +++ b/src/control/settings/Settings.h @@ -330,6 +330,15 @@ public: int getPdfPageCacheSize() const; [[maybe_unused]] void setPdfPageCacheSize(int size); + unsigned int getPreloadPagesBefore() const; + void setPreloadPagesBefore(unsigned int n); + + unsigned int getPreloadPagesAfter() const; + void setPreloadPagesAfter(unsigned int n); + + bool isEagerPageCleanup() const; + void setEagerPageCleanup(bool b); + string const& getPageTemplate() const; void setPageTemplate(const string& pageTemplate); @@ -937,4 +946,18 @@ private: * e.g. "en_US" */ std::string preferredLocale; + /** + * The number of pages to pre-load before the current page. + */ + unsigned int preloadPagesBefore{}; + + /** + * The number of pages to pre-load after the current page. + */ + unsigned int preloadPagesAfter{}; + + /** + * Whether to evict from the page buffer cache when scrolling. + */ + bool eagerPageCleanup{}; }; diff --git a/src/gui/XournalView.cpp b/src/gui/XournalView.cpp index d584b738..543ba90d 100644 --- a/src/gui/XournalView.cpp +++ b/src/gui/XournalView.cpp @@ -1,5 +1,6 @@ #include "XournalView.h" +#include #include #include #include @@ -24,6 +25,14 @@ #include "XournalppCursor.h" #include "filesystem.h" +std::pair XournalView::preloadPageBounds(size_t page, size_t maxPage) { + const size_t preloadBefore = this->control->getSettings()->getPreloadPagesBefore(); + const size_t preloadAfter = this->control->getSettings()->getPreloadPagesAfter(); + const size_t lower = page > preloadBefore ? page - preloadBefore : 0; + const size_t upper = std::min(maxPage, page + preloadAfter); + return {lower, upper}; +} + XournalView::XournalView(GtkWidget* parent, Control* control, ScrollHandling* scrollHandling): scrollHandling(scrollHandling), control(control) { this->cache = new PdfCache(control->getSettings()->getPdfPageCacheSize()); @@ -87,40 +96,24 @@ void XournalView::staticLayoutPages(GtkWidget* widget, GtkAllocation* allocation xv->layoutPages(); } -auto XournalView::clearMemoryTimer(XournalView* widget) -> gboolean { - GList* list = nullptr; - - for (auto&& page: widget->viewPages) { - if (page->getLastVisibleTime() > 0) { - list = g_list_insert_sorted(list, page, reinterpret_cast(pageViewIncreasingClockTime)); - } - } - int pixel = 2884560; - int firstPages = 4; - - int i = 0; +auto XournalView::clearMemoryTimer(XournalView* widget) -> gboolean { + widget->cleanupBufferCache(); + return true; +} - for (GList* l = g_list_last(list); l != nullptr; l = l->prev) // older (higher time) to newer (lower time) - { - if (firstPages) { - firstPages--; - } else { - auto* v = static_cast(l->data); +auto XournalView::cleanupBufferCache() -> void { + const auto& [pagesLower, pagesUpper] = this->preloadPageBounds(this->currentPage, this->viewPages.size()); + g_assert(pagesLower <= pagesUpper); - if (pixel <= 0) { - v->deleteViewBuffer(); - } else { - pixel -= v->getBufferPixels(); - } + for (size_t i = 0; i < this->viewPages.size(); i++) { + auto&& page = this->viewPages[i]; + const size_t pageNum = i + 1; + const bool isPreload = pagesLower <= pageNum && pageNum <= pagesUpper; + if (!isPreload && page->getLastVisibleTime() > 0 && page->getBufferPixels() > 0) { + page->deleteViewBuffer(); } - i++; } - - g_list_free(list); - - // call again - return true; } auto XournalView::getCurrentPage() const -> size_t { return currentPage; } @@ -378,6 +371,19 @@ void XournalView::pageSelected(size_t page) { control->updatePageNumbers(currentPage, pdfPage); control->updateBackgroundSizeButton(); + + if (control->getSettings()->isEagerPageCleanup()) { + this->cleanupBufferCache(); + } + + // Load surrounding pages if they are not + const auto& [pagesLower, pagesUpper] = preloadPageBounds(page, this->viewPages.size()); + g_assert(pagesLower <= pagesUpper); + for (size_t i = pagesLower; i < pagesUpper; i++) { + if (this->viewPages[i]->getBufferPixels() == 0) { + this->viewPages[i]->rerenderPage(); + } + } } auto XournalView::getControl() -> Control* { return control; } diff --git a/src/gui/XournalView.h b/src/gui/XournalView.h index 899181cf..e1fd8b3e 100644 --- a/src/gui/XournalView.h +++ b/src/gui/XournalView.h @@ -140,12 +140,14 @@ public: private: void fireZoomChanged(); - void addLoadPageToQue(PageRef page, int priority); + std::pair preloadPageBounds(size_t page, size_t maxPage); Rectangle* getVisibleRect(size_t page); static gboolean clearMemoryTimer(XournalView* widget); + void cleanupBufferCache(); + static void staticLayoutPages(GtkWidget* widget, GtkAllocation* allocation, void* data); private: diff --git a/src/gui/dialog/SettingsDialog.cpp b/src/gui/dialog/SettingsDialog.cpp index 5fbf4e19..27741ead 100644 --- a/src/gui/dialog/SettingsDialog.cpp +++ b/src/gui/dialog/SettingsDialog.cpp @@ -487,6 +487,12 @@ void SettingsDialog::load() { loadCheckbox("cbHidePresentationSidebar", hidePresentationSidebar); loadCheckbox("cbHideMenubarStartup", settings->isMenubarVisible()); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(get("preloadPagesBefore")), + static_cast(settings->getPreloadPagesBefore())); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(get("preloadPagesAfter")), + static_cast(settings->getPreloadPagesAfter())); + loadCheckbox("cbEagerPageCleanup", settings->isEagerPageCleanup()); + enableWithCheckbox("cbAutosave", "boxAutosave"); enableWithCheckbox("cbIgnoreFirstStylusEvents", "spNumIgnoredStylusEvents"); enableWithCheckbox("cbAddVerticalSpace", "spAddVerticalSpace"); @@ -706,6 +712,16 @@ void SettingsDialog::save() { settings->setMenubarVisible(getCheckbox("cbHideMenubarStartup")); + constexpr auto spinAsUint = [&](GtkSpinButton* btn) { + int v = gtk_spin_button_get_value_as_int(btn); + return v < 0 ? 0U : static_cast(v); + }; + unsigned int preloadPagesBefore = spinAsUint(GTK_SPIN_BUTTON(get("preloadPagesBefore"))); + unsigned int preloadPagesAfter = spinAsUint(GTK_SPIN_BUTTON(get("preloadPagesAfter"))); + settings->setPreloadPagesAfter(preloadPagesAfter); + settings->setPreloadPagesBefore(preloadPagesBefore); + settings->setEagerPageCleanup(getCheckbox("cbEagerPageCleanup")); + settings->setDefaultSaveName(gtk_entry_get_text(GTK_ENTRY(get("txtDefaultSaveName")))); // Todo(fabian): use Util::fromGFilename! char* uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(get("fcAudioPath"))); diff --git a/ui/settings.glade b/ui/settings.glade index c4339ec1..3f69f832 100644 --- a/ui/settings.glade +++ b/ui/settings.glade @@ -48,6 +48,16 @@ 1 1 + + 99 + 1 + 10 + + + 99 + 1 + 10 + 0.5 4 @@ -3227,6 +3237,106 @@ This also enables touch drawing.</i> 5 + + + True + False + 0.009999999776482582 + + + True + False + 12 + + + + True + False + 10 + + + True + False + start + Pre-load pages before + + + 0 + 0 + + + + + True + False + start + Pre-load pages after + + + 0 + 1 + + + + + preloadPagesBefore + True + True + number + adjustmentPreloadPagesBefore + True + + + 1 + 0 + + + + + preloadPagesAfter + True + True + number + adjustmentPreloadPagesAfter + True + + + 1 + 1 + + + + + Clear cached paged while scrolling + True + True + False + True + + + 0 + 2 + 2 + + + + + + + + + True + False + Performance + + + + + False + True + 6 + +