perf(editor): replace editor rendering pipeline with standalone viewport-windowed editor architecture - #1857
Open
loerei wants to merge 17 commits into
Open
perf(editor): replace editor rendering pipeline with standalone viewport-windowed editor architecture#1857loerei wants to merge 17 commits into
loerei wants to merge 17 commits into
Conversation
Author
|
Hi team! Long-time user here. 👋 This is my first contribution to the project. I started looking into this after noticing typing lag once my personal diary grew to around 100k characters, which led me to explore a viewport-windowed approach to reduce layout work during editing. I've verified the changes on my device, ran the existing unit test suite successfully, and included performance measurements and a demo video in the PR description. I'm happy to iterate on the implementation based on your feedback. |
loerei
force-pushed
the
feature/viewport-windowed-editor
branch
from
August 4, 2026 14:26
15432dc to
30b91a1
Compare
…complete VelocityTracker lifecycle
…n up legacy NestedScrollView checks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR replaces the editor rendering pipeline with a standalone viewport-windowed editor architecture while preserving the existing editing model and document format. The primary goal is to reduce layout work during typing in very large notes (>100k characters) without changing user-visible behavior.
It scope-bounds layout measurements and formatting span processing, reducing measured execution duration inside
SimplenoteEditText.onDraw()at 100,000+ characters (2,500+ lines) from ~80.5ms down to ~29.8ms, while preserving native touch-drag selection, restoring scroll position persistence, and restoring native Android OverScroller fling inertia scrolling.Performance Demonstration
The attached video demonstrates typing responsiveness on a document containing 108,440 characters and 2,658 lines:
SimplenoteEditText.onDraw()execution duration).SimplenoteEditText.onDraw()execution duration).SimplenotePerfDiffDemo.mp4
Why
1. Root Cause: Unbounded Layout Measurement
In the legacy architecture,
SimplenoteEditTextwas nested inside aNestedScrollView. BecauseScrollViewmeasures child views withMeasureSpec.UNSPECIFIED, Android was forced to measure, layout, and render all 100,000+ characters across the entire document on every single keypress, resulting in a baseline execution duration of ~80.5ms insideSimplenoteEditText.onDraw().2. User Impact: Main Thread Stalls & Cumulative Frame Skips
While 80.5ms represents a single execution pass inside
SimplenoteEditText.onDraw(), rapid typing compounds this delay on the Main UI Thread:SimplenoteEditText.onDraw()exceeds the frame deadline, skipping multiple VSYNC render cycles.toString()on 100,000 characters repeatedly allocates temporary String objects on every keypress.Implementation Details
1. Layout & View Architecture (
fragment_note_editor.xml&SimplenoteEditText.java)NestedScrollView: Replaced root and nestedNestedScrollViewwrappers with a cleanFrameLayout&LinearLayouthierarchy (layout_weight="1").SimplenoteEditTextnow manages its own vertical scrollbar (android:scrollbars="vertical").SimplenoteEditTextfromAppCompatMultiAutoCompleteTextViewto nativeMultiAutoCompleteTextViewto avoid AppCompat compatibility layer overhead.setIncludeFontPadding(false)) and elegant text height (setElegantTextHeight(false)).Layout.BREAK_STRATEGY_SIMPLEandLayout.HYPHENATION_FREQUENCY_NONE(Android M+).textAutoCorrect), relying on standard window-level hardware acceleration.2. Local Cursor Windowing & Incremental Scanning (
SimplenoteEditText.java,NoteEditorFragment.java, &AutoBullet.java)enoughToFilter): Replaced full-texttoString().substring()with a localizedsubSequencewindow inspecting max 200 characters around the cursor.processChecklists(int start, int count)to scan and apply checkbox spans only within the paragraph bounds of the active edit window instead of the whole file.setTitleSpan): RestrictedMetricAffectingSpansearch to the title line range ([0, titleEndPosition + 1]).AutoBulletEarly Exit: Added instant line-break checkeditable.charAt(newCursorPosition - 1) != '\n'to bypasstoString()allocations on standard typing.3. Scroll Position Persistence & Native Fling Inertia (
SimplenoteMovementMethod.java&SimplenoteEditText.java)OnScrollChangeListenerdirectly onSimplenoteEditTextto preserve scroll position state across navigation and note opens.SimplenoteMovementMethod.onTouchEvent()to returnsuper.onTouchEvent(...), restoring drag-selection and cursor placement.OverScrollerandVelocityTrackerinSimplenoteEditText.onTouchEvent(), restoring native Android fling deceleration and preserving standard platform scrolling dynamics upon touch release (ACTION_UP).Verification & Performance Benchmarks
1. Automated & Compilation Verification
.\gradlew assembleDebug.\gradlew testDebugUnitTestSM-S928B).2. Empirical Performance Metrics (108,440 characters / 2,658 lines)
SimplenoteEditText.onDraw()Duration: Measured directly insideSimplenoteEditText.onDraw(), execution duration dropped from ~80.5ms (legacytrunk) down to ~29.8ms at 108,440 characters.ScrollViewfeel.Files Changed
Core Editor Engine
Simplenote/src/main/java/com/automattic/simplenote/widgets/SimplenoteEditText.java: Implemented hardware render stack,OverScrollerfling inertia, local cursor windowing, and incremental checklist processing.Simplenote/src/main/java/com/automattic/simplenote/utils/SimplenoteMovementMethod.java: Delegated touch events back tosuper.onTouchEventfor touch selection.Fragment & Utility Optimizations
Simplenote/src/main/java/com/automattic/simplenote/NoteEditorFragment.java: Attached scroll listener onSimplenoteEditTextto persist scroll position, scopedsetTitleSpanto title paragraph, and enabled incremental checklist processing.Simplenote/src/main/java/com/automattic/simplenote/utils/AutoBullet.java: Added early exit check for non-newline typing.Layout & Build System
Simplenote/src/main/res/layout/fragment_note_editor.xml: RemovedNestedScrollViewwrappers and configured standalone vertical scrollbar layout withtextAutoCorrect.Simplenote/build.gradle: Configured debug build string resource value.