Skip to content

perf(editor): centralize shape culling display updates#7841

Merged
steveruizok merged 5 commits intoperformance-stack-1from
sr/centralize-shape-culling
Feb 4, 2026
Merged

perf(editor): centralize shape culling display updates#7841
steveruizok merged 5 commits intoperformance-stack-1from
sr/centralize-shape-culling

Conversation

@steveruizok
Copy link
Copy Markdown
Collaborator

@steveruizok steveruizok commented Feb 4, 2026

Summary

  • Creates a ShapeCullingProvider React context that maintains a registry of shape container refs
  • Replaces per-shape useQuickReactor subscriptions with a single centralized reactor in ShapesToDisplay
  • Each Shape component registers its container refs on mount via the context and unregisters on unmount
  • The centralized CullingController uses the context to update visibility of all registered containers

This keeps DOM element tracking in React-land rather than on the Editor, maintaining separation between the editor and rendering layers.

Performance improvement

With N shapes on canvas, this changes:

  • Before: N separate EffectScheduler instances, each subscribing to getCulledShapes() and running when it changes
  • After: 1 reactor that iterates only the registered containers, updating only those whose state changed

This reduces subscription overhead from O(N) to O(1).

Test plan

  1. Run yarn dev and open the examples app
  2. Create many shapes on the canvas
  3. Pan around the canvas - shapes outside the viewport should still be culled (hidden) correctly
  4. Shapes inside the viewport should remain visible
  5. Verify no visual regressions when selecting, editing, or interacting with shapes

Closes #7831


Note

Medium Risk
Changes the shape rendering pipeline by moving culling visibility updates to a shared registry and single reactor, which could cause shapes to incorrectly hide/show if registration or updates misfire. Also adjusts store string hashing cache behavior, which may affect memory/perf characteristics under load.

Overview
Centralizes shape culling display toggling. Shapes now register their DOM containers via a new ShapeCullingProvider/useShapeCulling context, and DefaultCanvas runs a single CullingController reactor to apply display: none/block updates for all registered shapes (replacing per-shape culling reactors in Shape.tsx).

Reduces reactive churn in a few hot paths. notVisibleShapes is rewritten to avoid unnecessary Set allocations when results don’t change, CanvasShapeIndicators replaces generic deep equality with a purpose-built comparator for its computed render data, and useActivePeerIds$ switches to a Set equality comparator.

Tweaks store hashing behavior. ImmutableMap now always uses cachedHashString for strings and removes cache size/length limits, changing caching/memory tradeoffs for string hashing.

Written by Cursor Bugbot for commit 1881c57. This will update automatically on new commits. Configure here.

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Feb 4, 2026 8:18pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Feb 4, 2026 8:18pm
chat-template Ignored Ignored Preview Feb 4, 2026 8:18pm
tldraw-docs Ignored Ignored Preview Feb 4, 2026 8:18pm
tldraw-shader Ignored Ignored Preview Feb 4, 2026 8:18pm
workflow-template Ignored Ignored Preview Feb 4, 2026 8:18pm

Request Review

@huppy-bot
Copy link
Copy Markdown
Contributor

huppy-bot bot commented Feb 4, 2026

Hey! 👋

This PR contains changes to api-report.api.md, which means you've made some API changes. To help reviewers and SDK users understand the impact, please add a "### API changes" section to your PR description.

Here's an example:

### API changes
- Describe what API changes were made
- List any breaking changes
- Note any new or removed parameters

Once you add this section, the check will pass automatically!

Instead of each Shape component having its own useQuickReactor
that subscribes to getCulledShapes(), this creates a centralized
ShapeCullingManager that:

- Maintains a registry of shape container refs
- Uses a single reactor in ShapesToDisplay to update visibility
- Only updates containers whose culling state actually changed

This reduces O(N) subscriptions to O(1), improving performance
when many shapes are on the canvas.

Closes #7831
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🔵 In progress
View logs
branching-chat-template 4f53d45 Feb 04 2026, 06:34 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🔵 In progress
View logs
agent-template 4f53d45 Feb 04 2026, 06:34 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Feb 4, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
agent-template 4f53d45 Feb 04 2026, 06:40 PM

hashed = hashString(string)
if (STRING_HASH_CACHE_SIZE === STRING_HASH_CACHE_MAX_SIZE) {
STRING_HASH_CACHE_SIZE = 0
stringHashCache = {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unbounded string hash cache causes memory leak

High Severity

The stringHashCache is now unbounded, causing a memory leak. The original Immutable.js implementation limited the cache to 255 entries and cleared it when full, and only cached strings longer than 16 characters. This change removes both safeguards: all strings (regardless of length) are now cached, and the cache is never cleared. In a long-running editor session with many unique string keys, this will cause continuous memory growth.

Additional Locations (1)

Fix in Cursor Fix in Web

Replace the imperative ShapeCullingManager with a React context-based provider and hook. Adds packages/editor/src/lib/hooks/useShapeCulling.tsx (ShapeCullingProvider + useShapeCulling) and removes the ShapeCullingManager class and its export. Update Shape to register/unregister via useShapeCulling, wrap rendered shapes in DefaultCanvas with ShapeCullingProvider, and have CullingController call the provider's updateCulling. Also remove the editor.shapeCulling property and related exports/initialization.
Replace getCurrentPageShapeIds() with getCurrentPageShapes() and iterate the shape objects directly. This avoids repeated editor.getShape() lookups, uses shape.id for set operations, and updates the fast-path visibility check to use allShapes.length. Minor performance and clarity improvements with no functional change.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@steveruizok steveruizok changed the base branch from main to performance-stack-1 February 4, 2026 20:15
@steveruizok steveruizok added the improvement Product improvement label Feb 4, 2026
@steveruizok steveruizok merged commit 8edd404 into performance-stack-1 Feb 4, 2026
19 of 20 checks passed
@steveruizok steveruizok deleted the sr/centralize-shape-culling branch February 4, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Product improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Centralize shape culling display updates to reduce per-shape subscriptions

1 participant