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

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

Merged
merged 40 commits into from Feb 20, 2015

Conversation

Projects
None yet
10 participants
@nathansobo
Contributor

nathansobo commented Feb 18, 2015

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

This comment has been minimized.

Show comment
Hide comment
@benogle

benogle Feb 18, 2015

Contributor

Legit

Contributor

benogle commented Feb 18, 2015

Legit

@nathansobo

This comment has been minimized.

Show comment
Hide comment
@nathansobo

nathansobo Feb 19, 2015

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.

Contributor

nathansobo commented Feb 19, 2015

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

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.
Fix assignment of oldState values in ScrollbarComponent
Signed-off-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Manually update DOM in ScrollbarCornerComponent
Signed-off-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Use plain JS object for ScrollbarCornerComponent instead of React
Signed-off-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Add .focused to presenter state
Signed-off-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Add document coordination methods to ViewRegistry
These will assist in updating and reading the DOM in a non-blocking
manner across components.
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.
Update scrollHeight/Width before scrollTop/Left in dummy scrollbars
This avoids jerky scroll behavior when auto-scrolling at the margins.
@nathansobo

This comment has been minimized.

Show comment
Hide comment
@nathansobo

nathansobo Feb 20, 2015

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.

Contributor

nathansobo commented Feb 20, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@lee-dohm

lee-dohm Feb 20, 2015

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 😀

Member

lee-dohm commented Feb 20, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@thomasjo

thomasjo Feb 20, 2015

Member

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

Member

thomasjo commented Feb 20, 2015

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

@thomasjo

This comment has been minimized.

Show comment
Hide comment
@thomasjo

thomasjo Feb 20, 2015

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.

Member

thomasjo commented Feb 20, 2015

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

This comment has been minimized.

Show comment
Hide comment
@postcasio

postcasio Feb 20, 2015

Contributor

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

Contributor

postcasio commented Feb 20, 2015

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

@as-cii

This comment has been minimized.

Show comment
Hide comment
@as-cii

as-cii Feb 20, 2015

Member

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

Member

as-cii commented Feb 20, 2015

Same here, it has been working fine so far. Great job, @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.
@nathansobo

This comment has been minimized.

Show comment
Hide comment
@nathansobo

nathansobo Feb 20, 2015

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.

Contributor

nathansobo commented Feb 20, 2015

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 added a commit that referenced this pull request Feb 20, 2015

Merge pull request #5624 from atom/ns-manual-dom-updates
WIP: Implement text editor DOM updates manually instead of via React

@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

This comment has been minimized.

Show comment
Hide comment
@kevinsawicki

kevinsawicki Feb 20, 2015

Member

📈 💚 ♻️

Member

kevinsawicki commented Feb 20, 2015

📈 💚 ♻️

@lee-dohm

This comment has been minimized.

Show comment
Hide comment
@lee-dohm

lee-dohm Feb 20, 2015

Member

👏 Awesome work!

Member

lee-dohm commented Feb 20, 2015

👏 Awesome work!

@nathansobo

This comment has been minimized.

Show comment
Hide comment
@nathansobo

nathansobo Feb 20, 2015

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.

Contributor

nathansobo commented Feb 20, 2015

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.

@performDocumentPollAfterUpdate = true
else
@performDocumentPollAfterUpdate = false
poller() for poller in @documentPollers

This comment has been minimized.

@kevinsawicki

kevinsawicki Feb 20, 2015

Member

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

@kevinsawicki

kevinsawicki Feb 20, 2015

Member

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

This comment has been minimized.

@nathansobo

nathansobo Feb 20, 2015

Contributor

👍 Very good catch.

@nathansobo

nathansobo Feb 20, 2015

Contributor

👍 Very good catch.

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

@nathansobo

This comment has been minimized.

Show comment
Hide comment
@nathansobo

nathansobo Feb 20, 2015

Contributor

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

Contributor

nathansobo commented Feb 20, 2015

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 Feb 27, 2015

Open

Log #1

@hemanth

This comment has been minimized.

Show comment
Hide comment
@hemanth

hemanth Mar 1, 2015

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

hemanth commented Mar 1, 2015

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

@nathansobo

This comment has been minimized.

Show comment
Hide comment
@nathansobo

nathansobo Mar 1, 2015

Contributor

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

Contributor

nathansobo commented Mar 1, 2015

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

@hemanth

This comment has been minimized.

Show comment
Hide comment
@hemanth

hemanth commented Mar 1, 2015

👍

@mcharytoniuk

This comment has been minimized.

Show comment
Hide comment
@mcharytoniuk

mcharytoniuk Mar 1, 2015

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?

mcharytoniuk commented Mar 1, 2015

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?

@kl3ryk

This comment has been minimized.

Show comment
Hide comment

kl3ryk commented Mar 1, 2015

@benogle

This comment has been minimized.

Show comment
Hide comment
@benogle

benogle Mar 2, 2015

Contributor

@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

shouldComponentUpdate: (newProps) ->
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
'overlayDecorations', 'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'visible',
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration',
'placeholderText', 'performedInitialMeasurement', 'backgroundColor', 'cursorPixelRects'
)
{renderedRowRange, pendingChanges} = newProps
return false unless renderedRowRange?
[renderedStartRow, renderedEndRow] = renderedRowRange
for change in pendingChanges
if change.screenDelta is 0
return true unless change.end < renderedStartRow or renderedEndRow <= change.start
else
return true unless renderedEndRow <= change.start
false

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

shouldComponentUpdate: (newProps) ->
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
'overlayDecorations', 'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'visible',
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration',
'placeholderText', 'performedInitialMeasurement', 'backgroundColor', 'cursorPixelRects'
)
{renderedRowRange, pendingChanges} = newProps
return false unless renderedRowRange?
[renderedStartRow, renderedEndRow] = renderedRowRange
for change in pendingChanges
if change.screenDelta is 0
return true unless change.end < renderedStartRow or renderedEndRow <= change.start
else
return true unless renderedEndRow <= change.start
false

@mcharytoniuk

This comment has been minimized.

Show comment
Hide comment
@mcharytoniuk

mcharytoniuk Mar 2, 2015

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. :)

mcharytoniuk commented Mar 2, 2015

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

This comment has been minimized.

Show comment
Hide comment
@nathansobo

nathansobo Mar 2, 2015

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.

Contributor

nathansobo commented Mar 2, 2015

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