* Question about (excessive?) ram usage when many overlays (with large vscroll) @ 2022-04-26 12:21 dalanicolai 2022-04-26 12:33 ` Eli Zaretskii 2022-04-26 12:49 ` Po Lu 0 siblings, 2 replies; 13+ messages in thread From: dalanicolai @ 2022-04-26 12:21 UTC (permalink / raw) To: Emacs Devel [-- Attachment #1.1: Type: text/plain, Size: 2199 bytes --] I have a question about ram usage when using overlays. So I have created `image-roll.el` for displaying documents/books (see here <https://lists.gnu.org/archive/html/emacs-devel/2022-04/msg00975.html>). However, I have just noticed that it uses a large amount of RAM when viewing (or trying to) pages in the back of 'large' books. But even if RAM usage still looks perfectly fine, Emacs crashes when trying to scroll to higher page numbers. I have looked a little into it, and have found that it is a consequence of using large overlays. There is no problem when creating a buffer containing many overlays, however, when trying to scroll to some overlay at the end of the buffer, Emacs will use huge amounts of RAM. So I am wondering if this is 'necessary' behavior; and then why is it necessary? The issue occurs even when displaying the same 'empty' svg-image on each overlay, but it occurs also when simply displaying some 'specified space' on each overlay. I have created a simple test file (attached to this mail) for reproducing the issue.From 'emacs -Q` you can simply load the file and do `M-x test`. Then you can type some number and press `C-c C-c` to jump to the page/overlay(number) (use arrow up/down to scroll). The overlay width is set to 1200 pixels (via a global variable, so that it can easily be changed) and the width is set to 1.4 times that size (you can also comment out the 'specified space' overlay, and uncomment the `insert-image` form). Over here, Emacs crashes when trying to jump to a page number of somewhere around 1750. When increasing the overlay size, e.g. setting the width to (window-pixel-width), then RAM usage increases much faster, and Emacs starts to crash from jumping to page around 800. So my question is, if it is really necessary that Emacs uses so much RAM for just displaying 'empty spaces'. And another question is why the maximum `vscroll` is (or seems to be) limited. I have designed the book-roll like this, so that the scroll bar might be used for indicating/scrolling to the/some position in a document. However, otherwise I probably have to design it so that the buffer only contains two overlays and 'simulate' the image-roll behavior. [-- Attachment #1.2: Type: text/html, Size: 2417 bytes --] [-- Attachment #2: test.el --] [-- Type: text/x-emacs-lisp, Size: 2276 bytes --] (require 'svg) (setq test-page-width 1200) (defun test-goto-page (page) (interactive (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg) (read-number "Page: ")))) (set-window-vscroll nil (print (* (print page) (+ (* 1.4 test-page-width) 10))) t)) (defun test-scroll-forward () (interactive) (set-window-vscroll nil (+ (window-vscroll nil t) 50) t)) (defun test-scroll-backward () (interactive) (set-window-vscroll nil (- (window-vscroll nil t) 50) t)) (define-derived-mode test-mode special-mode "Test") (define-key test-mode-map (kbd "<down>") 'test-scroll-forward) (define-key test-mode-map (kbd "<up>") 'test-scroll-backward) (define-key test-mode-map (kbd "C-c C-c") 'test-goto-page) (when (featurep 'evil) (evil-define-key 'motion test-mode-map "j" 'test-scroll-forward) (evil-define-key 'motion test-mode-map "k" 'test-scroll-backward) (evil-define-key 'motion test-mode-map "G" 'test-goto-page)) (defun test () (interactive) (with-current-buffer (get-buffer-create "*test*") ;; (let* ((w (window-pixel-width)) (let* ((w test-page-width) (h (* 1.4 w)) (svg (svg-create w h))) ;; uncomment for `colored page' ;; (svg-rectangle svg 0 0 w h :fill "red") (let ((im (svg-image svg))) (dotimes (i 1600) ;; NOTE either insert `specified space' overlays (let ((start (point))) (insert " ") (let ((o (make-overlay start (point)))) (overlay-put o 'display `(space . (:width (,w) :height (,h)))))) ;; NOTE or insert an `empty' svg image ;; (insert-image im " ") (insert "\n") ;; for `visual clarity', insert a page separator overlay (let ((start (point))) (insert " ") (let ((o (make-overlay start (point)))) (overlay-put o 'display `(space . (:width (,w) :height (,10)))) (overlay-put o 'face `(:background "gray")) (insert "\n"))))) (test-mode) (setq cursor-type nil) (switch-to-buffer (current-buffer)) (goto-char (point-min))))) ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-26 12:21 Question about (excessive?) ram usage when many overlays (with large vscroll) dalanicolai @ 2022-04-26 12:33 ` Eli Zaretskii 2022-04-26 13:24 ` Eli Zaretskii 2022-04-26 12:49 ` Po Lu 1 sibling, 1 reply; 13+ messages in thread From: Eli Zaretskii @ 2022-04-26 12:33 UTC (permalink / raw) To: dalanicolai; +Cc: emacs-devel > From: dalanicolai <dalanicolai@gmail.com> > Date: Tue, 26 Apr 2022 14:21:07 +0200 > > I have a question about ram usage when using overlays. > > So I have created `image-roll.el` for displaying documents/books (see here > <https://lists.gnu.org/archive/html/emacs-devel/2022-04/msg00975.html>). > However, I have just noticed that it uses a large amount of RAM when > viewing (or > trying to) pages in the back of 'large' books. But even if RAM usage still > looks > perfectly fine, Emacs crashes when trying to scroll to higher page numbers. > > I have looked a little into it, and have found that it is a consequence of > using > large overlays. There is no problem when creating a buffer containing many > overlays, however, when trying to scroll to some overlay at the end of the > buffer, > Emacs will use huge amounts of RAM. > > So I am wondering if this is 'necessary' behavior; and then why is it > necessary? > The issue occurs even when displaying the same 'empty' svg-image on each > overlay, but it occurs also when simply displaying some 'specified space' on > each overlay. I didn't yet try to reproduce this, but could you perhaps run this test under GDB, and when Emacs crashes, show the C-level backtrace? This could give good hints about the possible reason(s). Thanks. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-26 12:33 ` Eli Zaretskii @ 2022-04-26 13:24 ` Eli Zaretskii 2022-04-27 14:13 ` dalanicolai 0 siblings, 1 reply; 13+ messages in thread From: Eli Zaretskii @ 2022-04-26 13:24 UTC (permalink / raw) To: dalanicolai; +Cc: emacs-devel > Date: Tue, 26 Apr 2022 15:33:08 +0300 > From: Eli Zaretskii <eliz@gnu.org> > Cc: emacs-devel@gnu.org > > > From: dalanicolai <dalanicolai@gmail.com> > > Date: Tue, 26 Apr 2022 14:21:07 +0200 > > > > I have a question about ram usage when using overlays. > > > > So I have created `image-roll.el` for displaying documents/books (see here > > <https://lists.gnu.org/archive/html/emacs-devel/2022-04/msg00975.html>). > > However, I have just noticed that it uses a large amount of RAM when > > viewing (or > > trying to) pages in the back of 'large' books. But even if RAM usage still > > looks > > perfectly fine, Emacs crashes when trying to scroll to higher page numbers. > > > > I have looked a little into it, and have found that it is a consequence of > > using > > large overlays. There is no problem when creating a buffer containing many > > overlays, however, when trying to scroll to some overlay at the end of the > > buffer, > > Emacs will use huge amounts of RAM. > > > > So I am wondering if this is 'necessary' behavior; and then why is it > > necessary? > > The issue occurs even when displaying the same 'empty' svg-image on each > > overlay, but it occurs also when simply displaying some 'specified space' on > > each overlay. > > I didn't yet try to reproduce this, but could you perhaps run this > test under GDB, and when Emacs crashes, show the C-level backtrace? > This could give good hints about the possible reason(s). What I see here is that the memory footprint indeed goes up quite quickly, but then (not sure exactly what triggers that in my case), it gets reset back to almost the original small value. If this doesn't happen for you, then I guess your code somehow triggers bad behavior of glibc's malloc, forcing it not to release memory back to the OS, due to the particular pattern of allocations and deallocations? Or maybe something else is at work here and I just got lucky? Memory is allocated for dealing with overlays, I think, because redisplay needs to have the overlays centered around the position the text it is rendering, and moving around many hundreds of overlays needs memory for the move. But that's a guess. If someone can find out why we allocate large amounts of memory in this scenario, that could perhaps help us understand better what's going on here. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-26 13:24 ` Eli Zaretskii @ 2022-04-27 14:13 ` dalanicolai 2022-04-27 15:44 ` Eli Zaretskii 2022-04-27 17:13 ` Stefan Monnier 0 siblings, 2 replies; 13+ messages in thread From: dalanicolai @ 2022-04-27 14:13 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Emacs Devel [-- Attachment #1: Type: text/plain, Size: 3185 bytes --] Thanks again Eli for looking into this quickly. Over here, the memory does not even get released after killing the buffer, while the overlays are 'bound' in a buffer local list variable. But anyway, I should design/use things like Po suggested, in which case there are no issues. If you are still interested in the GDB c-level backtrace, then of course I would be happy to create it (or does your second mail mean you already did this?), but then I would need to find some time to read about how those things work first. Anyway, if so, then let me know (I guess dealing with 'very large' vscroll isn't really a priority, as this probably only occurs in 'designing' image-rolls). On Tue, 26 Apr 2022 at 15:25, Eli Zaretskii <eliz@gnu.org> wrote: > > Date: Tue, 26 Apr 2022 15:33:08 +0300 > > From: Eli Zaretskii <eliz@gnu.org> > > Cc: emacs-devel@gnu.org > > > > > From: dalanicolai <dalanicolai@gmail.com> > > > Date: Tue, 26 Apr 2022 14:21:07 +0200 > > > > > > I have a question about ram usage when using overlays. > > > > > > So I have created `image-roll.el` for displaying documents/books (see > here > > > <https://lists.gnu.org/archive/html/emacs-devel/2022-04/msg00975.html > >). > > > However, I have just noticed that it uses a large amount of RAM when > > > viewing (or > > > trying to) pages in the back of 'large' books. But even if RAM usage > still > > > looks > > > perfectly fine, Emacs crashes when trying to scroll to higher page > numbers. > > > > > > I have looked a little into it, and have found that it is a > consequence of > > > using > > > large overlays. There is no problem when creating a buffer containing > many > > > overlays, however, when trying to scroll to some overlay at the end of > the > > > buffer, > > > Emacs will use huge amounts of RAM. > > > > > > So I am wondering if this is 'necessary' behavior; and then why is it > > > necessary? > > > The issue occurs even when displaying the same 'empty' svg-image on > each > > > overlay, but it occurs also when simply displaying some 'specified > space' on > > > each overlay. > > > > I didn't yet try to reproduce this, but could you perhaps run this > > test under GDB, and when Emacs crashes, show the C-level backtrace? > > This could give good hints about the possible reason(s). > > What I see here is that the memory footprint indeed goes up quite > quickly, but then (not sure exactly what triggers that in my case), it > gets reset back to almost the original small value. > > If this doesn't happen for you, then I guess your code somehow > triggers bad behavior of glibc's malloc, forcing it not to release > memory back to the OS, due to the particular pattern of allocations > and deallocations? Or maybe something else is at work here and I just > got lucky? > > Memory is allocated for dealing with overlays, I think, because > redisplay needs to have the overlays centered around the position the > text it is rendering, and moving around many hundreds of overlays > needs memory for the move. > > But that's a guess. If someone can find out why we allocate large > amounts of memory in this scenario, that could perhaps help us > understand better what's going on here. > [-- Attachment #2: Type: text/html, Size: 4283 bytes --] ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-27 14:13 ` dalanicolai @ 2022-04-27 15:44 ` Eli Zaretskii 2022-04-27 17:13 ` Stefan Monnier 1 sibling, 0 replies; 13+ messages in thread From: Eli Zaretskii @ 2022-04-27 15:44 UTC (permalink / raw) To: dalanicolai; +Cc: emacs-devel > From: dalanicolai <dalanicolai@gmail.com> > Date: Wed, 27 Apr 2022 16:13:58 +0200 > Cc: Emacs Devel <emacs-devel@gnu.org> > > If you are still interested in the GDB c-level backtrace, then of course I would be > happy to create it (or does your second mail mean you already did this?), No, I didn't get any backtraces, because it doesn't crash here. > but then I would need to find some time to read about how those > things work first. Anyway, if so, then let me know (I guess dealing with 'very large' > vscroll isn't really a priority, as this probably only occurs in 'designing' image-rolls). No need to invest time in problems that were solved, thanks. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-27 14:13 ` dalanicolai 2022-04-27 15:44 ` Eli Zaretskii @ 2022-04-27 17:13 ` Stefan Monnier 2022-04-27 17:18 ` Eli Zaretskii 1 sibling, 1 reply; 13+ messages in thread From: Stefan Monnier @ 2022-04-27 17:13 UTC (permalink / raw) To: dalanicolai; +Cc: Eli Zaretskii, Emacs Devel > Thanks again Eli for looking into this quickly. Over here, the memory > does not even get released after killing the buffer, while the > overlays are 'bound' in a buffer local list variable. IIUC current glibc basically never returns malloc'd memory to the OS. Stefan ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-27 17:13 ` Stefan Monnier @ 2022-04-27 17:18 ` Eli Zaretskii 0 siblings, 0 replies; 13+ messages in thread From: Eli Zaretskii @ 2022-04-27 17:18 UTC (permalink / raw) To: Stefan Monnier; +Cc: dalanicolai, emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Cc: Eli Zaretskii <eliz@gnu.org>, Emacs Devel <emacs-devel@gnu.org> > Date: Wed, 27 Apr 2022 13:13:04 -0400 > > > Thanks again Eli for looking into this quickly. Over here, the memory > > does not even get released after killing the buffer, while the > > overlays are 'bound' in a buffer local list variable. > > IIUC current glibc basically never returns malloc'd memory to the OS. That is NOT true, AFAIU. It can do that in some cases, but it doesn't _always_ do that. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-26 12:21 Question about (excessive?) ram usage when many overlays (with large vscroll) dalanicolai 2022-04-26 12:33 ` Eli Zaretskii @ 2022-04-26 12:49 ` Po Lu 2022-04-27 14:01 ` dalanicolai 2022-04-28 11:56 ` dalanicolai 1 sibling, 2 replies; 13+ messages in thread From: Po Lu @ 2022-04-26 12:49 UTC (permalink / raw) To: dalanicolai; +Cc: Emacs Devel dalanicolai <dalanicolai@gmail.com> writes: > So I have created `image-roll.el` for displaying documents/books (see > here). However, I have just noticed that it uses a large amount of > RAM when viewing (or trying to) pages in the back of 'large' > books. But even if RAM usage still looks perfectly fine, Emacs crashes > when trying to scroll to higher page numbers. I didn't try to reproduce this problem, but note that it's slow to vscroll large amounts of text. Instead, find the start of the first line that will be visible onscreen (using window-text-pixel-size or posn-at-point), make that the window start, and set vscroll starting from there instead. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-26 12:49 ` Po Lu @ 2022-04-27 14:01 ` dalanicolai 2022-04-28 11:56 ` dalanicolai 1 sibling, 0 replies; 13+ messages in thread From: dalanicolai @ 2022-04-27 14:01 UTC (permalink / raw) To: Po Lu; +Cc: Emacs Devel [-- Attachment #1: Type: text/plain, Size: 1174 bytes --] Thanks Po! Indeed, I was not aware of that (I couldn't find anywhere what vscroll means 'exactly', i.e. I did not know it was 'relative w.r.t. the 'current point'). Anyway, indeed doing what you suggested works great (i.e. fast scrolling and no issue with the large memory). Too bad I have to rewrite substantial parts of image-roll.el again, but after that I think it will finally provide a quite nice 'image-roll'. On Tue, 26 Apr 2022 at 14:49, Po Lu <luangruo@yahoo.com> wrote: > dalanicolai <dalanicolai@gmail.com> writes: > > > So I have created `image-roll.el` for displaying documents/books (see > > here). However, I have just noticed that it uses a large amount of > > RAM when viewing (or trying to) pages in the back of 'large' > > books. But even if RAM usage still looks perfectly fine, Emacs crashes > > when trying to scroll to higher page numbers. > > I didn't try to reproduce this problem, but note that it's slow to > vscroll large amounts of text. Instead, find the start of the first > line that will be visible onscreen (using window-text-pixel-size or > posn-at-point), make that the window start, and set vscroll starting > from there instead. > [-- Attachment #2: Type: text/html, Size: 1676 bytes --] ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-26 12:49 ` Po Lu 2022-04-27 14:01 ` dalanicolai @ 2022-04-28 11:56 ` dalanicolai 2022-04-28 12:06 ` Po Lu 1 sibling, 1 reply; 13+ messages in thread From: dalanicolai @ 2022-04-28 11:56 UTC (permalink / raw) To: Po Lu; +Cc: Emacs Devel [-- Attachment #1: Type: text/plain, Size: 1360 bytes --] I have a question about this, namely: how to make a line the 'window start'? Using 'set-window-start` does not work. From 'emacs -Q' (which starts within the scratch buffer), immediately evaluate (set-window-start nil (point)) to set the 'new' window start. Subsequently do (set-window-vscroll nil 1) it will scroll from the start of the buffer, and not from the 'new' window start as I would expect (of course, here there are no lines after the 'new' window start, but you could insert 1 to 3 on separate lines then set the line with 1 to window start, but this does not really change anything). On Tue, 26 Apr 2022 at 14:49, Po Lu <luangruo@yahoo.com> wrote: > dalanicolai <dalanicolai@gmail.com> writes: > > > So I have created `image-roll.el` for displaying documents/books (see > > here). However, I have just noticed that it uses a large amount of > > RAM when viewing (or trying to) pages in the back of 'large' > > books. But even if RAM usage still looks perfectly fine, Emacs crashes > > when trying to scroll to higher page numbers. > > I didn't try to reproduce this problem, but note that it's slow to > vscroll large amounts of text. Instead, find the start of the first > line that will be visible onscreen (using window-text-pixel-size or > posn-at-point), make that the window start, and set vscroll starting > from there instead. > [-- Attachment #2: Type: text/html, Size: 2037 bytes --] ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-28 11:56 ` dalanicolai @ 2022-04-28 12:06 ` Po Lu 2022-04-28 16:28 ` dalanicolai 0 siblings, 1 reply; 13+ messages in thread From: Po Lu @ 2022-04-28 12:06 UTC (permalink / raw) To: dalanicolai; +Cc: Emacs Devel dalanicolai <dalanicolai@gmail.com> writes: > I have a question about this, namely: how to make a line the 'window > start'? Using 'set-window-start` does not work. > > From 'emacs -Q' (which starts within the scratch buffer), immediately > evaluate > > (set-window-start nil (point)) > > to set the 'new' window start. Subsequently do > > (set-window-vscroll nil 1) > > it will scroll from the start of the buffer, and not from the 'new' > window start as I would expect That's because the point is obscured after the vscroll is applied, so the display is recentered. You have to move point to some location that is at least partially visible after the vscroll if you set `make-cursor-line-fully-visible' to t, or a location that is fully visible otherwise. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-28 12:06 ` Po Lu @ 2022-04-28 16:28 ` dalanicolai 2022-04-28 16:48 ` dalanicolai 0 siblings, 1 reply; 13+ messages in thread From: dalanicolai @ 2022-04-28 16:28 UTC (permalink / raw) To: Po Lu; +Cc: Emacs Devel [-- Attachment #1.1: Type: text/plain, Size: 3879 bytes --] Thank you Po, for your explanations. Indeed this clears things up a little, but unfortunately my question was not 'precise enough', for the answer to 'solve' the issue that motivated this question. (I wanted to keep my `image-mode.el` out of the question, but I guess it is necessary to add it to explain the issue I am experiencing). So I have rewritten `image-mode.el` to scroll by `line + vscroll` instead of using vscroll only. This solves the issue with the RAM usage for 'large documents'. Additionally, it made the code a little 'simpler', which is a welcome 'side effect' (I got rid of the 'gap' (page separation) overlays, and instead added margins to the images. So now the buffer contents is just single spaces on separate lines, where the number of lines is equal to the number of `images/pages`. The 'gap' color can be configured by adding the overlay's face background). It seems to work mostly correct (the 'scrolling' look a tiny bit less smooth than with the pure vscroll version, but I would say it is very acceptable). Anyway, I hope someone can quickly have a look at the issue explained below (this should take less than a minute). So let me quickly provide some context for the issue. Currently, each overlay is overlaid on a single space on separate lines. So the contents of the buffer is a single space (holding a page overlay) on every line. So to jump to the next page, I can simply use 'forward-line'. Now this works fine when the page is 'off-window` but when the next page is already within view ('on-window'), then going to the next line does not position that line at the top of the window, nor makes it the current `window-start. To get it at the top of the window, I could subsequently call `(set-window-start nil (point))`, but then subsequently, `set-window-vscroll` does not work. To make `set-window-vscroll` have effect, I can add a `redisplay` just before I call it. This works, but the 'jump' gets very/unacceptably ugly (you see the 'recentering' steps taking place). However, when the next page is 'off-window' than using the 'forward-line' works directly, and the page jump looks smooth. So to reproduce it from `emacs -Q` you can just load the `image-roll.el` file that I will attach here, then do `M-x image-roll-demo` and scroll down using the arrow keys until you see the page transition between page one and two. Now press the `page down` (PgDn) button to jump to the next page. Because page two is already 'on-window' this only moves the cursor one page down. If you now press a second time `PgDn` then, because page 3 is still `off-window`, the jump takes place how it should. I would be happy, if someone could tell me how I could 'solve' this issue. Otherwise, all scrolling functions work fine, although I did not yet take care of the 'edge cases' (i.e. first and last page). Also, I did not update the docstrings yet, so I am not asking for feedback on those. My only question is if someone could have a look at what I am describing above. On Thu, 28 Apr 2022 at 14:07, Po Lu <luangruo@yahoo.com> wrote: > dalanicolai <dalanicolai@gmail.com> writes: > > > I have a question about this, namely: how to make a line the 'window > > start'? Using 'set-window-start` does not work. > > > > From 'emacs -Q' (which starts within the scratch buffer), immediately > > evaluate > > > > (set-window-start nil (point)) > > > > to set the 'new' window start. Subsequently do > > > > (set-window-vscroll nil 1) > > > > it will scroll from the start of the buffer, and not from the 'new' > > window start as I would expect > > That's because the point is obscured after the vscroll is applied, so > the display is recentered. You have to move point to some location that > is at least partially visible after the vscroll if you set > `make-cursor-line-fully-visible' to t, or a location that is fully > visible otherwise. > [-- Attachment #1.2: Type: text/html, Size: 5086 bytes --] [-- Attachment #2: image-roll.el --] [-- Type: text/x-emacs-lisp, Size: 18228 bytes --] (require 'image-mode) (require 'svg) (defgroup image-roll nil "Image roll configurations.") (defcustom image-roll-vertical-margin 2 "Page gap height." :type 'integer) (defcustom image-roll-step-size (lambda () (let* ((o (image-roll-page-overlay)) (s (overlay-get o 'display)) (w (image-property s :width))) (if w (* 50 (/ (float (if (consp w) (car w) w)) (window-pixel-height))) 50))) "Scroll step size. The value can be either a number or a function that takes no arguments and returns a positive number. If the number is equal to or larger than 1, it represents pixel units. Otherwise, if the value is between 0 and 1, it represents a fraction of the current page height." :type '(choice function interger float)) (defcustom image-roll-center nil "When non-nil, center the roll horizontally in the window." :type 'boolean) (defvar-local image-roll-number-of-pages-function nil "Function that should return the total number of pages. The function should return an integer with the total number of pages in the document.") (defvar-local image-roll-page-sizes-function nil "Function that should return page-sizes of document. The function should return a list of conses of the form (WIDTH . HEIGHT), both numbers.") (defvar-local image-roll-set-redisplay-flag-function nil) (defvar-local image-roll-display-page 'image-roll-demo-display-page "Function that sets the overlay's display property. The function receives the page number as a single argument (PAGE). The function should use `(image-roll-page-overlay PAGE)' to add the image of the page as the overlay's display-property.") (defmacro image-roll-debug (object) `(progn (print (format "%s = %s" ,object (eval ,object)) #'external-debugging-output) (eval ,object))) ;; define as macro's for setf-ability ;; TODO update docstring (defmacro image-roll-overlays (&optional window) "List of overlays that make up a scroll. Overlays with an even index hold the page-overlays, where the overlay at index 0 holds page number 1. For each page, except for the last page, the subsequent element holds the gap-overlay." `(image-mode-window-get 'overlays ,window)) (defmacro image-roll-page-overlay (&optional page) "Return the overlay that hold page number PAGE. Implemented as macro to make it setf'able." `(nth (1- ,page) (image-roll-overlays))) (defmacro image-roll-page-overlay-get (page prop) "Get overlay-property PROP of overlay holding page number PAGE. Implemented as macro to make it setf'able." `(overlay-get (nth (1- ,page) (image-roll-overlays)) ,prop)) (defmacro image-roll-current-page (&optional window) "Return the page number of the currently displayed page. The current page is the page that overlaps with the window start (this choice was made in order to simplify the scrolling logic)" `(image-mode-window-get 'page ,window)) (defun image-roll-overlay-height (page) (+ (cdr (image-roll-page-overlay-get page 'page-size)) (* 2 image-roll-vertical-margin))) (defun image-roll-visible-overlays () "Page numbers corresponding of currently visible overlays. The numbers are returned in a list. Overlays that are only partially visible are included." (let* (visible (page (image-roll-current-page)) (available-height (window-pixel-height))) (push page visible) (cl-decf available-height (- (image-roll-overlay-height page) (window-vscroll nil t))) (cl-incf page) (while (> available-height 0) (push page visible) (cl-decf available-height (image-roll-overlay-height page)) (cl-incf page)) visible)) (defun image-roll-undisplay-page (page) "Undisplay PAGE. The function replaces the image display property of the overlay holding PAGE with a space. It size is determined from the image its `image-size'." (display-warning '(image-roll) (format "undisplay %s" page) :debug "*image-roll-debug-log*") (let* ((o (image-roll-page-overlay page)) (im (overlay-get o 'display)) (s (image-size im t)) (w (car s)) (h (cdr s))) (overlay-put o 'display `(space . (:width (,w) :height (,h)))) (overlay-put o 'face `(:background "gray")))) (defun image-roll--new-window-function (winprops) "Function called first after displaying buffer in a new window. If the buffer is newly created, then it does not contain any overlay and this function creates erases the buffer contents after which it inserts empty spaces that each holds a page or gap overlay. If the buffer already has overlays (i.e. a second or subsequent window is created), the function simply copies the overlays and adds the new window as window overlay-property to each overlay." ;; (if (= (buffer-size) 0) (if (not (overlays-at 1)) (let (overlays (pages (if image-roll-number-of-pages-function (funcall image-roll-number-of-pages-function) image-roll-demo-number-of-pages)) (win (car winprops)) (inhibit-read-only t)) (erase-buffer) ;; here we only add the 'page' and 'window' overlay-properties, we add ;; more properties/information as soon as it becomes available in the ;; 'image-roll-display' function (dotimes (i pages) (let ((i (1+ i))) (insert " ") (let ((po (make-overlay (1- (point)) (point)))) (overlay-put po 'page i) (overlay-put po 'window win) (push po overlays)) (insert "\n"))) (delete-char -1) (image-mode-window-put 'overlays (nreverse overlays)) (set-buffer-modified-p nil) ;; we must put the cursor at the `point-min' for the vscroll ;; functionality to work. It is only required here because we will never ;; move the cursor (we will merely update overlay properties and vscroll) ;; (goto-char (point-min)) ;; required to make `pdf-view-redisplay-some-windows' call `pdf-view-redisplay' (when-let (fun image-roll-set-redisplay-flag-function) (funcall fun))) (let ((ols (mapcar (lambda (o) (let ((oc (copy-overlay o))) (overlay-put oc 'window (car winprops)) oc)) (image-roll-overlays)))) (image-mode-window-put 'overlays ols winprops))) (image-roll-goto-page 1)) (defun image-roll--redisplay (&optional window no-relative-vscroll) "Redisplay the scroll. Besides that this function can be called directly, it should also be added to the `window-configuration-change-hook'. The argument WINDOW is not use in the body, but it exists to make the function compatible with `pdf-tools' (in which case is a substitute for `pdf-view-redisplay'). When NO-RELATIVE-SCROLL is non-nil, then the relative-scroll is not included when setting teh vscroll position. For example this is used in `pdf-view-goto-page' (in the `pdf-scroll.el' extension) to make it scroll to the start of the page." (display-warning '(image-roll) (format "redisplay %s" (car (image-mode-winprops))) :debug "*image-roll-debug-log*") ;; NOTE the `(when (listp image-mode-winprops-alist)' from ;; `image-mode-reapply-winprops' was removed here (but in the end might turn ;; out to be required) ;; Beware: this call to image-mode-winprops can't be optimized away, because ;; it not only gets the winprops data but sets it up if needed (e.g. it's used ;; by doc-view to display the image in a new window). (image-mode-winprops nil t) (let* ((pages image-roll-demo-number-of-pages) ;; (page-sizes (make-list pages (cons (- (window-text-width nil t) 200) ;; (* 1.4 (window-text-width nil t))))) (page-sizes (if image-roll-page-sizes-function (funcall image-roll-page-sizes-function) (make-list pages (if (functionp image-roll-demo-page-size) (funcall image-roll-demo-page-size) image-roll-demo-page-size)))) ;; (let ((w (window-pixel-width))) ;; (make-list pages (cons w (* 1.4 w)))))) (n 0)) ;; (vpos 0)) (dolist (page-size page-sizes) (let* ((page-width (car page-size)) (overley-heigth (+ (cdr page-size) (* 2 image-roll-vertical-margin))) (o (nth n (image-roll-overlays)))) (when image-roll-center (overlay-put o 'before-string (when (> (window-pixel-width) page-width) (propertize " " 'display `(space :align-to (,(floor (/ (- (window-pixel-width) page-width) 2)))))))) (overlay-put o 'display `(space . (:width (,page-width) :height (,overley-heigth)))) (overlay-put o 'face `(:background "gray")) (overlay-put o 'page-size page-size) (setq n (+ n 1))))) ;; (let ((current-page (car (image-mode-window-get 'displayed-pages)))) (let (displayed) (dolist (p (image-roll-visible-overlays)) (funcall image-roll-display-page p) (push p displayed)) ;; (image-mode-window-put 'page (car (last displayed))) ; TODO check if possible to use 'displayed-pages (image-mode-window-put 'displayed-pages (reverse displayed)) (image-mode-window-put 'visible-pages-vscroll-limit (- (apply #'+ (mapcar #'image-roll-overlay-height displayed)) (window-text-height nil t)))) (when-let (p (image-roll-current-page)) (goto-line p) ;; (redisplay) (image-set-window-vscroll (or (image-mode-window-get 'vscroll) 10)))) (defun image-roll-goto-page (page &optional window) "Go to PAGE in PDF. If optional parameter WINDOW, go to PAGE in all `pdf-view' windows." (interactive (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg) (read-number "Page: ")))) (unless (and (>= page 1) (<= page (count-lines (point-min) (point-max)))) (error "No such page: %d" page)) ;; (unless window ;; (setq window ;; (if (pdf-util-pdf-window-p) ;; (selected-window) ;; t))) (save-selected-window ;; Select the window for the hooks below. (when (window-live-p window) (select-window window 'norecord)) (let ((changing-p (not (eq page (image-roll-current-page window))))) (when changing-p ;; (run-hooks 'pdf-view-before-change-page-hook) (setf (image-roll-current-page window) page) ;; (run-hooks 'pdf-view-change-page-hook)) (when (window-live-p window) (image-roll--redisplay window)) ;; (when changing-p ;; (pdf-view-deactivate-region) ;; (force-mode-line-update) ;; (run-hooks 'pdf-view-after-change-page-hook)))) nil)))) (defun image-roll-update-displayed-pages () (let ((old (print (image-mode-window-get 'displayed-pages) #'external-debugging-output)) (new (print (image-roll-visible-overlays) #'external-debugging-output))) ;; dolist because if images/pages are small enough, there might be ;; multiple image that need to get updated (dolist (p (cl-set-difference old new)) (image-roll-undisplay-page p) (image-mode-window-put 'displayed-pages (setq old (delete p old)))) ; important to update/setq old before ;; setting/appending new below (dolist (p (cl-set-difference new old)) (funcall image-roll-display-page p) (image-mode-window-put 'displayed-pages (setq old (append old (list p))))) ;; update also visible-range (image-mode-window-put 'visible-pages-vscroll-limit (- (apply #'+ (mapcar #'image-roll-overlay-height new)) (window-text-height nil t))))) (defun image-roll-next-page (&optional n) (interactive) (cl-incf (image-roll-current-page) (or n 1)) ;; (set-window-start nil (+ (point) 2)) (image-roll--redisplay)) (defun image-roll-previous-page () (interactive) (image-roll-next-page -1)) (defun image-roll-scroll-forward (&optional backward screen) (interactive) (let* ((current-page (image-roll-current-page)) (current-overlay-height (image-roll-overlay-height current-page)) (visible-pages-vscroll-limit (image-mode-window-get 'visible-pages-vscroll-limit)) (step-size (if screen (window-text-height nil t) image-roll-step-size)) ;; determine number of pages to forward/backward ;; (required if pages are small) (n 0) (available-height step-size) (remaining-height available-height) new-vscroll) (cond (backward (cl-decf available-height (window-vscroll nil t)) (while (> available-height 0) (setq remaining-height available-height) (setq n (1+ n)) (cl-decf available-height (image-roll-overlay-height (- current-page n)))) (setq n (- n))) (t (cl-decf available-height (- (image-roll-overlay-height current-page) (window-vscroll nil t))) (while (> available-height 0) (setq remaining-height available-height) (setq n (1+ n)) (cl-decf available-height (image-roll-overlay-height (+ current-page n)))))) (when backward (setq step-size (- step-size))) (image-roll-debug 'n) (if (= n 0) (setq new-vscroll (+ (window-vscroll nil t) step-size)) (setq new-vscroll (+ (window-vscroll nil t) remaining-height))) (if (cond ((< n 0) (forward-line n) (cl-decf (image-roll-current-page)) (image-set-window-vscroll (- (image-roll-overlay-height (image-roll-current-page)) remaining-height))) ((> n 0) (forward-line n) (cl-incf (image-roll-current-page) n) (image-set-window-vscroll remaining-height)) ((> (image-roll-debug 'new-vscroll) (image-roll-debug 'visible-pages-vscroll-limit)) (image-set-window-vscroll new-vscroll))) (image-roll-update-displayed-pages) (image-set-window-vscroll new-vscroll)))) (defun image-roll-scroll-backward () (interactive) (image-roll-scroll-forward t)) (defun image-roll-scroll-screen-forward () (interactive) (image-roll-scroll-forward nil t)) (defun image-roll-scroll-screen-backward () (interactive) (image-roll-scroll-forward t t)) (defun image-roll-demo-display-page (page) "Return demo image of page. This function is used for the image-roll-demo." (image-roll-debug 'page) (let* ((o (image-roll-page-overlay page)) (s (cdr (overlay-get o 'display))) (w (car (plist-get s :width))) (h (car (plist-get s :height))) (svg (svg-create w h))) (unless w (print "NO W" #'external-debugging-output)) (svg-rectangle svg 0 0 w h :fill-color "white") (svg-text svg (number-to-string page) :font-size "40" :fill "black" :x 20 :y 50) (when image-roll-center (overlay-put o 'before-string (when (> (window-pixel-width) w) (propertize " " 'display `(space :align-to (,(floor (/ (- (window-pixel-width) w) 2)))))))) (overlay-put o 'display (svg-image svg :margin `(0 . ,image-roll-vertical-margin))))) (define-derived-mode image-roll-mode special-mode "Image Roll" ;; we don't use `(image-mode-setup-winprops)' because it would additionally ;; add `image-mode-reapply-winprops' to the ;; `window-configuration-change-hook', but `image-roll--redisplay' already ;; reapplies the vscroll, so we simply initialize the ;; `image-mode-winprops-alist' here, and add lines from ;; `image-mode-reapply-winprops' at the start of `image-roll--redisplay'. (add-hook 'window-configuration-change-hook 'image-roll--redisplay nil t) (add-hook 'image-mode-new-window-functions 'image-roll--new-window-function nil t) (unless (listp image-mode-winprops-alist) (setq image-mode-winprops-alist nil))) ;; (add-hook 'window-configuration-change-hook ;; #'image-mode-reapply-winprops nil t)) ;; (image-mode-setup-winprops)) (setq image-roll-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "<down>") 'image-roll-scroll-forward) (define-key map (kbd "<up>") 'image-roll-scroll-backward) (define-key map (kbd "<next>") 'image-roll-next-page) (define-key map (kbd "<prior>") 'image-roll-previous-page) (define-key map (kbd "S-<next>") 'image-roll-scroll-screen-forward) (define-key map (kbd "S-<prior>") 'image-roll-scroll-screen-backward) map)) (when (featurep 'evil) (evil-define-key 'motion image-roll-mode-map "j" 'image-roll-scroll-forward "k" 'image-roll-scroll-backward "J" 'image-roll-next-page "K" 'image-roll-previous-page (kbd "C-j") 'image-roll-scroll-screen-forward (kbd "C-k") 'image-roll-scroll-screen-backward)) (defun image-roll-demo (&optional page-size pages) (interactive) (with-current-buffer (get-buffer-create "*image-roll-demo*") (erase-buffer) (image-roll-mode) (setq cursor-type nil) (setq image-roll-step-size 50) (setq-local image-roll-demo-page-size (or page-size (lambda () (let ((w (window-pixel-width))) (cons w (* 1.4 w)))))) (setq-local image-roll-demo-number-of-pages (or pages 1000)) (setq image-roll-center t) (switch-to-buffer (current-buffer)))) ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Question about (excessive?) ram usage when many overlays (with large vscroll) 2022-04-28 16:28 ` dalanicolai @ 2022-04-28 16:48 ` dalanicolai 0 siblings, 0 replies; 13+ messages in thread From: dalanicolai @ 2022-04-28 16:48 UTC (permalink / raw) To: Po Lu; +Cc: Emacs Devel [-- Attachment #1: Type: text/plain, Size: 4516 bytes --] B.t.w to see how things work when adding the `set-window-start`, you could uncomment that function in the `image-roll-next-page` function. You will find that the `image-set-window-vscroll` at the end of the `image-roll--redisplay` function has no effect. To see how things work when adding the redisplay, you could additionally uncomment it at the end of the `image-roll--redisplay` function. On Thu, 28 Apr 2022 at 18:28, dalanicolai <dalanicolai@gmail.com> wrote: > Thank you Po, for your explanations. Indeed this clears things > up a little, but unfortunately my question was not 'precise enough', > for the answer to 'solve' the issue that motivated this question. > (I wanted to keep my `image-mode.el` out of the question, but I > guess it is necessary to add it to explain the issue I am experiencing). > > So I have rewritten `image-mode.el` to scroll by `line + vscroll` instead > of using vscroll only. This solves the issue with the RAM usage for > 'large documents'. > Additionally, it made the code a little 'simpler', which is a welcome > 'side effect' > (I got rid of the 'gap' (page separation) overlays, and instead added > margins > to the images. So now the buffer contents is just single spaces on > separate > lines, where the number of lines is equal to the number of `images/pages`. > The 'gap' color can be configured by adding the overlay's face background). > It seems to work mostly correct (the 'scrolling' look a tiny bit less > smooth than > with the pure vscroll version, but I would say it is very acceptable). > > Anyway, I hope someone can quickly have a look at the issue explained below > (this should take less than a minute). So let me quickly provide some > context > for the issue. > > Currently, each overlay is overlaid on a single space on separate lines. > So the > contents of the buffer is a single space (holding a page overlay) on every > line. > So to jump to the next page, I can simply use 'forward-line'. Now this > works fine > when the page is 'off-window` but when the next page is already within view > ('on-window'), then going to the next line does not position that line at > the top of > the window, nor makes it the current `window-start. To get it at the top > of the > window, I could subsequently call `(set-window-start nil (point))`, > but then subsequently, `set-window-vscroll` does not work. To make > `set-window-vscroll` have effect, I can add a `redisplay` just before I > call it. > This works, but the 'jump' gets very/unacceptably ugly (you see the > 'recentering' > steps taking place). > > However, when the next page is 'off-window' than using the 'forward-line' > works > directly, and the page jump looks smooth. > > So to reproduce it from `emacs -Q` you can just load the `image-roll.el` > file that I > will attach here, then do `M-x image-roll-demo` and scroll down using the > arrow > keys until you see the page transition between page one and two. Now press > the > `page down` (PgDn) button to jump to the next page. > > Because page two is already 'on-window' this only moves the cursor one > page down. > > If you now press a second time `PgDn` then, because page 3 is still > `off-window`, > the jump takes place how it should. > > I would be happy, if someone could tell me how I could 'solve' this issue. > Otherwise, > all scrolling functions work fine, although I did not yet take care of the > 'edge cases' > (i.e. first and last page). Also, I did not update the docstrings yet, so > I am not asking > for feedback on those. My only question is if someone could have a look at > what I am > describing above. > > On Thu, 28 Apr 2022 at 14:07, Po Lu <luangruo@yahoo.com> wrote: > >> dalanicolai <dalanicolai@gmail.com> writes: >> >> > I have a question about this, namely: how to make a line the 'window >> > start'? Using 'set-window-start` does not work. >> > >> > From 'emacs -Q' (which starts within the scratch buffer), immediately >> > evaluate >> > >> > (set-window-start nil (point)) >> > >> > to set the 'new' window start. Subsequently do >> > >> > (set-window-vscroll nil 1) >> > >> > it will scroll from the start of the buffer, and not from the 'new' >> > window start as I would expect >> >> That's because the point is obscured after the vscroll is applied, so >> the display is recentered. You have to move point to some location that >> is at least partially visible after the vscroll if you set >> `make-cursor-line-fully-visible' to t, or a location that is fully >> visible otherwise. >> > [-- Attachment #2: Type: text/html, Size: 5956 bytes --] ^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2022-04-28 16:48 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2022-04-26 12:21 Question about (excessive?) ram usage when many overlays (with large vscroll) dalanicolai 2022-04-26 12:33 ` Eli Zaretskii 2022-04-26 13:24 ` Eli Zaretskii 2022-04-27 14:13 ` dalanicolai 2022-04-27 15:44 ` Eli Zaretskii 2022-04-27 17:13 ` Stefan Monnier 2022-04-27 17:18 ` Eli Zaretskii 2022-04-26 12:49 ` Po Lu 2022-04-27 14:01 ` dalanicolai 2022-04-28 11:56 ` dalanicolai 2022-04-28 12:06 ` Po Lu 2022-04-28 16:28 ` dalanicolai 2022-04-28 16:48 ` dalanicolai
Code repositories for project(s) associated with this external index https://git.savannah.gnu.org/cgit/emacs.git https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.