Steve Ruiz 2024-04-09 11:02:53 +01:00
rodzic 78e1009358
commit 5bd495670d
6 zmienionych plików z 145 dodań i 162 usunięć

Wyświetl plik

@ -676,7 +676,7 @@ export class Editor extends EventEmitter<TLEventMap> {
getCrashingError(): unknown;
getCroppingShapeId(): null | TLShapeId;
// (undocumented)
getCulledShapes(): Map<TLShapeId, Box | undefined>;
getCulledShapes(): Set<TLShapeId>;
getCurrentPage(): TLPage;
getCurrentPageBounds(): Box | undefined;
getCurrentPageId(): TLPageId;

Wyświetl plik

@ -10295,8 +10295,8 @@
},
{
"kind": "Reference",
"text": "Map",
"canonicalReference": "!Map:interface"
"text": "Set",
"canonicalReference": "!Set:interface"
},
{
"kind": "Content",
@ -10309,16 +10309,7 @@
},
{
"kind": "Content",
"text": ", "
},
{
"kind": "Reference",
"text": "Box",
"canonicalReference": "@tldraw/editor!Box:class"
},
{
"kind": "Content",
"text": " | undefined>"
"text": ">"
},
{
"kind": "Content",
@ -10328,7 +10319,7 @@
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 7
"endIndex": 5
},
"releaseTag": "Public",
"isProtected": false,

Wyświetl plik

@ -1,5 +1,4 @@
import { computed, react } from '@tldraw/state'
import { measureCbDuration } from '@tldraw/utils'
import { useEffect, useRef } from 'react'
import { useEditor } from '../hooks/useEditor'
import { useIsDarkMode } from '../hooks/useIsDarkMode'
@ -111,32 +110,32 @@ export function CulledShapes() {
} = webGl
const shapeVertices = computed('shape vertices', function calculateCulledShapeVertices() {
const results: number[] = []
return []
// const results: number[] = []
// return measureCbDuration('vertices', () => {
// editor.getCulledShapes().forEach((maskedPageBounds) => {
// if (maskedPageBounds) {
// results.push(
// // triangle 1
// maskedPageBounds.minX,
// maskedPageBounds.minY,
// maskedPageBounds.minX,
// maskedPageBounds.maxY,
// maskedPageBounds.maxX,
// maskedPageBounds.maxY,
// // triangle 2
// maskedPageBounds.minX,
// maskedPageBounds.minY,
// maskedPageBounds.maxX,
// maskedPageBounds.minY,
// maskedPageBounds.maxX,
// maskedPageBounds.maxY
// )
// }
// })
return measureCbDuration('vertices', () => {
editor.getCulledShapes().forEach((maskedPageBounds) => {
if (maskedPageBounds) {
results.push(
// triangle 1
maskedPageBounds.minX,
maskedPageBounds.minY,
maskedPageBounds.minX,
maskedPageBounds.maxY,
maskedPageBounds.maxX,
maskedPageBounds.maxY,
// triangle 2
maskedPageBounds.minX,
maskedPageBounds.minY,
maskedPageBounds.maxX,
maskedPageBounds.minY,
maskedPageBounds.maxX,
maskedPageBounds.maxY
)
}
})
return results
})
// return results
// })
})
return react('render culled shapes ', function renderCulledShapes() {

Wyświetl plik

@ -20,7 +20,6 @@ import { toDomPrecision } from '../../primitives/utils'
import { debugFlags } from '../../utils/debug-flags'
import { setStyleProperty } from '../../utils/dom'
import { nearestMultiple } from '../../utils/nearestMultiple'
import { CulledShapes } from '../CulledShapes'
import { GeometryDebuggingView } from '../GeometryDebuggingView'
import { LiveCollaborators } from '../LiveCollaborators'
import { Shape } from '../Shape'
@ -97,9 +96,9 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
<Background />
</div>
)}
<div className="tl-culled-shapes">
{/* <div className="tl-culled-shapes">
<CulledShapes />
</div>
</div> */}
<div
ref={rCanvas}
draggable={false}

Wyświetl plik

@ -1,4 +1,4 @@
import { EMPTY_ARRAY, RESET_VALUE, atom, computed, isUninitialized, transact } from '@tldraw/state'
import { EMPTY_ARRAY, atom, computed, transact } from '@tldraw/state'
import { ComputedCache, RecordType, StoreSnapshot } from '@tldraw/store'
import {
CameraRecordType,
@ -102,6 +102,7 @@ import { getReorderingShapesChanges } from '../utils/reorderShapes'
import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
import { uniqueId } from '../utils/uniqueId'
import { arrowBindingsIndex } from './derivations/arrowBindingsIndex'
import { culledShapes } from './derivations/culledShapes'
import { parentsToChildren } from './derivations/parentsToChildren'
import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage'
import { getSvgJsx } from './getSvgJsx'
@ -612,6 +613,7 @@ export class Editor extends EventEmitter<TLEventMap> {
this.getCurrentPageId()
)
this._parentIdsToChildIds = parentsToChildren(this.store)
this._culledShapes = culledShapes(this)
this.disposables.add(
this.store.listen((changes) => {
@ -4247,127 +4249,18 @@ export class Editor extends EventEmitter<TLEventMap> {
return this.isShapeOrAncestorLocked(this.getShapeParent(shape))
}
private _getShapeCullingInfo(
id: TLShapeId,
selectedShapeIds: TLShapeId[],
editingId: TLShapeId | null,
renderingBoundsExpanded: Box
): { isCulled: false } | { isCulled: true; maskedPageBounds: Box | undefined } {
if (editingId === id) return { isCulled: false }
const maskedPageBounds = this.getShapeMaskedPageBounds(id)
// if the shape is fully outside of its parent's clipping bounds...
if (maskedPageBounds === undefined) return { isCulled: true, maskedPageBounds: undefined }
// We don't cull selected shapes
if (selectedShapeIds.includes(id)) return { isCulled: false }
// the shape is outside of the expanded viewport bounds...
const isCulled = !renderingBoundsExpanded.includes(maskedPageBounds)
return isCulled ? { isCulled, maskedPageBounds } : { isCulled }
}
getCulledShapes(): Map<TLShapeId, Box | undefined> {
return this._getCulledShapes().get()
}
@computed
private _getCulledShapes() {
const isCullingOffScreenShapes = Number.isFinite(this.renderingBoundsMargin)
const shapeHistory = this.store.query.filterHistory('shape')
let lastPageId: TLPageId | null = null
let prevRenderingBoundsExpanded: Box
function fromScratch(editor: Editor): Map<TLShapeId, Box | undefined> {
const renderingBoundsExpanded = editor.getRenderingBoundsExpanded()
prevRenderingBoundsExpanded = renderingBoundsExpanded.clone()
lastPageId = editor.getCurrentPageId()
const shapes = editor.getCurrentPageShapeIds()
const culledShapes = new Map<TLShapeId, Box | undefined>()
const selectedShapeIds = editor.getSelectedShapeIds()
const editingId = editor.getEditingShapeId()
shapes.forEach((id) => {
const ci = editor._getShapeCullingInfo(
id,
selectedShapeIds,
editingId,
renderingBoundsExpanded
)
if (ci.isCulled) {
culledShapes.set(id, ci.maskedPageBounds)
}
})
return culledShapes
}
return computed<Map<TLShapeId, Box | undefined>>(
'getCulledShapes',
(prevValue, lastComputedEpoch) => {
if (!isCullingOffScreenShapes) return new Map<TLShapeId, Box | undefined>()
if (isUninitialized(prevValue)) {
return fromScratch(this)
}
const diff = shapeHistory.getDiffSince(lastComputedEpoch)
if (diff === RESET_VALUE) {
return fromScratch(this)
}
const currentPageId = this.getCurrentPageId()
if (lastPageId !== currentPageId) {
return fromScratch(this)
}
const renderingBoundsExpanded = this.getRenderingBoundsExpanded()
if (
!prevRenderingBoundsExpanded ||
!renderingBoundsExpanded.equals(prevRenderingBoundsExpanded)
) {
return fromScratch(this)
}
const selectedShapeIds = this.getSelectedShapeIds()
const editingId = this.getEditingShapeId()
const nextValue = new Map(prevValue)
let isDirty = false
const checkShapeCullingInfo = (id: TLShapeId) => {
const ci = this._getShapeCullingInfo(
id,
selectedShapeIds,
editingId,
renderingBoundsExpanded
)
if (ci.isCulled && !prevValue.has(id)) {
nextValue.set(id, ci.maskedPageBounds)
isDirty = true
}
}
for (const changes of diff) {
for (const record of Object.values(changes.added)) {
if (isShape(record)) {
checkShapeCullingInfo(record.id)
}
}
for (const [_from, to] of Object.values(changes.updated)) {
if (isShape(to)) {
checkShapeCullingInfo(to.id)
}
}
for (const id of Object.keys(changes.removed)) {
if (isShapeId(id)) {
const hasBeenDeleted = nextValue.delete(id)
if (hasBeenDeleted) {
isDirty = true
}
}
}
}
return isDirty ? nextValue : prevValue
}
)
getCulledShapes(): Set<TLShapeId> {
return this._culledShapes.get()
}
/**
* A cache of parents to children.
*
* @internal
*/
private readonly _culledShapes: ReturnType<typeof culledShapes>
/**
* The bounds of the current page (the common bounds of all of the shapes on the page).
*

Wyświetl plik

@ -0,0 +1,101 @@
import { computed, isUninitialized, RESET_VALUE } from '@tldraw/state'
import { isShape, isShapeId, TLPageId, TLShapeId } from '@tldraw/tlschema'
import { Box } from '../../primitives/Box'
import { Editor } from '../Editor'
export function culledShapes(editor: Editor) {
function getShapeCullingInfo(
id: TLShapeId,
selectedShapeIds: TLShapeId[],
editingId: TLShapeId | null,
viewportPageBounds: Box
): boolean {
if (editingId === id) return false
const maskedPageBounds = editor.getShapeMaskedPageBounds(id)
// if the shape is fully outside of its parent's clipping bounds...
if (maskedPageBounds === undefined) return true
// We don't cull selected shapes
if (selectedShapeIds.includes(id)) return false
// the shape is outside of the expanded viewport bounds...
return !viewportPageBounds.includes(maskedPageBounds)
}
const isCullingOffScreenShapes = Number.isFinite(editor.renderingBoundsMargin)
const shapeHistory = editor.store.query.filterHistory('shape')
let lastPageId: TLPageId | null = null
let prevBounds: Box
function fromScratch(editor: Editor): Set<TLShapeId> {
const bounds = editor.getViewportPageBounds()
prevBounds = bounds.clone()
lastPageId = editor.getCurrentPageId()
const shapes = editor.getCurrentPageShapeIds()
const culledShapes = new Set<TLShapeId>()
const selectedShapeIds = editor.getSelectedShapeIds()
const editingId = editor.getEditingShapeId()
shapes.forEach((id) => {
if (getShapeCullingInfo(id, selectedShapeIds, editingId, bounds)) {
culledShapes.add(id)
}
})
return culledShapes
}
return computed<Set<TLShapeId>>('getCulledShapes', (prevValue, lastComputedEpoch) => {
if (!isCullingOffScreenShapes) return new Set<TLShapeId>()
if (isUninitialized(prevValue)) {
return fromScratch(editor)
}
const diff = shapeHistory.getDiffSince(lastComputedEpoch)
if (diff === RESET_VALUE) {
return fromScratch(editor)
}
const currentPageId = editor.getCurrentPageId()
if (lastPageId !== currentPageId) {
return fromScratch(editor)
}
const renderingBoundsExpanded = editor.getViewportPageBounds()
if (!prevBounds || !renderingBoundsExpanded.equals(prevBounds)) {
return fromScratch(editor)
}
const selectedShapeIds = editor.getSelectedShapeIds()
const editingId = editor.getEditingShapeId()
const nextValue = new Set(prevValue)
let isDirty = false
const checkShapeCullingInfo = (id: TLShapeId) => {
if (
getShapeCullingInfo(id, selectedShapeIds, editingId, renderingBoundsExpanded) &&
!prevValue.has(id)
) {
nextValue.add(id)
isDirty = true
}
}
for (const changes of diff) {
for (const record of Object.values(changes.added)) {
if (isShape(record)) {
checkShapeCullingInfo(record.id)
}
}
for (const [_from, to] of Object.values(changes.updated)) {
if (isShape(to)) {
checkShapeCullingInfo(to.id)
}
}
for (const id of Object.keys(changes.removed)) {
if (isShapeId(id)) {
if (nextValue.delete(id)) {
isDirty = true
}
}
}
}
return isDirty ? nextValue : prevValue
})
}