Implement text editor DOM updates manually instead of via React #5624

Merged
merged 40 commits into from Feb 20, 2015

Projects

None yet

10 participants

@nathansobo
Contributor

React is an amazing abstraction, but very few abstractions come without at least some overhead. In the case of Atom's text editor, it's worth the effort to avoid this overhead by hand coding all DOM updates. The update code is repetitive, but it's also pretty straightforward. In exchange, we get much simpler profile traces, drop a big dependency, and reduce the overall number of moving parts in Atom. Below are some flame graphs for entering a character at the end of a syntax-highlighted line. The comparison is somewhat unfair because the React case could have been a bit more optimized, but I think the pictures also show how much simpler the manual approach is in our case.

screenshot_2015-02-18_15_03_39

screenshot_2015-02-18_15_01_42

React is a great tool for many cases, for our particular needs in this particular case I decided it would be easier to just do things for ourselves.

Remaining Tasks:

  • Don't use React for EditorComponent
  • Fix autocomplete spec.
  • Unify DOM updates / polling across editors.
@benogle
Contributor
benogle commented Feb 18, 2015

Legit

@nathansobo
Contributor

I'm going to keep the basic structure of the code with the TextEditorComponent separate from the TextEditorElement the same for now. It might be nice to clean it up but it's not really a huge deal and I think time could be spent better in other ways. Someday I would love to get to a more minimal DOM structure as well, but it doesn't seem worth the risk of breaking anything to lose a couple extra nodes.

nathansobo added some commits Feb 10, 2015
@nathansobo nathansobo Update cursor nodes manually 883af7a
@nathansobo nathansobo Update highlight nodes manually c294bb1
@nathansobo nathansobo Make CursorsComponent a plain object instead of using React b4ecb65
@nathansobo nathansobo Make HighlightsComponent a plain object instead of using React b0a29bd
@nathansobo nathansobo Perform all LinesComponent DOM updates manually da793e8
@nathansobo nathansobo Make LinesComponent a normal object instead of a React component
Also, remove ability to disable hardware acceleration since there’s
no longer a need for it and it complicated this conversion.
c06e100
@nathansobo nathansobo Store maxLineNumberDigits in oldState 679cada
@nathansobo nathansobo Perform GutterComponent DOM updates manually 2d771ab
@nathansobo nathansobo Make GutterComponent a plain JS object instead of a React component 168df98
@nathansobo nathansobo Store hidden input data in TextEditorPresenter::state 8e27d82
@nathansobo nathansobo Use presenter state in InputComponent 52a9a76
@nathansobo nathansobo Perform InputComponent DOM updates manually 0047e3b
@nathansobo nathansobo Construct LinesComponent with visibility param to avoid redundant update ea49fc6
@nathansobo nathansobo Use plain JS object for InputComponent instead of React 8784d48
@nathansobo nathansobo 🐎 Optimize line node updates 8552aca
@nathansobo nathansobo Perform ScrollbarComponent DOM updates manually d9e100c
@nathansobo nathansobo Use plain JS object for ScrollbarComponent instead of React da6bd36
@nathansobo nathansobo Fix assignment of oldState values in ScrollbarComponent
Signed-off-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
77a4482
@nathansobo nathansobo Manually update DOM in ScrollbarCornerComponent
Signed-off-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
6b3d29a
@nathansobo nathansobo Use plain JS object for ScrollbarCornerComponent instead of React
Signed-off-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
5c7e0c3
@nathansobo nathansobo Add .focused to presenter state
Signed-off-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
4654bad
@nathansobo nathansobo Use presenter for focused state in EditorComponent bf29a02
@nathansobo nathansobo Move new character measurement to end of full update to avoid reflow fd603a0
@nathansobo nathansobo Add TextEditorPresenter::state.gutter.visible 156569f
@nathansobo nathansobo Use presenter to determine gutter visibility 2fba497
@nathansobo nathansobo Remove unnecessary ::mini subscription on model in TextEditorComponent 1845234
@nathansobo nathansobo Update scoped config values in presenter when grammar changes 5ecefe7
@nathansobo nathansobo Cache ::showIndentGuide config setting e244aae
@nathansobo nathansobo Remove showIndentGuide subscription from view d89ec25
@nathansobo nathansobo Update TextEditorComponent DOM node manually ccdc4eb
@nathansobo nathansobo Remove unused prototype properties dae15ea
@nathansobo nathansobo Use CompositeDisposable instead of SubscriberMixin in editor view 0d109d6
@nathansobo nathansobo Remove redundant update requests in editor view 1e8b0ac
@nathansobo nathansobo Replace cursor blink React props with normal properties c9a6c32
@nathansobo nathansobo Make EditorComponent a plain JS object rather than a React component 7033b27
@nathansobo nathansobo Delete overlay node from hash before removing d337c88
@nathansobo nathansobo Add document coordination methods to ViewRegistry
These will assist in updating and reading the DOM in a non-blocking
manner across components.
de4d995
@nathansobo nathansobo Centralize text editor DOM interaction through atom.views
This ensures that DOM writing, reading, and polling properly interleaves
with DOM interactions from other text editors and any other code that
coordinates via atom.views. Not sure about the location of it though.
1d84d74
@nathansobo nathansobo Update scrollHeight/Width before scrollTop/Left in dummy scrollbars
This avoids jerky scroll behavior when auto-scrolling at the margins.
dc752ed
@nathansobo
Contributor

@atom/core @atom/non-github-maintainers Could some people build this branch and try working with it tomorrow. I need some more 👀 looking for any regressions, and as a reward you'll see speed improvements.

@lee-dohm
Member

@nathansobo I've been working with it for the past hour. I haven't noticed any issues. But it does seem a bit more responsive 😀

@thomasjo
Member

Been using this all day and haven't noticed any regressions. The constant console logging is very annoying though, hehe 👼

@thomasjo
Member

I've not really noticed any major performance issues in the past few months, so hard to tell whether the speedup is noticeable or not on my machine, but things do seem a bit more responsive. Could be confirmation bias though.

@postcasio
Contributor

Same here. No issues so far, playing well with my other packages. Scrolling is silky smooth!

@as-cii
Member
as-cii commented Feb 20, 2015

Same here, it has been working fine so far. Great job, @nathansobo! 👍

@nathansobo nathansobo Pause polling when updates are requested, but don’t start polling over
The blinking cursor was ensuring that we never polled in certain cases.
We need to allow the interval to continue polling at a normal pace, but
just avoid doing any work that could delay the next animation frame.
32d393d
@nathansobo
Contributor

Thanks to all of you for your time! @thomasjo thanks for pointing out the console logging. I accidentally left it in on the last commit and just force-pushed an amendment.

@nathansobo nathansobo merged commit f4116c7 into master Feb 20, 2015

0 of 4 checks passed

atom Build #1910483 started
Details
atom-linux Build #1910484 started
Details
atom-rpm Build #1910485 started
Details
atom-win Build #1910486 started
Details
@nathansobo nathansobo deleted the ns-manual-dom-updates branch Feb 20, 2015
@kevinsawicki
Member

📈 💚 ♻️

@lee-dohm
Member

👏 Awesome work!

@nathansobo
Contributor

I force pushed back the merge of this PR so we can get a release out this morning without me having to worry about this code over the weekend. Will merge it again once the release is out.

@kevinsawicki kevinsawicki commented on the diff Feb 20, 2015
src/view-registry.coffee
+ writer() while writer = @documentWriters.shift()
+ reader() while reader = @documentReaders.shift()
+ @performDocumentPoll() if @performDocumentPollAfterUpdate
+
+ startPollingDocument: ->
+ @pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval)
+
+ stopPollingDocument: ->
+ window.clearInterval(@pollIntervalHandle)
+
+ performDocumentPoll: =>
+ if @documentUpdateRequested
+ @performDocumentPollAfterUpdate = true
+ else
+ @performDocumentPollAfterUpdate = false
+ poller() for poller in @documentPollers
@kevinsawicki
kevinsawicki Feb 20, 2015 Member

Might want to return after this line to prevent array return.

@nathansobo
nathansobo Feb 20, 2015 Contributor

👍 Very good catch.

@nathansobo nathansobo restored the ns-manual-dom-updates branch Feb 20, 2015
@nathansobo
Contributor

Okay, merged in 9648093. Sorry for the false 🚨.

@nathansobo nathansobo changed the title from WIP: Implement text editor DOM updates manually instead of via React to Implement text editor DOM updates manually instead of via React Feb 25, 2015
@bernardodiasc bernardodiasc referenced this pull request in bernardodiasc/readings Feb 27, 2015
Open

Log #1

@hemanth
hemanth commented Mar 1, 2015

Interesting stats, but was curious about why was react opted before...

@nathansobo
Contributor

@hemanth There was also a huge improvement over the first version of the editor when we added React. It was much like scaffolding.

@hemanth
hemanth commented Mar 1, 2015

👍

@mcharytoniuk

Why didn't you use React's PureRenderMixin (http://facebook.github.io/react/docs/pure-render-mixin.html), 'shouldComponentUpdate' custom implementation, batched updates and other optimisations that would produce better results than manual updates and abandoned the entire solution rapidly instead?

@benogle
Contributor
benogle commented Mar 2, 2015

@mcharytoniuk The old implementation was doing the same thing as described in the doc page:

Under the hood, the mixin implements shouldComponentUpdate, in which it compares the current props and state with the next ones and returns false if the equalities pass.

See https://github.com/atom/atom/blob/97a671cb496f288ba30703af4800a12551e99d50/src/lines-component.coffee#L74-L92

@mcharytoniuk

I know that maybe it's too late for such ideas but I'd try moving away at least cursor component out of lines component and position it absolutely on top of it to prevent lines component updates caused by cursor movement. Also, Flipboard published react-canvas recently. Did you ever consider using canvas for animations / some editor elements as they did? Many people criticised them for negative SEO impact but inside text editor every crazy trick is acceptable imo. :)

@nathansobo
Contributor

A big selling-point of Atom is the ability to style content with CSS. Used properly, the DOM doesn't introduce prohibitive overhead for our use case.

React being removed is purely an implementation detail in this case and shouldn't affect package authors in any way. Appreciate your concern and suggestions here, but on balance it's easier to achieve our goals without it in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment