UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

8 lines (7 loc) • 27.6 kB
{ "version": 3, "sources": ["../../../../../src/lib/editor/managers/PerformanceManager/PerformanceManager.ts"], "sourcesContent": ["import type { TLRecord, TLShapeId } from '@tldraw/tlschema'\nimport { bind } from '@tldraw/utils'\nimport EventEmitter from 'eventemitter3'\nimport type { Editor } from '../../Editor'\nimport type {\n\tTLCameraEndPerfEvent,\n\tTLCameraStartPerfEvent,\n\tTLFramePerfEvent,\n\tTLInteractionEndPerfEvent,\n\tTLInteractionStartPerfEvent,\n\tTLPerfEventMap,\n\tTLPerfLongAnimationFrame,\n\tTLShapeOperationPerfEvent,\n\tTLUndoRedoPerfEvent,\n} from './perf-types'\n\nfunction percentile(sorted: number[], p: number): number {\n\tconst idx = Math.ceil(p * sorted.length) - 1\n\treturn sorted[Math.max(0, idx)]\n}\n\nfunction computeFrameTimeStats(frameTimes: number[]) {\n\tif (frameTimes.length === 0) return { avg: 0, median: 0, p95: 0, p99: 0, min: 0, max: 0 }\n\tconst sorted = [...frameTimes].sort((a, b) => a - b)\n\tconst sum = sorted.reduce((a, b) => a + b, 0)\n\treturn {\n\t\tavg: sum / sorted.length,\n\t\tmedian: percentile(sorted, 0.5),\n\t\tp95: percentile(sorted, 0.95),\n\t\tp99: percentile(sorted, 0.99),\n\t\tmin: sorted[0],\n\t\tmax: sorted[sorted.length - 1],\n\t}\n}\n\nfunction toLoafEntry(entry: PerformanceEntry): TLPerfLongAnimationFrame | null {\n\t// LoAF entries have these properties but TypeScript doesn't know about them yet\n\tconst e = entry as PerformanceEntry & {\n\t\tblockingDuration?: number\n\t\tscripts?: ReadonlyArray<{\n\t\t\tsourceURL?: string\n\t\t\tinvoker?: string\n\t\t\tduration?: number\n\t\t}>\n\t}\n\tif (typeof e.duration !== 'number') return null\n\treturn {\n\t\tstartTime: e.startTime,\n\t\tduration: e.duration,\n\t\tblockingDuration: e.blockingDuration ?? 0,\n\t\tscripts: (e.scripts ?? []).map((s) => ({\n\t\t\tsourceURL: s.sourceURL ?? '',\n\t\t\tinvoker: s.invoker ?? '',\n\t\t\tduration: s.duration ?? 0,\n\t\t})),\n\t}\n}\n\n/**\n * Manages performance event subscriptions for the editor. Available as `editor.performance`.\n *\n * Listeners are lazy \u2014 internal editor hooks (frame, shape events) are only attached while\n * at least one subscriber exists, so there is zero overhead when unused.\n *\n * @example\n * ```ts\n * const unsub = editor.performance.on('interaction-end', (event) => {\n * console.log(`${event.name}: ${event.fps.toFixed(1)} fps, p95=${event.p95FrameTime.toFixed(1)}ms`)\n * })\n * ```\n *\n * @public\n */\nexport class PerformanceManager {\n\t/** @internal */\n\treadonly emitter = new EventEmitter<TLPerfEventMap>()\n\n\tprivate editor: Editor\n\n\t// Active interaction tracking\n\tprivate activeInteraction: {\n\t\tname: string\n\t\tpath: string\n\t\tstartTime: number\n\t\tframeTimes: number[]\n\t\tselectedShapeTypes: Record<string, number>\n\t\tloafEntries: TLPerfLongAnimationFrame[]\n\t} | null = null\n\n\t// Active camera tracking\n\tprivate activeCamera: {\n\t\ttype: 'panning' | 'zooming'\n\t\tstartTime: number\n\t\tframeTimes: number[]\n\t\ttimeout: number | null\n\t\tloafEntries: TLPerfLongAnimationFrame[]\n\t} | null = null\n\n\t// Lazy listener cleanup functions\n\tprivate frameCleanup: (() => void) | null = null\n\tprivate shapeCreatedCleanup: (() => void) | null = null\n\tprivate shapeEditedCleanup: (() => void) | null = null\n\tprivate shapeDeletedCleanup: (() => void) | null = null\n\n\t// LoAF observer\n\tprivate loafObserver: PerformanceObserver | null = null\n\n\tconstructor(editor: Editor) {\n\t\tthis.editor = editor\n\t}\n\n\t/**\n\t * Subscribe to a performance event. Returns an unsubscribe function.\n\t *\n\t * @example\n\t * ```ts\n\t * const unsub = editor.performance.on('interaction-end', (event) => {\n\t * sendToAnalytics({ name: event.name, fps: event.fps, p95: event.p95FrameTime })\n\t * })\n\t * // later: unsub()\n\t * ```\n\t *\n\t * @public\n\t */\n\ton<K extends keyof TLPerfEventMap>(\n\t\tevent: K,\n\t\tfn: (...args: TLPerfEventMap[K]) => void\n\t): () => void {\n\t\tthis.emitter.on(event, fn as any)\n\t\tthis._maybeAttachLazyListeners(event)\n\t\treturn () => {\n\t\t\tthis.emitter.off(event, fn as any)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to a performance event once. The listener is removed after the first invocation.\n\t * Returns an unsubscribe function for early removal.\n\t *\n\t * @public\n\t */\n\tonce<K extends keyof TLPerfEventMap>(\n\t\tevent: K,\n\t\tfn: (...args: TLPerfEventMap[K]) => void\n\t): () => void {\n\t\tconst wrapped = (...args: TLPerfEventMap[K]) => {\n\t\t\t;(fn as any)(...args)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t\tthis.emitter.once(event, wrapped as any)\n\t\tthis._maybeAttachLazyListeners(event)\n\t\treturn () => {\n\t\t\tthis.emitter.off(event, wrapped as any)\n\t\t\tthis._maybeDetachLazyListeners(event)\n\t\t}\n\t}\n\n\t/** @internal */\n\tdispose() {\n\t\tif (this.activeCamera?.timeout) clearTimeout(this.activeCamera.timeout)\n\t\tthis.activeInteraction = null\n\t\tthis.activeCamera = null\n\t\tthis.frameCleanup?.()\n\t\tthis.frameCleanup = null\n\t\tthis.shapeCreatedCleanup?.()\n\t\tthis.shapeCreatedCleanup = null\n\t\tthis.shapeEditedCleanup?.()\n\t\tthis.shapeEditedCleanup = null\n\t\tthis.shapeDeletedCleanup?.()\n\t\tthis.shapeDeletedCleanup = null\n\t\tthis._stopLoafObserver()\n\t\tthis.emitter.removeAllListeners()\n\t}\n\n\t// --- Internal notification methods ---\n\n\t/** @internal */\n\t_notifyInteractionStart(name: string, path: string) {\n\t\tif (\n\t\t\tthis.emitter.listenerCount('interaction-start') === 0 &&\n\t\t\tthis.emitter.listenerCount('interaction-end') === 0\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tif (this.activeInteraction) {\n\t\t\tconsole.warn(\n\t\t\t\t`[tldraw] New interaction '${name}' started while '${this.activeInteraction.name}' was still active`\n\t\t\t)\n\t\t}\n\n\t\t// Capture selected shape types at start\n\t\tconst selectedShapeTypes: Record<string, number> = {}\n\t\tfor (const shape of this.editor.getSelectedShapes()) {\n\t\t\tselectedShapeTypes[shape.type] = (selectedShapeTypes[shape.type] || 0) + 1\n\t\t}\n\n\t\tthis.activeInteraction = {\n\t\t\tname,\n\t\t\tpath,\n\t\t\tstartTime: performance.now(),\n\t\t\tframeTimes: [],\n\t\t\tselectedShapeTypes,\n\t\t\tloafEntries: [],\n\t\t}\n\n\t\tconst event: TLInteractionStartPerfEvent = {\n\t\t\tname,\n\t\t\tpath,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('interaction-start', event)\n\t}\n\n\t/** @internal */\n\t_notifyInteractionEnd() {\n\t\tconst interaction = this.activeInteraction\n\t\tif (!interaction) return\n\t\tthis.activeInteraction = null\n\n\t\tif (this.emitter.listenerCount('interaction-end') === 0) return\n\n\t\tconst duration = performance.now() - interaction.startTime\n\t\tconst stats = computeFrameTimeStats(interaction.frameTimes)\n\n\t\tconst event: TLInteractionEndPerfEvent = {\n\t\t\tname: interaction.name,\n\t\t\tpath: interaction.path,\n\t\t\tduration,\n\t\t\tfps:\n\t\t\t\tinteraction.frameTimes.length > 0 ? (interaction.frameTimes.length / duration) * 1000 : 0,\n\t\t\tframeCount: interaction.frameTimes.length,\n\t\t\tavgFrameTime: stats.avg,\n\t\t\tmedianFrameTime: stats.median,\n\t\t\tp95FrameTime: stats.p95,\n\t\t\tp99FrameTime: stats.p99,\n\t\t\tminFrameTime: stats.min,\n\t\t\tmaxFrameTime: stats.max,\n\t\t\tframeTimes: interaction.frameTimes,\n\t\t\tshapeCount: this.editor.getCurrentPageShapeIds().size,\n\t\t\tselectedShapeTypes: interaction.selectedShapeTypes,\n\t\t\tlongAnimationFrames: interaction.loafEntries.length > 0 ? interaction.loafEntries : undefined,\n\t\t\tzoomLevel: this.editor.getCamera().z,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('interaction-end', event)\n\t}\n\n\t/** @internal */\n\t_notifyCameraOperation(type: 'panning' | 'zooming') {\n\t\tif (\n\t\t\tthis.emitter.listenerCount('camera-start') === 0 &&\n\t\t\tthis.emitter.listenerCount('camera-end') === 0\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tif (this.activeCamera) {\n\t\t\t// Extend existing camera session\n\t\t\tif (this.activeCamera.timeout) {\n\t\t\t\tclearTimeout(this.activeCamera.timeout)\n\t\t\t}\n\t\t\t// If type changed, end old and start new\n\t\t\tif (this.activeCamera.type !== type) {\n\t\t\t\tthis._endCameraSession()\n\t\t\t\tthis._startCameraSession(type)\n\t\t\t} else {\n\t\t\t\t// Reset timeout\n\t\t\t\tthis.activeCamera.timeout = this.editor.timers.setTimeout(\n\t\t\t\t\t() => this._endCameraSession(),\n\t\t\t\t\t50\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tthis._startCameraSession(type)\n\t\t}\n\t}\n\n\t/** @internal */\n\t_notifyUndoRedo(type: 'undo' | 'redo', undoDepth: number, redoDepth: number) {\n\t\tif (this.emitter.listenerCount(type) === 0) return\n\n\t\tconst event: TLUndoRedoPerfEvent = {\n\t\t\ttype,\n\t\t\tundoDepth,\n\t\t\tredoDepth,\n\t\t}\n\t\tthis.emitter.emit(type, event)\n\t}\n\n\t// --- Private helpers ---\n\n\tprivate _startCameraSession(type: 'panning' | 'zooming') {\n\t\tthis.activeCamera = {\n\t\t\ttype,\n\t\t\tstartTime: performance.now(),\n\t\t\tframeTimes: [],\n\t\t\ttimeout: this.editor.timers.setTimeout(() => this._endCameraSession(), 50),\n\t\t\tloafEntries: [],\n\t\t}\n\n\t\tif (this.emitter.listenerCount('camera-start') > 0) {\n\t\t\tconst event: TLCameraStartPerfEvent = {\n\t\t\t\ttype,\n\t\t\t\ttimestamp: performance.now(),\n\t\t\t}\n\t\t\tthis.emitter.emit('camera-start', event)\n\t\t}\n\t}\n\n\tprivate _endCameraSession() {\n\t\tconst camera = this.activeCamera\n\t\tif (!camera) return\n\t\tthis.activeCamera = null\n\t\tif (camera.timeout) clearTimeout(camera.timeout)\n\n\t\tif (this.emitter.listenerCount('camera-end') === 0) return\n\n\t\tconst duration = performance.now() - camera.startTime\n\t\tconst stats = computeFrameTimeStats(camera.frameTimes)\n\t\tconst viewportBounds = this.editor.getViewportScreenBounds()\n\t\tconst totalShapes = this.editor.getCurrentPageShapeIds().size\n\t\tconst culledShapeCount = this.editor.getCulledShapes().size\n\n\t\tconst event: TLCameraEndPerfEvent = {\n\t\t\ttype: camera.type,\n\t\t\tduration,\n\t\t\tfps: camera.frameTimes.length > 0 ? (camera.frameTimes.length / duration) * 1000 : 0,\n\t\t\tframeCount: camera.frameTimes.length,\n\t\t\tavgFrameTime: stats.avg,\n\t\t\tmedianFrameTime: stats.median,\n\t\t\tp95FrameTime: stats.p95,\n\t\t\tp99FrameTime: stats.p99,\n\t\t\tminFrameTime: stats.min,\n\t\t\tmaxFrameTime: stats.max,\n\t\t\tframeTimes: camera.frameTimes,\n\t\t\tshapeCount: totalShapes,\n\t\t\tviewportWidth: viewportBounds.w,\n\t\t\tviewportHeight: viewportBounds.h,\n\t\t\tlongAnimationFrames: camera.loafEntries.length > 0 ? camera.loafEntries : undefined,\n\t\t\tvisibleShapeCount: totalShapes - culledShapeCount,\n\t\t\tculledShapeCount,\n\t\t\tzoomLevel: this.editor.getCamera().z,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('camera-end', event)\n\t}\n\n\t@bind\n\tprivate _onFrame(elapsed: number) {\n\t\t// Record frame time for active interaction/camera\n\t\tif (this.activeInteraction) {\n\t\t\tthis.activeInteraction.frameTimes.push(elapsed)\n\t\t}\n\t\tif (this.activeCamera) {\n\t\t\tthis.activeCamera.frameTimes.push(elapsed)\n\t\t}\n\n\t\t// Emit standalone frame event if listeners exist\n\t\tif (this.emitter.listenerCount('frame') > 0) {\n\t\t\tconst totalShapes = this.editor.getCurrentPageShapeIds().size\n\t\t\tconst culledShapes = this.editor.getCulledShapes()\n\t\t\tconst culledCount = culledShapes.size\n\t\t\tconst event: TLFramePerfEvent = {\n\t\t\t\telapsed,\n\t\t\t\tshapeCount: totalShapes,\n\t\t\t\tculledShapeCount: culledCount,\n\t\t\t\tvisibleShapeCount: totalShapes - culledCount,\n\t\t\t}\n\t\t\tthis.emitter.emit('frame', event)\n\t\t}\n\t}\n\n\t@bind\n\tprivate _onShapesCreated(records: TLRecord[]) {\n\t\tif (this.emitter.listenerCount('shapes-created') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const record of records) {\n\t\t\tif (record.typeName === 'shape') {\n\t\t\t\tshapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)\n\t\tif (count === 0) return\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'create',\n\t\t\tcount,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-created', event)\n\t}\n\n\t@bind\n\tprivate _onShapesEdited(records: TLRecord[]) {\n\t\tif (this.emitter.listenerCount('shapes-updated') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const record of records) {\n\t\t\tif (record.typeName === 'shape') {\n\t\t\t\tshapeTypes[record.type] = (shapeTypes[record.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst count = Object.values(shapeTypes).reduce((a, b) => a + b, 0)\n\t\tif (count === 0) return\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'update',\n\t\t\tcount,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-updated', event)\n\t}\n\n\t@bind\n\tprivate _onShapesDeleted(ids: TLShapeId[]) {\n\t\tif (this.emitter.listenerCount('shapes-deleted') === 0) return\n\t\tconst shapeTypes: Record<string, number> = {}\n\t\tfor (const id of ids) {\n\t\t\t// Works because 'deleted-shapes' fires before store.remove() in Editor.deleteShapes\n\t\t\tconst shape = this.editor.getShape(id)\n\t\t\tif (shape) {\n\t\t\t\tshapeTypes[shape.type] = (shapeTypes[shape.type] || 0) + 1\n\t\t\t}\n\t\t}\n\t\tconst event: TLShapeOperationPerfEvent = {\n\t\t\toperation: 'delete',\n\t\t\tcount: ids.length,\n\t\t\tshapeTypes,\n\t\t\ttimestamp: performance.now(),\n\t\t}\n\t\tthis.emitter.emit('shapes-deleted', event)\n\t}\n\n\t// --- LoAF observer ---\n\n\tprivate _startLoafObserver() {\n\t\tif (typeof PerformanceObserver === 'undefined') return\n\n\t\ttry {\n\t\t\tconst supported = PerformanceObserver.supportedEntryTypes\n\t\t\tif (!supported?.includes('long-animation-frame')) return\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\n\t\tthis.loafObserver = new PerformanceObserver((list) => {\n\t\t\tconst isInteractionActive = this.activeInteraction !== null\n\t\t\tconst isCameraActive = this.activeCamera !== null\n\n\t\t\tif (!isInteractionActive && !isCameraActive) return\n\n\t\t\tfor (const entry of list.getEntries()) {\n\t\t\t\tconst loaf = toLoafEntry(entry)\n\t\t\t\tif (!loaf) continue\n\n\t\t\t\tif (isInteractionActive) {\n\t\t\t\t\tthis.activeInteraction!.loafEntries.push(loaf)\n\t\t\t\t}\n\t\t\t\tif (isCameraActive) {\n\t\t\t\t\tthis.activeCamera!.loafEntries.push(loaf)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tthis.loafObserver.observe({ type: 'long-animation-frame', buffered: false })\n\t}\n\n\tprivate _stopLoafObserver() {\n\t\tif (this.loafObserver) {\n\t\t\tthis.loafObserver.disconnect()\n\t\t\tthis.loafObserver = null\n\t\t}\n\t}\n\n\t// --- Lazy listener management ---\n\n\tprivate _needsFrameListener(): boolean {\n\t\treturn (\n\t\t\tthis.emitter.listenerCount('frame') > 0 ||\n\t\t\tthis.emitter.listenerCount('interaction-start') > 0 ||\n\t\t\tthis.emitter.listenerCount('interaction-end') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-start') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-end') > 0\n\t\t)\n\t}\n\n\tprivate _needsLoafObserver(): boolean {\n\t\treturn (\n\t\t\tthis.emitter.listenerCount('interaction-end') > 0 ||\n\t\t\tthis.emitter.listenerCount('camera-end') > 0\n\t\t)\n\t}\n\n\tprivate _maybeAttachLazyListeners(event: keyof TLPerfEventMap) {\n\t\t// Frame listener needed for frame event + interaction/camera frame time tracking\n\t\tif (\n\t\t\t!this.frameCleanup &&\n\t\t\t(event === 'frame' ||\n\t\t\t\tevent === 'interaction-start' ||\n\t\t\t\tevent === 'interaction-end' ||\n\t\t\t\tevent === 'camera-start' ||\n\t\t\t\tevent === 'camera-end')\n\t\t) {\n\t\t\tif (this._needsFrameListener()) {\n\t\t\t\tthis.editor.on('frame', this._onFrame)\n\t\t\t\tthis.frameCleanup = () => this.editor.off('frame', this._onFrame)\n\t\t\t}\n\t\t}\n\n\t\t// LoAF observer needed when interaction-end or camera-end listeners exist\n\t\tif (!this.loafObserver && (event === 'interaction-end' || event === 'camera-end')) {\n\t\t\tif (this._needsLoafObserver()) {\n\t\t\t\tthis._startLoafObserver()\n\t\t\t}\n\t\t}\n\n\t\tif (!this.shapeCreatedCleanup && event === 'shapes-created') {\n\t\t\tthis.editor.on('created-shapes', this._onShapesCreated)\n\t\t\tthis.shapeCreatedCleanup = () => this.editor.off('created-shapes', this._onShapesCreated)\n\t\t}\n\n\t\tif (!this.shapeEditedCleanup && event === 'shapes-updated') {\n\t\t\tthis.editor.on('edited-shapes', this._onShapesEdited)\n\t\t\tthis.shapeEditedCleanup = () => this.editor.off('edited-shapes', this._onShapesEdited)\n\t\t}\n\n\t\tif (!this.shapeDeletedCleanup && event === 'shapes-deleted') {\n\t\t\tthis.editor.on('deleted-shapes', this._onShapesDeleted)\n\t\t\tthis.shapeDeletedCleanup = () => this.editor.off('deleted-shapes', this._onShapesDeleted)\n\t\t}\n\t}\n\n\tprivate _maybeDetachLazyListeners(event: keyof TLPerfEventMap) {\n\t\tif (\n\t\t\tthis.frameCleanup &&\n\t\t\t(event === 'frame' ||\n\t\t\t\tevent === 'interaction-start' ||\n\t\t\t\tevent === 'interaction-end' ||\n\t\t\t\tevent === 'camera-start' ||\n\t\t\t\tevent === 'camera-end')\n\t\t) {\n\t\t\tif (!this._needsFrameListener()) {\n\t\t\t\tthis.frameCleanup()\n\t\t\t\tthis.frameCleanup = null\n\t\t\t}\n\t\t}\n\n\t\t// Stop LoAF observer when no longer needed\n\t\tif (this.loafObserver && (event === 'interaction-end' || event === 'camera-end')) {\n\t\t\tif (!this._needsLoafObserver()) {\n\t\t\t\tthis._stopLoafObserver()\n\t\t\t}\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeCreatedCleanup &&\n\t\t\tevent === 'shapes-created' &&\n\t\t\tthis.emitter.listenerCount('shapes-created') === 0\n\t\t) {\n\t\t\tthis.shapeCreatedCleanup()\n\t\t\tthis.shapeCreatedCleanup = null\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeEditedCleanup &&\n\t\t\tevent === 'shapes-updated' &&\n\t\t\tthis.emitter.listenerCount('shapes-updated') === 0\n\t\t) {\n\t\t\tthis.shapeEditedCleanup()\n\t\t\tthis.shapeEditedCleanup = null\n\t\t}\n\n\t\tif (\n\t\t\tthis.shapeDeletedCleanup &&\n\t\t\tevent === 'shapes-deleted' &&\n\t\t\tthis.emitter.listenerCount('shapes-deleted') === 0\n\t\t) {\n\t\t\tthis.shapeDeletedCleanup()\n\t\t\tthis.shapeDeletedCleanup = null\n\t\t}\n\t}\n}\n"], "mappings": "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", "names": [] }