RichTextFX icon indicating copy to clipboard operation
RichTextFX copied to clipboard

Jerky vertical scrolling on wide lines

Open samhutchins opened this issue 8 years ago • 9 comments

We're using a CodeArea to show the source code for pages to users. Some websites go a bit mad with minification and have huge chunks of code on one line, and when scrolling down the view there's a noticeable pause as you scroll over it. The pause is worst on the first encounter, but it's a noticeable jump every time you scroll over it.

I've had a quick look using jvisualvm, and it doesn't look like theres a huge CPU spike or anything, are there some potentially long running operations happening on the UI thread?

Using RTFX 0.8.1 from Maven JRE 1.8.0_131 Pauses happen on macOS 10.12.6 and Windows 10

Here's some code you can run to try for yourself:

import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class RichTextFXDemo extends Application
{
    public static void main(
        String[] args)
    {
        launch(args);
    }

    @Override
    public void start(
        Stage primaryStage) throws Exception
    {
        final CodeArea codeArea = new CodeArea();
        codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
        codeArea.replaceText(getDemoString());
        codeArea.showParagraphAtTop(0);
        
        final BorderPane root = new BorderPane(new VirtualizedScrollPane<>(codeArea));
        final Scene scene = new Scene(root, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private String getDemoString()
    {
        final StringBuilder sb = new StringBuilder();
        
        for (int i = 0; i < 1000; i++)
        {
            sb.append("This is a short line\n");
        }
        
        for (int i = 0; i < 100; i++)
        {
            for (int j = 0; j < 1000; j++)
            {
                sb.append("A section of a really long line. ");
            }
            sb.append("\n");
        }
        
        for (int i = 0; i < 1000; i++)
        {
            sb.append("This is a short line\n");
        }
        
        return sb.toString();
    }
}

samhutchins avatar Jan 10 '18 13:01 samhutchins

I don't see any noticeable pause on Linux. Could you post up a small video demonstrating the pause?

I'd guess the issue is in RichTextFX's dependency: Flowless.

JordanMartinez avatar Jan 11 '18 18:01 JordanMartinez

You can see the scrollbar jump when it gets to the long lines.

Demo

Feels weirds every time I scroll past that block of text. It's worse with bigger documents and syntax highlighting. I wonder if the screen density impacts it, on my 4k screen at work it's much more noticeable.

Bigger document

The text in the second example is just the source code for bbc.co.uk

samhutchins avatar Jan 12 '18 14:01 samhutchins

Ok, the second video makes that much more noticeable.

So, some part of the jerky scrolling is Flowless. It works by identifying one cell to lay out as the anchor and then lays out the cells above and below it. If there is space remaining, it may need to re-layout those cells to push them up/down so that all the space is used. This layout process isn't as optimized as it could be. Sometimes, it updates certain observables before finishing a full layout of the viewport, taking up some computation time to update those observables' dependencies.

The other part might be caused by the syntax highlighter code you might be using. See #659 and #657 for an idea of what I mean.

There might be some places where RichTextFX could be optimized. However, this may also just be a JDK issue because we can only build upon what they provide in their API. I'm guessing there are more efficient ways of laying out rich text besides TextFlow to lay out individual lines of text.

JordanMartinez avatar Jan 12 '18 19:01 JordanMartinez

I've removed the syntax highlighting and the scrolling behaviour is much better. I'm going to look through those examples you've linked, and try to implement more efficient highlighting. Thanks!

samhutchins avatar Jan 15 '18 13:01 samhutchins

So I've looked over the example you've provided in other issues, and I've adapted it to the application I'm working on. It doesn't really solve the problem though. The approach you've outlined calculates styles only for the visible paragraphs, but doing it for the whole document at once doesn't seem to be a problem for me anyway.

Once the problematic section of text is reached and styled, the scroll behaviour is as bad as it was when the program calculated the highlighting for the entire document.

Given that the scrolling issues are only there when there are many StyleSpans in a Paragraph, it seems that the problem is in figuring out how to lay-out all the StyleSpans. Is there any way of allowing the RichTextFX (or VirtualFlow) components to make some assumptions to speed this up? For example, in our case we're never going to change the height of a character or line, all we're doing is changing the colour.

samhutchins avatar Jan 15 '18 15:01 samhutchins

Here's how the model gets rendered onto the screen:

  1. Each Paragraph object maps to a TextFlow object
  2. Each differently-styled part in the text maps to an individual Text object for text-only styles (e.g. font size, font color, etc.). These are then added to the TextFlow object's children list.
  3. RTFX-specific styles (background color, underline, selection [I think]) that are shared between Text objects map to one Path[] object. These get updated on each layout call in case the user has scrolled (I think).

Since you have such a long line of text, it takes longer to run steps 2 and 3 for that one line than it does for all others. I'm not sure whether disabling step three (if you don't use it) would speed things up or not, but it'd be worth looking into. If so, perhaps the third step's implementation could be better optimized. You can see the code for the base class used to generate RTFX-specific style objects by looking at ParagraphText's CustomCSSShapeHelper.

If the issue is purely with step two, then the only thing I can think of is to use some sort of caching.

JordanMartinez avatar Jan 15 '18 17:01 JordanMartinez

Ok, we'll try and look into skipping steps and result caching

samhutchins avatar Jan 16 '18 08:01 samhutchins

comparison of background omission.zip

I commented out all the custom background stuff. There appears to be a slight difference in rendering. I'm guessing it would be more noticeable if I added a syntax highlighter.

JordanMartinez avatar Jan 16 '18 18:01 JordanMartinez

@Jugen this is probably related to #935 .

ghost avatar Jun 26 '20 15:06 ghost