kopia lustrzana https://github.com/Tldraw/Tldraw
Merge da76b5c98c
into cce794e04b
commit
4f50d878b8
|
@ -0,0 +1,23 @@
|
|||
import { TLUiEventSource, TLUiOverrides, debugFlags, measureCbDuration, useValue } from 'tldraw'
|
||||
|
||||
export function usePerformance(): TLUiOverrides {
|
||||
const measurePerformance = useValue(
|
||||
'measurePerformance',
|
||||
() => debugFlags.measurePerformance.get(),
|
||||
[debugFlags]
|
||||
)
|
||||
if (!measurePerformance) return {}
|
||||
return {
|
||||
actions(_editor, actions) {
|
||||
Object.keys(actions).forEach((key) => {
|
||||
const action = actions[key]
|
||||
const cb = action.onSelect
|
||||
action.onSelect = (source: TLUiEventSource) => {
|
||||
return measureCbDuration(`Action ${key}`, () => cb(source))
|
||||
}
|
||||
})
|
||||
|
||||
return actions
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
import { Tldraw } from 'tldraw'
|
||||
import 'tldraw/tldraw.css'
|
||||
import { usePerformance } from '../hooks/usePerformance'
|
||||
|
||||
export default function Develop() {
|
||||
const performanceOverrides = usePerformance()
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
overrides={[performanceOverrides]}
|
||||
persistenceKey="tldraw_example"
|
||||
onMount={(editor) => {
|
||||
;(window as any).app = editor
|
||||
|
|
|
@ -23,6 +23,7 @@ import { JSX as JSX_2 } from 'react/jsx-runtime';
|
|||
import { LegacyMigrations } from '@tldraw/store';
|
||||
import { MigrationSequence } from '@tldraw/store';
|
||||
import { NamedExoticComponent } from 'react';
|
||||
import { PerformanceTracker } from '@tldraw/utils';
|
||||
import { PointerEventHandler } from 'react';
|
||||
import { react } from '@tldraw/state';
|
||||
import { default as React_2 } from 'react';
|
||||
|
@ -448,6 +449,7 @@ export const debugFlags: {
|
|||
readonly logElementRemoves: DebugFlag<boolean>;
|
||||
readonly logPointerCaptures: DebugFlag<boolean>;
|
||||
readonly logPreventDefaults: DebugFlag<boolean>;
|
||||
readonly measurePerformance: DebugFlag<boolean>;
|
||||
readonly reconnectOnPing: DebugFlag<boolean>;
|
||||
readonly showFps: DebugFlag<boolean>;
|
||||
readonly throwToBlob: DebugFlag<boolean>;
|
||||
|
@ -1868,6 +1870,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|||
// (undocumented)
|
||||
_path: Computed<string>;
|
||||
// (undocumented)
|
||||
performanceTracker: PerformanceTracker;
|
||||
// (undocumented)
|
||||
setCurrentToolIdMask(id: string | undefined): void;
|
||||
// (undocumented)
|
||||
shapeType?: string;
|
||||
|
|
|
@ -35454,6 +35454,37 @@
|
|||
"isProtected": false,
|
||||
"isAbstract": false
|
||||
},
|
||||
{
|
||||
"kind": "Property",
|
||||
"canonicalReference": "@tldraw/editor!StateNode#performanceTracker:member",
|
||||
"docComment": "",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "performanceTracker: "
|
||||
},
|
||||
{
|
||||
"kind": "Reference",
|
||||
"text": "PerformanceTracker",
|
||||
"canonicalReference": "@tldraw/utils!PerformanceTracker:class"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isReadonly": false,
|
||||
"isOptional": false,
|
||||
"releaseTag": "Public",
|
||||
"name": "performanceTracker",
|
||||
"propertyTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"isStatic": false,
|
||||
"isProtected": false,
|
||||
"isAbstract": false
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/editor!StateNode#setCurrentToolIdMask:member(1)",
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
import {
|
||||
IndexKey,
|
||||
JsonObject,
|
||||
PerformanceTracker,
|
||||
annotateError,
|
||||
assert,
|
||||
compact,
|
||||
|
@ -95,6 +96,7 @@ import { PI2, approximately, areAnglesCompatible, clamp, pointInPolygon } from '
|
|||
import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
|
||||
import { WeakMapCache } from '../utils/WeakMapCache'
|
||||
import { dataUrlToFile } from '../utils/assets'
|
||||
import { debugFlags } from '../utils/debug-flags'
|
||||
import { getIncrementedName } from '../utils/getIncrementedName'
|
||||
import { getReorderingShapesChanges } from '../utils/reorderShapes'
|
||||
import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
|
||||
|
@ -624,6 +626,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
requestAnimationFrame(() => {
|
||||
this._tickManager.start()
|
||||
})
|
||||
|
||||
this.performanceTracker = new PerformanceTracker()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8367,6 +8371,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
/** @internal */
|
||||
capturedPointerId: number | null = null
|
||||
|
||||
/** @internal */
|
||||
private readonly performanceTracker: PerformanceTracker
|
||||
|
||||
/** @internal */
|
||||
private performanceTrackerTimeout = -1 as any
|
||||
|
||||
/**
|
||||
* Dispatch an event to the editor.
|
||||
*
|
||||
|
@ -8558,6 +8568,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
this.stopFollowingUser()
|
||||
}
|
||||
if (inputs.ctrlKey) {
|
||||
if (debugFlags.measurePerformance.get()) {
|
||||
if (this.performanceTracker.isStarted()) {
|
||||
clearTimeout(this.performanceTrackerTimeout)
|
||||
} else {
|
||||
this.performanceTracker.start('Zooming')
|
||||
}
|
||||
this.performanceTrackerTimeout = setTimeout(() => {
|
||||
this.performanceTracker.stop()
|
||||
}, 50)
|
||||
}
|
||||
// todo: Start or update the zoom end interval
|
||||
|
||||
// If the alt or ctrl keys are pressed,
|
||||
|
@ -8584,7 +8604,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// statechart should respond to this event (a camera zoom)
|
||||
return
|
||||
}
|
||||
|
||||
// Update the camera here, which will dispatch a pointer move...
|
||||
// this will also update the pointer position, etc
|
||||
const { x: cx, y: cy, z: cz } = this.getCamera()
|
||||
|
@ -8674,6 +8693,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
if (this.inputs.isPanning && this.inputs.isPointing) {
|
||||
if (debugFlags.measurePerformance.get()) {
|
||||
if (this.performanceTracker.isStarted()) {
|
||||
clearTimeout(this.performanceTrackerTimeout)
|
||||
} else {
|
||||
this.performanceTracker.start('Panning')
|
||||
}
|
||||
this.performanceTrackerTimeout = setTimeout(() => {
|
||||
this.performanceTracker.stop()
|
||||
}, 50)
|
||||
}
|
||||
clearTimeout(this._longPressTimeout)
|
||||
// Handle panning
|
||||
const { currentScreenPoint, previousScreenPoint } = this.inputs
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { Atom, Computed, atom, computed } from '@tldraw/state'
|
||||
import { PerformanceTracker } from '@tldraw/utils'
|
||||
import { debugFlags } from '../../utils/debug-flags'
|
||||
import type { Editor } from '../Editor'
|
||||
import {
|
||||
EVENT_NAME_MAP,
|
||||
|
@ -10,6 +12,19 @@ import {
|
|||
} from '../types/event-types'
|
||||
|
||||
type TLStateNodeType = 'branch' | 'leaf' | 'root'
|
||||
const STATE_NODES_TO_MEASURE = [
|
||||
'brushing',
|
||||
'cropping',
|
||||
'dragging',
|
||||
'dragging_handle',
|
||||
'drawing',
|
||||
'erasing',
|
||||
'lasering',
|
||||
'resizing',
|
||||
'rotating',
|
||||
'scribble_brushing',
|
||||
'translating',
|
||||
]
|
||||
|
||||
/** @public */
|
||||
export interface TLStateNodeConstructor {
|
||||
|
@ -21,6 +36,7 @@ export interface TLStateNodeConstructor {
|
|||
|
||||
/** @public */
|
||||
export abstract class StateNode implements Partial<TLEventHandlers> {
|
||||
performanceTracker: PerformanceTracker
|
||||
constructor(
|
||||
public editor: Editor,
|
||||
parent?: StateNode
|
||||
|
@ -60,6 +76,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|||
this._current.set(this.children[this.initial])
|
||||
}
|
||||
}
|
||||
this.performanceTracker = new PerformanceTracker()
|
||||
}
|
||||
|
||||
static id: string
|
||||
|
@ -159,6 +176,10 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|||
|
||||
// todo: move this logic into transition
|
||||
enter = (info: any, from: string) => {
|
||||
if (debugFlags.measurePerformance.get() && STATE_NODES_TO_MEASURE.includes(this.id)) {
|
||||
this.performanceTracker.start(this.id)
|
||||
}
|
||||
|
||||
this._isActive.set(true)
|
||||
this.onEnter?.(info, from)
|
||||
|
||||
|
@ -171,6 +192,9 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|||
|
||||
// todo: move this logic into transition
|
||||
exit = (info: any, from: string) => {
|
||||
if (debugFlags.measurePerformance.get() && this.performanceTracker.isStarted()) {
|
||||
this.performanceTracker.stop()
|
||||
}
|
||||
this._isActive.set(false)
|
||||
this.onExit?.(info, from)
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ export const debugFlags = {
|
|||
showFps: createDebugValue('showFps', {
|
||||
defaults: { all: false },
|
||||
}),
|
||||
measurePerformance: createDebugValue('measurePerformance', { defaults: { all: false } }),
|
||||
throwToBlob: createDebugValue('throwToBlob', {
|
||||
defaults: { all: false },
|
||||
}),
|
||||
|
|
|
@ -242,6 +242,18 @@ export function omitFromStackTrace<Args extends Array<unknown>, Return>(fn: (...
|
|||
// @internal
|
||||
export function partition<T>(arr: T[], predicate: (item: T) => boolean): [T[], T[]];
|
||||
|
||||
// @public (undocumented)
|
||||
export class PerformanceTracker {
|
||||
// (undocumented)
|
||||
isStarted(): boolean;
|
||||
// (undocumented)
|
||||
recordFrame: () => void;
|
||||
// (undocumented)
|
||||
start(name: string): void;
|
||||
// (undocumented)
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class PngHelpers {
|
||||
// (undocumented)
|
||||
|
|
|
@ -2353,6 +2353,165 @@
|
|||
"endIndex": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "Class",
|
||||
"canonicalReference": "@tldraw/utils!PerformanceTracker:class",
|
||||
"docComment": "/**\n * @public\n */\n",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "export declare class PerformanceTracker "
|
||||
}
|
||||
],
|
||||
"fileUrlPath": "packages/utils/src/lib/PerformanceTracker.ts",
|
||||
"releaseTag": "Public",
|
||||
"isAbstract": false,
|
||||
"name": "PerformanceTracker",
|
||||
"preserveMemberOrder": false,
|
||||
"members": [
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/utils!PerformanceTracker#isStarted:member(1)",
|
||||
"docComment": "",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "isStarted(): "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "boolean"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isStatic": false,
|
||||
"returnTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"releaseTag": "Public",
|
||||
"isProtected": false,
|
||||
"overloadIndex": 1,
|
||||
"parameters": [],
|
||||
"isOptional": false,
|
||||
"isAbstract": false,
|
||||
"name": "isStarted"
|
||||
},
|
||||
{
|
||||
"kind": "Property",
|
||||
"canonicalReference": "@tldraw/utils!PerformanceTracker#recordFrame:member",
|
||||
"docComment": "",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "recordFrame: "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "() => void"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isReadonly": false,
|
||||
"isOptional": false,
|
||||
"releaseTag": "Public",
|
||||
"name": "recordFrame",
|
||||
"propertyTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"isStatic": false,
|
||||
"isProtected": false,
|
||||
"isAbstract": false
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/utils!PerformanceTracker#start:member(1)",
|
||||
"docComment": "",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "start(name: "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "string"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "): "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "void"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isStatic": false,
|
||||
"returnTypeTokenRange": {
|
||||
"startIndex": 3,
|
||||
"endIndex": 4
|
||||
},
|
||||
"releaseTag": "Public",
|
||||
"isProtected": false,
|
||||
"overloadIndex": 1,
|
||||
"parameters": [
|
||||
{
|
||||
"parameterName": "name",
|
||||
"parameterTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"isOptional": false
|
||||
}
|
||||
],
|
||||
"isOptional": false,
|
||||
"isAbstract": false,
|
||||
"name": "start"
|
||||
},
|
||||
{
|
||||
"kind": "Method",
|
||||
"canonicalReference": "@tldraw/utils!PerformanceTracker#stop:member(1)",
|
||||
"docComment": "",
|
||||
"excerptTokens": [
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "stop(): "
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": "void"
|
||||
},
|
||||
{
|
||||
"kind": "Content",
|
||||
"text": ";"
|
||||
}
|
||||
],
|
||||
"isStatic": false,
|
||||
"returnTypeTokenRange": {
|
||||
"startIndex": 1,
|
||||
"endIndex": 2
|
||||
},
|
||||
"releaseTag": "Public",
|
||||
"isProtected": false,
|
||||
"overloadIndex": 1,
|
||||
"parameters": [],
|
||||
"isOptional": false,
|
||||
"isAbstract": false,
|
||||
"name": "stop"
|
||||
}
|
||||
],
|
||||
"implementsTokenRanges": []
|
||||
},
|
||||
{
|
||||
"kind": "Class",
|
||||
"canonicalReference": "@tldraw/utils!PngHelpers:class",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export { PerformanceTracker } from './lib/PerformanceTracker'
|
||||
export {
|
||||
areArraysShallowEqual,
|
||||
compact,
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import { PERFORMANCE_COLORS, PERFORMANCE_PREFIX_COLOR } from './perf'
|
||||
|
||||
/** @public */
|
||||
export class PerformanceTracker {
|
||||
private startTime = 0
|
||||
private name = ''
|
||||
private frames = 0
|
||||
private started = false
|
||||
private frame: number | null = null
|
||||
|
||||
recordFrame = () => {
|
||||
this.frames++
|
||||
if (!this.started) return
|
||||
this.frame = requestAnimationFrame(this.recordFrame)
|
||||
}
|
||||
|
||||
start(name: string) {
|
||||
this.name = name
|
||||
this.frames = 0
|
||||
this.started = true
|
||||
if (this.frame !== null) cancelAnimationFrame(this.frame)
|
||||
this.frame = requestAnimationFrame(this.recordFrame)
|
||||
this.startTime = performance.now()
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.started = false
|
||||
if (this.frame !== null) cancelAnimationFrame(this.frame)
|
||||
const duration = (performance.now() - this.startTime) / 1000
|
||||
const fps = duration === 0 ? 0 : Math.floor(this.frames / duration)
|
||||
const background =
|
||||
fps > 55
|
||||
? PERFORMANCE_COLORS.Good
|
||||
: fps > 30
|
||||
? PERFORMANCE_COLORS.Mid
|
||||
: PERFORMANCE_COLORS.Poor
|
||||
const color = background === PERFORMANCE_COLORS.Mid ? 'black' : 'white'
|
||||
const capitalized = this.name[0].toUpperCase() + this.name.slice(1)
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(
|
||||
`%cPerf%c ${capitalized} %c${fps}%c fps`,
|
||||
`color: white; background: ${PERFORMANCE_PREFIX_COLOR};padding: 2px;border-radius: 3px;`,
|
||||
'font-weight: normal',
|
||||
`font-weight: bold; padding: 2px; background: ${background};color: ${color};`,
|
||||
'font-weight: normal'
|
||||
)
|
||||
}
|
||||
|
||||
isStarted() {
|
||||
return this.started
|
||||
}
|
||||
}
|
|
@ -1,9 +1,21 @@
|
|||
export const PERFORMANCE_COLORS = {
|
||||
Good: '#40C057',
|
||||
Mid: '#FFC078',
|
||||
Poor: '#E03131',
|
||||
}
|
||||
|
||||
export const PERFORMANCE_PREFIX_COLOR = PERFORMANCE_COLORS.Good
|
||||
|
||||
/** @internal */
|
||||
export function measureCbDuration(name: string, cb: () => any) {
|
||||
const now = performance.now()
|
||||
const start = performance.now()
|
||||
const result = cb()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${name} took`, performance.now() - now, 'ms')
|
||||
console.debug(
|
||||
`%cPerf%c ${name} took ${performance.now() - start}ms`,
|
||||
`color: white; background: ${PERFORMANCE_PREFIX_COLOR};padding: 2px;border-radius: 3px;`,
|
||||
'font-weight: normal'
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -13,9 +25,12 @@ export function measureDuration(_target: any, propertyKey: string, descriptor: P
|
|||
descriptor.value = function (...args: any[]) {
|
||||
const start = performance.now()
|
||||
const result = originalMethod.apply(this, args)
|
||||
const end = performance.now()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${propertyKey} took ${end - start}ms `)
|
||||
console.debug(
|
||||
`%cPerf%c ${propertyKey} took: ${performance.now() - start}ms`,
|
||||
`color: white; background: ${PERFORMANCE_PREFIX_COLOR};padding: 2px;border-radius: 3px;`,
|
||||
'font-weight: normal'
|
||||
)
|
||||
return result
|
||||
}
|
||||
return descriptor
|
||||
|
@ -41,8 +56,10 @@ export function measureAverageDuration(
|
|||
const count = value.count + 1
|
||||
averages.set(descriptor.value, { total, count })
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`${propertyKey} took ${(end - start).toFixed(2)}ms | average ${(total / count).toFixed(2)}ms`
|
||||
console.debug(
|
||||
`%cPerf%c ${propertyKey} took ${(end - start).toFixed(2)}ms | average ${(total / count).toFixed(2)}ms`,
|
||||
`color: white; background: ${PERFORMANCE_PREFIX_COLOR};padding: 2px;border-radius: 3px;`,
|
||||
'font-weight: normal'
|
||||
)
|
||||
}
|
||||
return result
|
||||
|
|
Ładowanie…
Reference in New Issue