diff --git a/packages/editor/package.json b/packages/editor/package.json index 6b5d134aa..4b28dea1d 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -59,7 +59,8 @@ "is-plain-object": "^5.0.0", "lodash.throttle": "^4.1.1", "lodash.uniq": "^4.5.0", - "nanoid": "4.0.2" + "nanoid": "4.0.2", + "rbush": "^3.0.1" }, "peerDependencies": { "react": "^18", @@ -72,6 +73,7 @@ "@types/benchmark": "^2.1.2", "@types/lodash.throttle": "^4.1.7", "@types/lodash.uniq": "^4.5.7", + "@types/rbush": "^3.0.3", "@types/react-test-renderer": "^18.0.0", "@types/wicg-file-system-access": "^2020.9.5", "benchmark": "^2.1.4", diff --git a/packages/editor/src/lib/editor/derivations/notVisibleShapes.ts b/packages/editor/src/lib/editor/derivations/notVisibleShapes.ts index 461835500..c63c4370c 100644 --- a/packages/editor/src/lib/editor/derivations/notVisibleShapes.ts +++ b/packages/editor/src/lib/editor/derivations/notVisibleShapes.ts @@ -1,5 +1,7 @@ import { RESET_VALUE, computed, isUninitialized } from '@tldraw/state' import { TLPageId, TLShapeId, isShape, isShapeId } from '@tldraw/tlschema' +import { measureCbDuration } from '@tldraw/utils' +import RBush from 'rbush' import { Box } from '../../primitives/Box' import { Editor } from '../Editor' @@ -12,6 +14,28 @@ function isShapeNotVisible(editor: Editor, id: TLShapeId, viewportPageBounds: Bo return !viewportPageBounds.includes(maskedPageBounds) } +type Element = { + minX: number + minY: number + maxX: number + maxY: number + id: TLShapeId +} + +function getElement(editor: Editor, id: TLShapeId): Element | null { + const bounds = editor.getShapeMaskedPageBounds(id) + if (!bounds) return null + return { + minX: bounds.minX, + minY: bounds.minY, + maxX: bounds.maxX, + maxY: bounds.maxY, + id, + } +} + +class TldrawRBush extends RBush {} + /** * Incremental derivation of not visible shapes. * Non visible shapes are shapes outside of the viewport page bounds and shapes outside of parent's clipping bounds. @@ -26,17 +50,24 @@ export const notVisibleShapes = (editor: Editor) => { let prevViewportPageBounds: Box function fromScratch(editor: Editor): Set { - const shapes = editor.getCurrentPageShapeIds() - lastPageId = editor.getCurrentPageId() - const viewportPageBounds = editor.getViewportPageBounds() - prevViewportPageBounds = viewportPageBounds.clone() - const notVisibleShapes = new Set() - shapes.forEach((id) => { - if (isShapeNotVisible(editor, id, viewportPageBounds)) { - notVisibleShapes.add(id) - } + return measureCbDuration('fromScratch rbush', () => { + const viewportPageBounds = editor.getViewportPageBounds() + prevViewportPageBounds = viewportPageBounds.clone() + const elementsToAdd: Element[] = [] + const shapes = editor.getCurrentPageShapeIds() + lastPageId = editor.getCurrentPageId() + + shapes.forEach((id) => { + const e = getElement(editor, id) + if (!e) return + elementsToAdd.push(e) + }) + const culled = new Set(shapes) + const rbush = new TldrawRBush().load(elementsToAdd) + rbush.search(viewportPageBounds).forEach((e) => culled.delete(e.id)) + console.log('culled', culled.size) + return culled }) - return notVisibleShapes } return computed>('getCulledShapes', (prevValue, lastComputedEpoch) => { if (!isCullingOffScreenShapes) return new Set() diff --git a/yarn.lock b/yarn.lock index 16e79c9db..77c6d82c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7488,6 +7488,7 @@ __metadata: "@types/core-js": "npm:^2.5.5" "@types/lodash.throttle": "npm:^4.1.7" "@types/lodash.uniq": "npm:^4.5.7" + "@types/rbush": "npm:^3.0.3" "@types/react-test-renderer": "npm:^18.0.0" "@types/wicg-file-system-access": "npm:^2020.9.5" "@use-gesture/react": "npm:^10.2.27" @@ -7504,6 +7505,7 @@ __metadata: lodash.throttle: "npm:^4.1.1" lodash.uniq: "npm:^4.5.0" nanoid: "npm:4.0.2" + rbush: "npm:^3.0.1" react-test-renderer: "npm:^18.2.0" resize-observer-polyfill: "npm:^1.5.1" peerDependencies: @@ -8330,6 +8332,13 @@ __metadata: languageName: node linkType: hard +"@types/rbush@npm:^3.0.3": + version: 3.0.3 + resolution: "@types/rbush@npm:3.0.3" + checksum: 59c75d20d3ebf95f8853a98f67d437adc047bf875df6e6bba90884fdfa8fa927402ccec762ecbc8724d98f9ed14c9e97d16eddb709a702021ce1874da5d0d8d7 + languageName: node + linkType: hard + "@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.2.18": version: 18.2.18 resolution: "@types/react-dom@npm:18.2.18" @@ -21060,6 +21069,13 @@ __metadata: languageName: node linkType: hard +"quickselect@npm:^2.0.0": + version: 2.0.0 + resolution: "quickselect@npm:2.0.0" + checksum: ed2e78431050d223fb75da20ee98011aef1a03f7cb04e1a32ee893402e640be3cfb76d72e9dbe01edf3bb457ff6a62e5c2d85748424d1aa531f6ba50daef098c + languageName: node + linkType: hard + "raf@npm:^3.4.1": version: 3.4.1 resolution: "raf@npm:3.4.1" @@ -21097,6 +21113,15 @@ __metadata: languageName: node linkType: hard +"rbush@npm:^3.0.1": + version: 3.0.1 + resolution: "rbush@npm:3.0.1" + dependencies: + quickselect: "npm:^2.0.0" + checksum: 489e2e7d9889888ad533518f194e3ab7cc19b1f1365a38ee99fbdda542a47f41cda7dc89870180050f4d04ea402e9ff294e1d767d03c0f1694e0028b7609eec9 + languageName: node + linkType: hard + "rc@npm:^1.2.7, rc@npm:^1.2.8, rc@npm:~1.2.7": version: 1.2.8 resolution: "rc@npm:1.2.8"