rivo / tview

Terminal UI library with rich, interactive widgets — written in Golang
MIT License
10.81k stars 562 forks source link

Redraw after each key event slows down the main loop #446

Closed gnojus closed 3 years ago

gnojus commented 4 years ago

Current implementation performs a draw after each handled key event. While this behavior does make sense (because the primitive probably has adjusted it's content), it also creates its own problems.

The key events may be sent very rapidly on a quick scroll or a paste action. If application screen is large enough, the draw operation takes significant time to complete. This makes the main loop go out of sync with key input, resulting in delayed handling of continuous events, even after the user has finished his action (scroll/paste).

This creates noticeable lag that cannot be avoided with current implementation.

I don't see any nice solution to this. I was thinking of asynchronous draw or stacked events, but these of course creates their own problems. Anyway, I felt that this should be addressed as it is not a very pleasant experience on user end in some cases.

rivo commented 4 years ago

I understand that. Immediate redrawing after a key press is a property of tview so I can't just make it asynchronous as it will break a lot of applications.

I could add ways to temporarily suspend redrawing. But it would require us (or the app) to determine when this is appropriate. I don't think you can easily find out whether something is pasted, for example. You could measure the time between successive events, I guess. It would be messy. And it would introduce lag because you don't know what the last event of that sequence is so you'll have to wait a bit and then redraw.

We could try, though.

gnojus commented 4 years ago

I'm not a fan with measuring delays. I'm pretty sure that this could be easily broken by a quick swipe with that Apple magic mouse, because it has some dynamics if I recall correctly.

Another idea I had was to stack key events asynchronously and then process all that stack at once after a draw finishes. This however might result in a lot of lag if the user defined key handlers takes a lot of time to complete. Probably it would be necessary to redraw after certain amount of time even if the key stack is still not completed. While this wouldn't completely remove the lag, processing of the actions should end exactly after the actual stream of key events. Maybe this way it would be better, I don't know.

rivo commented 4 years ago

The question is, what are the situations where we have to process a lot of characters at once? Is it only when text is pasted? And if so, can that be recognized? Or have you had situations where you were just typing really fast?

gnojus commented 4 years ago

No, I’m not a fast typer. However, the lag was visible when quickly scrolling with a large terminal screen.

rivo commented 4 years ago

You mean this wasn't so much about entering text but rather holding down an arrow key or similar to scroll?

gnojus commented 4 years ago

It was by swiping with two fingers on a touch-pad vertically, that is quite fast scrolling. And if I'm not mistaken, scrolling on my terminal emulator results in UP/DOWN key events from tcell (at least when not in mouse-mode).

abitrolly commented 4 years ago

I wonder if https://github.com/vadimdemedes/ink has the same problem?

rivo commented 4 years ago

That's interesting. In this case, the question would be how the touchpad translates the swipe gesture into keystrokes. In graphical user interfaces, you would get a new screen position or the "traveled" distance so you can calculate the scrolling offset. But we don't get that here.

So even if keystroke processing was faster, I don't know if would behave the way you would expect.

gnojus commented 4 years ago

Well, if we would process the key event backlog without redrawing after each one, maybe we could keep up with the speed of incoming events and not have the widget still scrolling after the user has ended the scroll gesture?

rivo commented 4 years ago

So you're suggesting that we keep a separate (asynchronous) buffer for key events and then track if there's a backlog. The question is, how can we catch up with the backlog? Is it enough to simply not redraw the screen? I haven't run any performance tests. I wonder if the bottleneck is the drawing or the actual processing of events.

rivo commented 3 years ago

I will close this for now. We can reopen when other people are having the same problem. At the moment, it doesn't look very urgent to me and I don't see a good solution.