RawTextView Primitive? #88

Open
opened 3 months ago by luckyobserver · 8 comments

I've been wrestling with the performance of TextView for a few days. I'm working on a mud client and have a main window that scrolls the mud text which is originally rendered with ANSI color codes. These are translated when using tview/cview and as the main scroll buffer grows the application becomes extremely sluggish / unresponsive. I would really like to have a primitive that I can just pass the raw ANSI text to and that supports scrolling. Maybe I'm using the library wrong? Any advice is appreciated.

I've been wrestling with the performance of TextView for a few days. I'm working on a mud client and have a main window that scrolls the mud text which is originally rendered with ANSI color codes. These are translated when using tview/cview and as the main scroll buffer grows the application becomes extremely sluggish / unresponsive. I would really like to have a primitive that I can just pass the raw ANSI text to and that supports scrolling. Maybe I'm using the library wrong? Any advice is appreciated.
Owner

From what I can tell, ANSI escape sequences are not automatically converted into color tags (you must pass them through ANSIWriter or TranslateANSI first). Because cview stores text as []byte, you should have noticed a performance gain when switching from tview.

Will you please share a link to your source repository, or share a minimal example? I'm glad to take a closer look and do some profiling.

From what I can tell, ANSI escape sequences are not automatically converted into color tags (you must pass them through `ANSIWriter` or `TranslateANSI` first). Because cview stores text as `[]byte`, you should have noticed a performance gain when switching from tview. Will you please share a link to your source repository, or share a minimal example? I'm glad to take a closer look and do some profiling.
Poster

The repo is here: https://github.com/seandheath/go-mud-client

All of the cview code is in this file:
https://github.com/seandheath/go-mud-client/blob/cview/internal/tui/tui.go

The data is coming from the MUD here (ANSI encoded):
dceccaff1d/internal/client/connect.go (L77)

I create a window object and instantiate an ANSIWriter to the textview for each window. Every write passes through the ANSIWriter, check here:
dceccaff1d/internal/tui/tui.go (L119)

Everything works well but as the scroll buffer grows the UI gets very sluggish and eventually freezes ~1000 lines. I did some profiling with pprof and it's set up on localhost:6060 in main():
dceccaff1d/main.go (L39)

but nothing jumped out at me, the biggest processor consumers were regexes being run by cview. Same with tview. There was a big boost in performance on tview when I disabled regions and line wrapping, but still not where I want it to be. I'm working on an implementation with tcell directly so I can avoid all the color tag parsing.

The repo is here: https://github.com/seandheath/go-mud-client All of the cview code is in this file: https://github.com/seandheath/go-mud-client/blob/cview/internal/tui/tui.go The data is coming from the MUD here (ANSI encoded): https://github.com/seandheath/go-mud-client/blob/dceccaff1dd23c2cf0b8c76e1ca95276b790119f/internal/client/connect.go#L77 I create a window object and instantiate an ANSIWriter to the textview for each window. Every write passes through the ANSIWriter, check here: https://github.com/seandheath/go-mud-client/blob/dceccaff1dd23c2cf0b8c76e1ca95276b790119f/internal/tui/tui.go#L119 Everything works well but as the scroll buffer grows the UI gets very sluggish and eventually freezes ~1000 lines. I did some profiling with pprof and it's set up on localhost:6060 in main(): https://github.com/seandheath/go-mud-client/blob/dceccaff1dd23c2cf0b8c76e1ca95276b790119f/main.go#L39 but nothing jumped out at me, the biggest processor consumers were regexes being run by cview. Same with tview. There was a big boost in performance on tview when I disabled regions and line wrapping, but still not where I want it to be. I'm working on an implementation with tcell directly so I can avoid all the color tag parsing.
Owner

Thanks, I will have a look when I have the time.

A possible workaround would be to handle the scrollback buffer yourself. You could create a struct which embeds a *TextView and overrides the MouseHandler and InputHandler methods (to facilitate internal scrolling). Instead of storing the entire buffer within the TextView, only the currently visible portion would ever be stored within the widget at one time. This may or may not take less time than simply writing a tcell implementation.

Thanks, I will have a look when I have the time. A possible workaround would be to handle the scrollback buffer yourself. You could create a struct which embeds a `*TextView` and overrides the `MouseHandler` and `InputHandler` methods (to facilitate internal scrolling). Instead of storing the entire buffer within the `TextView`, only the currently visible portion would ever be stored within the widget at one time. This may or may not take less time than simply writing a tcell implementation.
Poster

I tried something like that on tview. The best performance I have achieved was by storing all my MUD output into a long string and capturing the mouse/pageup handlers. When I detected a scroll up I would stop writing to the "main window" and print all of the content into the buffer and scroll around until I hit the bottom again and then go back into "live" mode. The only issue I had with that was that input was still coming in while I was scrolling and I didn't have a graceful way to append the input to the text of the TextView without bogging it down. Maybe I could just batch my writes to the TextView while scrolling and write once a second? I'll see if it works.

I tried something like that on tview. The best performance I have achieved was by storing all my MUD output into a long string and capturing the mouse/pageup handlers. When I detected a scroll up I would stop writing to the "main window" and print all of the content into the buffer and scroll around until I hit the bottom again and then go back into "live" mode. The only issue I had with that was that input was still coming in while I was scrolling and I didn't have a graceful way to append the input to the text of the TextView without bogging it down. Maybe I could just batch my writes to the TextView while scrolling and write once a second? I'll see if it works.
Owner

By long string do you mean an actual string variable? If so, storing text as []byte wherever possible will provide (at least slight) performance boosts, when that data must be shared/copied around the different areas of the program.

By long string do you mean an actual `string` variable? If so, storing text as `[]byte` wherever possible will provide (at least slight) performance boosts, when that data must be shared/copied around the different areas of the program.
Poster

I was using string but I'll switch to []byte and get the self-managed scroll buffer implemented and see how it goes

I was using `string` but I'll switch to `[]byte` and get the self-managed scroll buffer implemented and see how it goes
Poster

I almost have this working.

Expected behaviour:
Main window max lines are set to w.GetInnerRect() height inside the app SetAfterResizeFunc. Normal content scrolls without being cached in the TextView buffer. I cache all []bytes in the window.content buffer. If the user presses PgUp then we enter "scroll mode" and new content is not written to the TextView until we leave "scroll mode" by scrolling to the bottom or pressing ESC.

I'm restricting scrolling to PgUp and PgDn while I debug this. I call this function:
e3071f0948/internal/tui/tui.go (L116)

everything works perfectly the first time I scroll up. The second time I enter "scroll mode" the TextView starts at the very beginning/top of the content instead of the end. I've tried various combinations of Clear, Write, SetBytes, ScrollToEnd, ScrollTo, t.app.Draw, etc... but it always seems to jump all the way to the top every time I scroll up after the first time.

I almost have this working. Expected behaviour: Main window max lines are set to `w.GetInnerRect()` height inside the app `SetAfterResizeFunc`. Normal content scrolls without being cached in the `TextView` buffer. I cache all `[]bytes` in the `window.content` buffer. If the user presses `PgUp` then we enter "scroll mode" and new content is not written to the `TextView` until we leave "scroll mode" by scrolling to the bottom or pressing `ESC`. I'm restricting scrolling to PgUp and PgDn while I debug this. I call this function: https://github.com/seandheath/gomc/blob/e3071f094842ee686abb18298285aac78abab322/internal/tui/tui.go#L116 everything works perfectly the first time I scroll up. The second time I enter "scroll mode" the `TextView` starts at the very beginning/top of the content instead of the end. I've tried various combinations of `Clear`, `Write`, `SetBytes`, `ScrollToEnd`, `ScrollTo`, `t.app.Draw`, etc... but it always seems to jump all the way to the top every time I scroll up after the first time.
Owner

I've taken a quick look. GetBuffer returns the number of lines in the text buffer, rather than the number of lines needed to display the content with word wrapping. This discrepancy might be part of the issue, I wanted to note it at least. Adding something like GetInternalBufferSize and some clarification to their docs might be needed.

Will you confirm that the block inside the condition if !w.scrolling && h <= trow { does fire, and it is only the ScrollToEnd call which isn't having any effect?

I've taken a quick look. `GetBuffer` returns the number of lines in the text buffer, rather than the number of lines needed to display the content with word wrapping. This discrepancy might be part of the issue, I wanted to note it at least. Adding something like `GetInternalBufferSize` and some clarification to their docs might be needed. Will you confirm that the block inside the condition `if !w.scrolling && h <= trow {` does fire, and it is only the `ScrollToEnd` call which isn't having any effect?
Sign in to join this conversation.
No Milestone
No Assignees
2 Participants
Notifications
Due Date

No due date set.

Dependencies

This issue currently doesn't have any dependencies.

Loading…
There is no content yet.