kopia lustrzana https://github.com/Tldraw/Tldraw
skip-culled
rodzic
78e1009358
commit
5bd495670d
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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).
|
||||
*
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
Ładowanie…
Reference in New Issue