diff --git a/callbacks.c b/callbacks.c index 2c63945..b4242d6 100644 --- a/callbacks.c +++ b/callbacks.c @@ -95,6 +95,8 @@ cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpoi if (gdk_rectangle_intersect(&view_rect, &page_rect, NULL) == TRUE) { zathura_page_set_visibility(page, true); + zathura_page_widget_update_view_time(ZATHURA_PAGE(page_widget)); + zathura_page_cache_add(zathura, zathura_page_get_index(page)); if (zathura->global.update_page_number == true && updated == false && gdk_rectangle_intersect(¢er, &page_rect, NULL) == TRUE) { zathura_document_set_current_page_number(zathura->document, page_id); @@ -103,7 +105,6 @@ cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpoi } else { zathura_page_set_visibility(page, false); } - zathura_page_widget_update_view_time(ZATHURA_PAGE(page_widget)); } statusbar_page_number_update(zathura); diff --git a/config.c b/config.c index 2dd64df..3f49034 100644 --- a/config.c +++ b/config.c @@ -155,9 +155,8 @@ config_load_default(zathura_t* zathura) girara_setting_add(gsession, "zoom-min", &int_value, INT, false, _("Zoom minimum"), NULL, NULL); int_value = 1000; girara_setting_add(gsession, "zoom-max", &int_value, INT, false, _("Zoom maximum"), NULL, NULL); - int_value = 20; - girara_setting_add(gsession, "page-store-threshold", &int_value, INT, false, _("Life time (in seconds) of a hidden page"), NULL, NULL); - girara_setting_add(gsession, "page-store-interval", &int_value, INT, true, _("Amount of seconds between each cache purge"), NULL, NULL); + int_value = ZATHURA_PAGE_CACHE_DEFAULT_SIZE; + girara_setting_add(gsession, "page-cache-size", &int_value, INT, true, _("Maximum number of pages to keep in the cache"), NULL, NULL); int_value = 20; girara_setting_add(gsession, "jumplist-size", &int_value, INT, false, _("Number of positions to remember in the jumplist"), cb_jumplist_change, NULL); diff --git a/page-widget.c b/page-widget.c index 8000ccb..e2b1542 100644 --- a/page-widget.c +++ b/page-widget.c @@ -860,19 +860,3 @@ zathura_page_widget_update_view_time(ZathuraPage* widget) priv->last_view = g_get_real_time(); } } - -void -zathura_page_widget_purge_unused(ZathuraPage* widget, gint64 threshold) -{ - g_return_if_fail(ZATHURA_IS_PAGE(widget) == TRUE); - zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); - if (zathura_page_get_visibility(priv->page) == true || priv->surface == NULL || threshold <= 0) { - return; - } - - const gint64 now = g_get_real_time(); - if (now - priv->last_view >= threshold * G_USEC_PER_SEC) { - girara_debug("purge page %d from cache (unseen for %f seconds)", zathura_page_get_index(priv->page), ((double)now - priv->last_view) / G_USEC_PER_SEC); - zathura_page_widget_update_surface(widget, NULL); - } -} diff --git a/page-widget.h b/page-widget.h index fe0b67a..5609e78 100644 --- a/page-widget.h +++ b/page-widget.h @@ -88,12 +88,4 @@ zathura_link_t* zathura_page_widget_link_get(ZathuraPage* widget, unsigned int i */ void zathura_page_widget_update_view_time(ZathuraPage* widget); -/** - * If the page has not been viewed for some time, purge the surface. - * - * @param widget the widget - * @param threshold the threshold (in seconds) - */ -void zathura_page_widget_purge_unused(ZathuraPage* widget, gint64 threshold); - #endif diff --git a/render.c b/render.c index 05b12eb..faf5f79 100644 --- a/render.c +++ b/render.c @@ -33,9 +33,9 @@ render_job(void* data, void* user_data) return; } - girara_debug("rendering page %d ...", zathura_page_get_index(page)); + girara_debug("rendering page %d ...", zathura_page_get_index(page) + 1); if (render(zathura, page) != true) { - girara_error("Rendering failed (page %d)\n", zathura_page_get_index(page)); + girara_error("Rendering failed (page %d)\n", zathura_page_get_index(page) + 1); } } diff --git a/zathura.c b/zathura.c index 8e8c4da..5a97755 100644 --- a/zathura.c +++ b/zathura.c @@ -53,7 +53,10 @@ typedef struct position_set_delayed_s { } position_set_delayed_t; static gboolean document_info_open(gpointer data); -static gboolean purge_pages(gpointer data); +static bool zathura_page_cache_is_cached(zathura_t* zathura, unsigned int page_index); +static ssize_t zathura_page_cache_lru_invalidate(zathura_t* zathura); +static void zathura_page_cache_invalidate_all(zathura_t* zathura); +static bool zathura_page_cache_is_full(zathura_t* zathura, bool* result); /* function implementation */ zathura_t* @@ -264,11 +267,6 @@ zathura_init(zathura_t* zathura) zathura->bookmarks.bookmarks = girara_sorted_list_new2((girara_compare_function_t) zathura_bookmarks_compare, (girara_free_function_t) zathura_bookmark_free); - /* add even to purge old pages */ - int interval = 30; - girara_setting_get(zathura->ui.session, "page-store-interval", &interval); - g_timeout_add_seconds(interval, purge_pages, zathura); - /* jumplist */ zathura->jumplist.max_size = 20; @@ -279,6 +277,14 @@ zathura_init(zathura_t* zathura) zathura->jumplist.cur = NULL; zathura_jumplist_append_jump(zathura); zathura->jumplist.cur = girara_list_iterator(zathura->jumplist.list); + + /* page cache */ + + zathura->page_cache.size = ZATHURA_PAGE_CACHE_DEFAULT_SIZE; + girara_setting_get(zathura->ui.session, "page-cache-size", &zathura->page_cache.size); + zathura->page_cache.cache = g_malloc(zathura->page_cache.size * sizeof(int)); + zathura_page_cache_invalidate_all(zathura); + return true; error_free: @@ -353,6 +359,8 @@ zathura_free(zathura_t* zathura) girara_list_iterator_free(zathura->jumplist.cur); } + g_free(zathura->page_cache.cache); + g_free(zathura); } @@ -775,6 +783,9 @@ document_open(zathura_t* zathura, const char* path, const char* password, cb_view_vadjustment_value_changed(NULL, zathura); } + /* Invalidate all current entries in the page cache */ + zathura_page_cache_invalidate_all(zathura); + return true; error_free: @@ -1066,30 +1077,6 @@ page_widget_set_mode(zathura_t* zathura, unsigned int pages_per_row, unsigned in gtk_widget_show_all(zathura->ui.page_widget); } -static -gboolean purge_pages(gpointer data) -{ - zathura_t* zathura = data; - if (zathura == NULL || zathura->document == NULL) { - return TRUE; - } - - int threshold = 0; - girara_setting_get(zathura->ui.session, "page-store-threshold", &threshold); - if (threshold <= 0) { - return TRUE; - } - - girara_debug("purging pages ..."); - unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document); - for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) { - zathura_page_t* page = zathura_document_get_page(zathura->document, page_id); - GtkWidget* page_widget = zathura_page_get_widget(zathura, page); - zathura_page_widget_purge_unused(ZATHURA_PAGE(page_widget), threshold); - } - return TRUE; -} - static gboolean position_set_delayed_impl(gpointer data) { @@ -1215,3 +1202,117 @@ zathura_jumplist_save(zathura_t* zathura) cur->y = gtk_adjustment_get_value(view_vadjustment) / zathura_document_get_scale(zathura->document);; } } + +static bool +zathura_page_cache_is_cached(zathura_t* zathura, unsigned int page_index) +{ + g_return_val_if_fail(zathura != NULL, false); + + unsigned int i; + + if (zathura->page_cache.num_cached_pages != 0) { + for (i = 0; i < zathura->page_cache.size; ++i) { + if (zathura->page_cache.cache[i] >= 0 && page_index == (unsigned int)zathura->page_cache.cache[i]) { + girara_debug("Page %d is a cache hit", page_index + 1); + return true; + } + } + } + + girara_debug("Page %d is a cache miss", page_index + 1); + return false; +} + +static ssize_t +zathura_page_cache_lru_invalidate(zathura_t* zathura) +{ + g_return_val_if_fail(zathura != NULL, -1); + + ssize_t lru_index = 0; + guint64 view_time = 0; + guint64 lru_view_time = G_MAXUINT64; + GtkWidget* page_widget; + + for (unsigned int i = 0; i < zathura->page_cache.size; ++i) { + page_widget = zathura_page_get_widget(zathura, zathura_document_get_page(zathura->document, zathura->page_cache.cache[i])); + g_return_val_if_fail(page_widget != NULL, -1); + g_object_get(G_OBJECT(page_widget), "last-view", &view_time, NULL); + + if (view_time < lru_view_time) { + lru_view_time = view_time; + lru_index = i; + } + } + + zathura_page_t* page = zathura_document_get_page(zathura->document, zathura->page_cache.cache[lru_index]); + g_return_val_if_fail(page != NULL, -1); + + page_widget = zathura_page_get_widget(zathura, page); + g_return_val_if_fail(page_widget != NULL, -1); + + zathura_page_widget_update_surface(ZATHURA_PAGE(page_widget), NULL); + girara_debug("Invalidated page %d at cache index %ld", zathura->page_cache.cache[lru_index] + 1, lru_index); + zathura->page_cache.cache[lru_index] = -1; + --zathura->page_cache.num_cached_pages; + + return lru_index; +} + +static bool +zathura_page_cache_is_full(zathura_t* zathura, bool* result) +{ + g_return_val_if_fail(zathura != NULL, false); + + *result = zathura->page_cache.num_cached_pages == zathura->page_cache.size; + + return true; +} + +void +zathura_page_cache_invalidate_all(zathura_t* zathura) +{ + g_return_if_fail(zathura != NULL); + + unsigned int i; + + for (i = 0; i < zathura->page_cache.size; ++i) { + zathura->page_cache.cache[i] = -1; + } + + zathura->page_cache.num_cached_pages = 0; +} + +void +zathura_page_cache_add(zathura_t* zathura, unsigned int page_index) +{ + g_return_if_fail(zathura != NULL); + + zathura_page_t* page = zathura_document_get_page(zathura->document, page_index); + + g_return_if_fail(page != NULL); + + if (zathura_page_cache_is_cached(zathura, page_index)) { + return; + } + + bool full; + + if (zathura_page_cache_is_full(zathura, &full) == false) { + return; + } else if (full == true) { + ssize_t idx = zathura_page_cache_lru_invalidate(zathura); + + if (idx == -1) { + return; + } + + zathura->page_cache.cache[idx] = page_index; + ++zathura->page_cache.num_cached_pages; + girara_debug("Page %d is cached at cache index %ld", page_index + 1, idx); + return; + } + + zathura->page_cache.cache[zathura->page_cache.num_cached_pages++] = page_index; + girara_debug("Page %d is cached at cache index %d", page_index + 1, zathura->page_cache.num_cached_pages - 1); + return; +} diff --git a/zathura.h b/zathura.h index 1520d78..e75a7e2 100644 --- a/zathura.h +++ b/zathura.h @@ -13,6 +13,8 @@ #include #endif +#define ZATHURA_PAGE_CACHE_DEFAULT_SIZE 15 + enum { NEXT, PREVIOUS, LEFT, RIGHT, UP, DOWN, BOTTOM, TOP, HIDE, HIGHLIGHT, DELETE_LAST_WORD, DELETE_LAST_CHAR, DEFAULT, ERROR, WARNING, NEXT_GROUP, PREVIOUS_GROUP, ZOOM_IN, ZOOM_OUT, ZOOM_ORIGINAL, ZOOM_SPECIFIC, FORWARD, @@ -152,6 +154,15 @@ struct zathura_s gchar* file_path; /**< Save file path */ gchar* password; /**< Save password */ } file_monitor; + + /** + * The page cache + */ + struct { + int* cache; + unsigned int size; + unsigned int num_cached_pages; + } page_cache; }; /** @@ -366,5 +377,12 @@ void zathura_jumplist_add(zathura_t* zathura); */ void zathura_jumplist_append_jump(zathura_t* zathura); +/** + * Add a page to the page cache + * + * @param zathura The zathura session + * @param page_index The index of the page to be cached + */ +void zathura_page_cache_add(zathura_t* zathura, unsigned int page_index); #endif // ZATHURA_H diff --git a/zathurarc.5.rst b/zathurarc.5.rst index f317aa6..09854b6 100644 --- a/zathurarc.5.rst +++ b/zathurarc.5.rst @@ -530,20 +530,16 @@ The page padding defines the gap in pixels between each rendered page. * Value type: Integer * Default value: 1 -page-store-threshold -^^^^^^^^^^^^^^^^^^^^ -Pages that are not visible get unloaded after some time. Every page that has not -been visible for page-store-treshold seconds will be unloaded. +page-cache-size +^^^^^^^^^^^^^^^ +Defines the maximum number of pages that could be kept in the page cache. When +the cache is full and a new page that isn't cached becomes visible, the least +recently viewed page in the cache will be evicted to make room for the new one. +Large values for this variable are NOT recommended, because this will lead to +consuming a significant portion of the system memory. * Value type: Integer -* Default value: 30 - -page-store-interval -^^^^^^^^^^^^^^^^^^^ -Defines the amount of seconds between the check to unload invisible pages. - -* Value type: Integer -* Default value: 30 +* Default value: 15 pages-per-row ^^^^^^^^^^^^^