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

Prevent flickering in nushell due to FTCS marks #14677

Merged
merged 10 commits into from
May 11, 2023

Conversation

zadjii-msft
Copy link
Member

Tl;dr: Conpty would flush a frame whenever it encountered a FTCS mark. Combine that with the whole-line redrawing that nushell does, and the Terminal would get the prompt in two frames instead of one, causing a slight flickering. This fixes that by rendering the frame, but not flushing to the pipe when we encounter one of these sequences.

Closes #13710

A complication here: there are some sequences that we passthrough immediately when we encounter them. For example, \x1b[ 2q. we need to also not flush when we encounter one of these sequences. nushell emits one of these as a part of the prompt, and that would force the buffered frame to get written anyways, before writing that to the pipe.

@ghost ghost added Area-VT Virtual Terminal sequence support Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conpty For console issues specifically related to conpty labels Jan 13, 2023
@github-actions

This comment has been minimized.

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

I might need you to walk me through how the "flush to terminal" callback becoming "paint and then also flush" actually prevents excess flushes 😄

src/renderer/vt/XtermEngine.cpp Outdated Show resolved Hide resolved
@ghost ghost added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jan 13, 2023
src/renderer/vt/paint.cpp Outdated Show resolved Hide resolved
@ghost ghost removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jan 18, 2023
@zadjii-msft
Copy link
Member Author

@DHowett Okay I'm so sorry my handwriting is atrocious, but this is faster than trying to mermaid it

image

(imagine an "after" beneath the dotted line)

@zadjii-msft
Copy link
Member Author

zadjii-msft commented Apr 6, 2023

For real though:

Before

sequenceDiagram
    participant API Thread
    participant Render Thread
    participant Terminal
    Note left of API Thread: {the start of the prompt}
    Note left of API Thread: ^[[2q
    activate API Thread
    opt Parser
    Note right of API Thread: StateMachine::ProcessString
    Note right of API Thread: AdaptDispatch -> failed
    Note right of API Thread: StateMachine::FlushToTerminal
    opt XtermEngine
        Note right of API Thread: WriteTerminalW
        Note right of API Thread: write "^[[2q"to _buffer
        Note right of API Thread: _Flush
        API Thread ->> Terminal: _buffer goes out the pipe
    end
    Note left of API Thread: {the rest of the prompt}
        Note right of API Thread: InvalidateFlush
        Note right of API Thread: [paint the frame]
        Note right of API Thread: _Flush
        API Thread ->> Terminal: _buffer goes out the pipe
    end
    Note left of API Thread: Unlock console
    API Thread --> -Render Thread: 
    activate Render Thread
        Note right of Render Thread: PaintFrame
        opt XtermEngine
            Note right of Render Thread: [paint the frame]
            Note right of Render Thread: _Flush
        end
        Render Thread ->> Terminal: _buffer goes out the pipe
    deactivate Render Thread
Loading

After this PR

sequenceDiagram
    participant API Thread
    participant Render Thread
    participant Terminal
    Note left of API Thread: {the start of the prompt}
    Note left of API Thread: ^[[2q
    activate API Thread
        opt Parser
            Note right of API Thread: StateMachine::ProcessString
            Note right of API Thread: AdaptDispatch -> failed
            API Thread -->> Render Thread: NotifyPaintFrame
            Note right of API Thread: StateMachine::FlushToTerminal
        opt XtermEngine
            Note right of API Thread: WriteTerminalW
            Note right of API Thread: write "^[[2q"to _buffer
        end
        end
    Note left of API Thread: {the rest of the prompt}
        opt XtermEngine
            Note right of API Thread: InvalidateFlush
            Note right of API Thread: [paint the frame]
        end
    Note left of API Thread: Unlock console
    API Thread --> -Render Thread: 
    activate Render Thread
        Note right of Render Thread: PaintFrame
        opt XtermEngine
            Note right of Render Thread: [paint the frame]
            Note right of Render Thread: _Flush
        end
        Render Thread ->> Terminal: _buffer goes out the pipe
    deactivate Render Thread
Loading

Copy link
Member

@lhecker lhecker left a comment

Choose a reason for hiding this comment

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

The code does look good to me and I'd approve it instantly, but I'm not sure whether this is the best approach. From what I can tell, flushing during circling isn't necessarily at all and I believe it'd be better in general to instead only flush when the console API call finishes (i.e. when the entire user string has been processed), because this will avoid calling WriteFile for every line that's written. It should also solve the issue, while also improving ConPTY performance drastically.

src/renderer/vt/XtermEngine.cpp Outdated Show resolved Hide resolved
src/renderer/vt/invalidate.cpp Outdated Show resolved Hide resolved
@zadjii-msft
Copy link
Member Author

From what I can tell, flushing during circling isn't necessarily at all

That's kinda my thesis too 😉 Though, I was moreso tracking that in #12336. I figured best to separate the work into

  • fix nushell which we horribly broke
  • make conpty go brrrrrrrrrrr

@github-actions

This comment has been minimized.

//
// Instead, we'll leave this frame in _buffer, and just keep appending to
// it as needed.
if (_noFlushOnEnd) [[unlikely]]
Copy link
Member

Choose a reason for hiding this comment

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

FYI I don't think the [[unlikely]] is worth it here. Its implementation is super dumb in MSVC and causes any and all affected unlikely code to be moved to the end of the function, even if it's literally just a single mov [rcx+1234], 0 instruction like here. I think it's still useful, but it seems like its usage is mostly restricted to a rather "nuanced" application, especially after observing the disassembly output.

@DHowett DHowett merged commit 6364450 into main May 11, 2023
@DHowett DHowett deleted the dev/migrie/b/13710-ftcs-flushing branch May 11, 2023 21:11
PKRoma pushed a commit to PKRoma/Terminal that referenced this pull request May 12, 2023
DHowett pushed a commit that referenced this pull request Sep 21, 2023
Previously, all unknown escape sequences would lead to an immediate call
to `VtEngine::_Flush()`. This lead to problems with nushell which uses
FTCS marks that were unknown to us. Combined with the linewise redrawing
that nushell does, Terminal would get the prompt in two separate frames,
causing a slight flickering.

#14677 fixed this by suppressing the `_Flush()` call when unknown
sequences are encountered. Unfortunately, this triggered a bug due
to our somewhat "inconsistent" architecture in conhost:
`XtermEngine::WriteTerminalW` isn't just used to flush unknown sequences
but also used directly by `InputBuffer::PassThroughWin32MouseRequest`
to write its mouse sequence directly to the ConPTY host.
`VtEngine` already contains a number of specialized member functions
like `RequestWin32Input()` to ensure that `_Flush()` is called
immediately and another member could've been added to solve this issue.
This commit now adds `RequestMouseMode` in the same vein.

But I believe we can make the system more robust in general by using
eager flushing by default (= safe), similar to how a `write()` on a
TCP socket flushes by default, and instead only selectively pause and
unpause flushing with a system similar to `TCP_CORK`.

This seems to work fairly well, as it solves:
* The original nushell bug
* The new bug
* Improves overall throughput by ~33% (due to less flushing)

In particular the last point is noteworthy, as this commit removes
the last performance bottleneck in ConPTY that isn't `VtEngine`.
Around ~95% of all CPU and wall time is spent in there now and any
improvements to `VtEngine` should yield immediately results.

Closes #15711

## Validation Steps Performed
* Clone/Run https://github.com/chrisant996/repro_enable_mouse_input
* Hold Ctrl+Alt and circle with the mouse over the viewport
* Repro.exe prints the current cursor coordinates ✅
* Run nushell
* No flickering when typing in the prompt ✅
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-VT Virtual Terminal sequence support Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conpty For console issues specifically related to conpty zBugBash-Consider
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[1.15+] Flushing FTCS sequences causes flickering in nushell 0.66+
4 participants