Skip to content

Optimization: reduce bulk selection and history overhead - #1111

Open
prostohz wants to merge 1 commit into
SVG-Edit:masterfrom
prostohz:perf/bulk-selection-and-history
Open

prostohz wants to merge 1 commit into
SVG-Edit:masterfrom
prostohz:perf/bulk-selection-and-history

Conversation

@prostohz

@prostohz prostohz commented Oct 22, 2026 •

Copy link
Copy Markdown

PR description

Bulk selection and movement repeatedly alternate geometry reads with selection-outline writes. They also update the shared resize grips for elements whose grips are hidden. This makes commands on selections of hundreds of shapes expensive.

This change:

  • Reads bounding boxes before updating selection outlines during bulk selection and movement.
  • Updates the shared grips when shown or when their owning selector changes size, and reuses selectors without repeatedly scanning from the start of the pool.
  • Accumulates 2D matrix products numerically before creating the result matrix, preserves the existing 3D fallback, and avoids unnecessary identity transforms and bounding-box reads.
  • Skips rotation lookup for non-geometric attribute changes and reuses menu references and live toolbar collections.

Performance evidence

Exploratory Chromium benchmarks against c44f061d2f9a626d2771cc931298af5a45522d87 (the current master), using 500 rect/circle elements and 100 measurements per scenario. Five workers each run one scenario at a time. Values are medians in milliseconds:

Scenario Before After
Select 37.717 8.568
Move 49.867 16.435
Duplicate 89.878 33.733
Group 4.623 3.855
Ungroup 26.545 4.242
Align left 49.260 16.360
Fill 1.182 0.877
Delete 2.215 1.590
Undo move 222.915 75.510
Redo move 211.735 80.150

The timer covers the synchronous API call and its synchronous handlers, including layout forced during that call. Setup and result checks are outside the timer. Deferred work and subsequent paint are excluded; in particular, the fill result does not measure completion of the deferred fill operation. These are measurements on one machine with concurrent workers, and the patch was selected during a 20-iteration optimization experiment, rather than evaluated on an independent holdout run.

The source changes are the accepted cumulative patch from iteration-018. Raw samples and iteration history and the benchmark harness are available for inspection.

Validation

  • npm run build passed.
  • npm test passed: Standard lint, 617 unit tests, and 87 Playwright tests.
  • Added four browser regression tests covering matrix composition (including 3D inputs), independent empty transforms, selector reuse, and grip positions after bulk movement, undo, and redo.

The repository's PR template refers to Cypress; these tests use its current Playwright suite. No user-facing API changes or new documentation are needed.

Summary by Sourcery

Optimize bulk selection and editing workflows while preserving selection geometry and transform behavior.

Bug Fixes:

  • Keep selection grips, outlines, and matrix handling correct during bulk selection, movement, undo, redo, and empty-transform cases.

Enhancements:

  • Reduce bulk selection, movement, transformation, history, and editor UI overhead by minimizing redundant geometry work, reusing selectors and DOM collections, and avoiding unnecessary calculations.

Tests:

  • Add browser regression coverage for matrix composition, independent identity transforms, selector reuse, and grip positioning across bulk edits and history operations.

@sourcery-ai

sourcery-ai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This PR improves bulk editing performance by batching geometry reads, minimizing selector and grip DOM updates, reusing selector instances efficiently, and optimizing matrix, history, and UI lookup paths while retaining 3D transform behavior and adding browser regressions for the highest-risk changes.

Sequence diagram for batched bulk selection geometry updates

sequenceDiagram
    participant Selection
    participant SelectorManager
    participant Selector
    participant DOM

    Selection->>Selection: getBBox(elem)
    Selection->>SelectorManager: requestSelector(elem, bbox)
    SelectorManager->>Selector: reset(elem, bbox)
    Selection->>Selector: showGrips(false)
    Selection->>Selector: resize(bbox)
    Selector->>DOM: update selector outline
    opt grips shown
        Selector->>Selector: updateGrips()
        Selector->>DOM: update shared grip positions
    end
Loading

Sequence diagram for bulk movement and selector reuse

sequenceDiagram
    participant Move
    participant SelectorManager
    participant Selector
    participant DOM

    Move->>Move: apply movement transforms
    Move->>Move: utilsGetBBox(selected)
    Move->>SelectorManager: requestSelector(selected, bbox)
    SelectorManager->>Selector: reuse available selector
    SelectorManager->>Selector: resize(bbox)
    Selector->>DOM: update selector outline
    opt active grips
        Selector->>Selector: updateGrips()
        Selector->>DOM: update shared grips
    end
Loading

Flow diagram for optimized matrix multiplication

flowchart LR
    A[Matrix arguments] --> B{Any matrix is2D false?}
    B -->|Yes| C[DOMMatrix.fromMatrix]
    C --> D[3D matrix multiplication]
    B -->|No| E[Accumulate a b c d e f numerically]
    E --> F[Create SVG matrix result]
    D --> G[normalizeNearZero]
    F --> G
    H[Empty transform list] --> I[Return identity transform]
Loading

File-Level Changes

Change Details Files
Reduces geometry and DOM work during bulk selection, movement, and selector updates.
  • Precomputes bounding boxes before requesting or resizing selectors.
  • Updates shared grips only when visible or when their owning selector changes size.
  • Reuses selector instances via a tracked free-list position instead of rescanning the pool.
  • Adds end-to-end coverage for bulk movement grip placement and selector reuse.
packages/svgcanvas/core/select.js
packages/svgcanvas/core/selected-elem.js
packages/svgcanvas/core/selection.js
tests/e2e/bulk-selection-regressions.spec.js
Optimizes transform composition while preserving correct behavior for 3D matrices and empty transform lists.
  • Accumulates 2D matrix products numerically and creates the SVG matrix only once.
  • Uses the DOMMatrix path when any input is 3D.
  • Avoids creating or repeatedly resolving identity transforms for empty transform lists.
  • Adds regression coverage for transform ordering, 3D inputs, and independent identity results.
packages/svgcanvas/core/math.js
packages/svgcanvas/core/select.js
tests/e2e/unit/svgcore-matrix-regressions.spec.js
Defers selected-element bounding-box reads until after transform setup where possible.
  • Skips the initial bounding-box read for common geometric element types.
  • Reads the box after transform-related calculations and retains handling for elements without usable bounds.
packages/svgcanvas/core/recalculate.js
Avoids unnecessary history and UI lookup work for non-geometric updates.
  • Skips rotation lookup unless the changed attribute can affect the bounding-box center.
  • Caches context-menu link references.
  • Caches live toolbar element collections and simplifies single-child link handling.
packages/svgcanvas/core/undo.js
src/editor/dialogs/cmenuDialog.js
src/editor/panels/TopPanel.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

This branch has not been deployed

No deployments
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.

1 participant