unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* 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: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: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 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 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: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 public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).