Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(render): improve renderer; remove flickering #1132

Merged
merged 15 commits into from
Oct 30, 2024

Conversation

LeperGnome
Copy link
Contributor

@LeperGnome LeperGnome commented Sep 8, 2024

Hey! This is my naive approach to fixing output flickering. Here are my thoughts behind the solution:

  • Flickering is caused by ansi.EraseEntireLine escape chars, because it allows the time, when there's an empty output.
  • So instead we just move cursor to the beginning of the drawing section on each new frame and write over latest frame, thus replacing old characters.
  • Erase characters from the old frame on each line end with ansi.EraseLineRight
  • Erase lines from old frame with ansi.EraseDisplayRight

I tested #1032 and it solves it. Also solved the issue for my project + a handfull of examples all work as intended.

I also looked into some PRs regarding render mechanism (e.g. #1027), and it seems like you're reworking it, but my change seems to provide good value for what it is, without messing up the API and so on...

I might just be missing some important concept here, so please let me know what you think, thanks!

@LeperGnome
Copy link
Contributor Author

Hey guys! Any feedback? I'll gladly fix remaining tests if you confirm, that this is the right direction.

@meowgorithm
Copy link
Member

Wow, thanks for this, @LeperGnome. I didn't expect this to work as well as it appears to.

We are indeed working on a new renderer, but this is potentially quite helpful in the meantime as you suggest. That said, it's still a very fundamental change, so we will need to test this very thoroughly in a variety of terminals, environments, and situations before we can merge it (for example, on Windows, behind SSH, and so on). Because of that, I'd like to merge it but can't offer an answer or ETA just yet.

@LeperGnome
Copy link
Contributor Author

@meowgorithm that's great! Let me know if you need any help in testing / fixing golden solution.

@meowgorithm
Copy link
Member

@LeperGnome yeah, if you could fix the tests that would be awesome!

@LeperGnome
Copy link
Contributor Author

Could you please run the CI pipeline, so I can see if I fixed all the tests

@LeperGnome
Copy link
Contributor Author

All right, checks green, let me know if you need any more help, looking forward to this thing being merged!

@meowgorithm
Copy link
Member

Thanks for doing that! As I mentioned, this will take some time for us to vet, and I can neither offer and ETA nor promise it will be merged, but so far it seems pretty good.

Copy link
Member

@aymanbagabas aymanbagabas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @LeperGnome, thank you for sending this PR. I think this makes sense to merge, just a few comments below. We also need to ensure that this is VT100 compliant and works with all terminals out there i.e. Linux Console, Urxvt, Xterm, etc.

standard_renderer.go Outdated Show resolved Hide resolved
standard_renderer.go Outdated Show resolved Hide resolved
@LeperGnome
Copy link
Contributor Author

LeperGnome commented Sep 16, 2024

@aymanbagabas

ensure that this is VT100 compliant

I'm not a terminal emulator guru by any means, but it seems like control characters, that I've introduced are listed on vt100.net. Does it ensure the compliance?

I'm just educating myself, not proposing you to skip any testing

@aymanbagabas
Copy link
Member

I'm not a terminal emulator guru by any means, but it seems like control characters, that I've introduced are listed on vt100.net. Does it ensure the compliance?

Yes, but I would still test this inside terminals, especially things like the Linux Console.

@LeperGnome
Copy link
Contributor Author

@aymanbagabas Hi! What should I do about the skipLine? As I pointed out in review comment, it's not obvious for me, that this is actually an optimisation (correct me if I'm wrong). If decision was up to me, I would

  • either remove it (as I did in PR)
  • or conduct some testing with different terminal emulators, terminal sizes, rendered content

I'm not really sure, how would I measure results, since I would have to measure not only the speed of bubbletea program frame generation, but also emulator rendering speed with relation to frame content length.

@aymanbagabas
Copy link
Member

@aymanbagabas Hi! What should I do about the skipLine? As I pointed out in review comment, it's not obvious for me, that this is actually an optimisation (correct me if I'm wrong). If decision was up to me, I would

It would be a huge optimization for remote sessions like SSH. You want to reduce the amount of data sent over the wire as much as possible in such cases. I think comparing strings is not expensive and saves a lot of data from being sent.

if newLines[i] == oldLines[i] {
  buf.WriteString(ansi.CursorDown1) // "\x1b[B"
} else {
  line = line + ansi.EraseLineRight
  // ...
}

ansi.CursorDown1 is only 3 bytes [\x1b [ B] compared to the whole line

@LeperGnome
Copy link
Contributor Author

Oh, I never thought of this in context of ssh, I guess you're right!
I'll update my PR.

@LeperGnome
Copy link
Contributor Author

LeperGnome commented Sep 18, 2024

@aymanbagabas Hey, I added skipping logic.
Also tried to make it work even with queued messages, spent some time debugging why it does work, hours later noticed this:

case printLineMessage:
    ... 
    r.repaint()

which removes our previous frame state.

So I figured that it's easier to just skip this optimization in this case... Also added a comment, so the next person won't struggle so much =)

P.S. I guess tests are failing on main, regardless of my changes

@mcoops
Copy link

mcoops commented Sep 20, 2024

I'll throw in, can confirm this works fantastic for our usage, especially in a pty, and it'd be a great stop gap until the rewrite for the renderer is ready.

The last commit especially has removed any random artifacting that we were experiencing - the TUI screen was stable except for some occasional, random refresh for a lack of a better word.

But it's really nice now!

@LeperGnome
Copy link
Contributor Author

@meowgorithm @aymanbagabas Hey guys, let me know if you need any more help regarding this PR! For now it seems like my part is done.

@yorukot
Copy link
Contributor

yorukot commented Oct 5, 2024

It works incredibly well and successfully fixed my issue(#1032)! I've tested it on both tmux and zellij, and everything runs perfectly.

@LeperGnome
Copy link
Contributor Author

@yorukot I wrote this fix for my file manager, now it slowly fixes other file managers, such as your superfile! Really glad to hear!

@meowgorithm
Copy link
Member

Let's also make a point to test this on Windows. In theory it'll be fine, but we should do our diligence there, just in case.

@aymanbagabas
Copy link
Member

Tested Glow and a some examples on both macOS (Ghostty) and Windows Terminal. All of them run great except for the package-manager example. The cursor doesn't go back to the starting position after printing a line.

CC/ @LeperGnome

@LeperGnome
Copy link
Contributor Author

@aymanbagabas took me while, but it seems that somehow ansi.CursorUp(0) breaks rendering on single-line frames. This does not make much sense to me, and if you have more insight, please share. Anyhow, I fixed the issue by just not moving up if latest frame was a single line...

@LeperGnome
Copy link
Contributor Author

LeperGnome commented Oct 15, 2024

Ohh... I see... Looking at ansi.CursorUp(n) I see this:

func CursorUp(n int) string {
	var s string
	if n > 1 {
		s = strconv.Itoa(n)
	}
	return "\x1b[" + s + "A"
}

which basically means that ansi.CursorUp(0) (results in \x1b[A (!!!)) is the same as ansi.CursorUp(1) (results in \x1b[A, and there's probably no reason to make it a special case, except may be it's shorter). Does not seem too good to me.

@aymanbagabas
Copy link
Member

which basically means that ansi.CursorUp(0) (results in \x1b[A (!!!)) is the same as ansi.CursorUp(1) (results in \x1b[A, and there's probably no reason to make it a special case, except may be it's shorter). Does not seem too good to me.

Right, the reason behind this is to make it shorter. All of these have the same meaning \x1b[0A, \x1b[1A, and \x1b[A which is to move the cursor up by one.

@LeperGnome
Copy link
Contributor Author

Wait... \x1b[0A is that same as \x1b[A on the api level? That's wild. I thought it should be a noop or something. Anyway, thank you. You live - you learn xD

@meowgorithm
Copy link
Member

fwiw @aymanbagabas and I had almost this exact same exchange at some point

@LeperGnome
Copy link
Contributor Author

Hey guys! I see some progress being done on new renderer, which supersedes my changes. Does this mean anything regarding this PR being useful? i.e. are you still planning on merging this?)

@meowgorithm
Copy link
Member

meowgorithm commented Oct 24, 2024

Hey, @LeperGnome! We're still planning on merging this.

Getting the cell based renderer right will take some time, and the two can live in parallel. I think for the mid term there will be a flag where you can switch between the two, with the standard renderer being the default.

To confirm, did you fix tea.Println in Windows #1132 (comment)?

@LeperGnome
Copy link
Contributor Author

@meowgorithm I don't have a Windows machine, but it seems like the issue was not platform-related. And yes, I fixed it in this wonderful big brain commit: 8e3bbbc =)

standard_renderer.go Outdated Show resolved Hide resolved
@meowgorithm
Copy link
Member

meowgorithm commented Oct 25, 2024

Just an update @LeperGnome: things look good to us over here. We’re going to test a little bit more and if things look good after that we’ll plan to merge this next week.

Thanks for the PR: it’s awesome.

Copy link
Member

@caarlos0 caarlos0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes look good to me.

tested it both standalone and through SSH. Seemed to work great in both cases!

Thanks for the PR, btw ❤️

@meowgorithm meowgorithm merged commit 9bafc58 into charmbracelet:main Oct 30, 2024
19 checks passed
@meowgorithm
Copy link
Member

It is done! This will be available in the next release. Thanks for this awesome PR, @LeperGnome, and for your patience with it. This is such an improvement for literally everyone across the board.

@LeperGnome
Copy link
Contributor Author

LeperGnome commented Oct 30, 2024

Thank you all! I'm really glad I could make such an improvement for already awesome library!

  • Build a house
  • Raise a son
  • Plant a tree
  • Make an opensource contribution

@meowgorithm
Copy link
Member

Just a note that this is now available in Bubble Tea v1.2.0. Thanks again for the great contribution!

aymanbagabas added a commit that referenced this pull request Nov 12, 2024
~This implements a cell-based renderer instead of the existing
line-based one. The cell-based renderer will be used when
`TEA_EXPERIMENTAL=cellbuf` is exported.~ The cell-based renderer, now
called The Ferocious Renderer, is enabled by default. To disable it set
`TEA_EXPERIMENTAL=unferocious`.

To _trace_ the output of the program, you can export
`TEA_TRACE=<filename>` and `TEA_TRACE_OUTPUT=1` and/or
`TEA_TRACE_INPUT=1` to see the I/O of the program.

Supersedes: #1132
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants