Mitja Bezenšek 2024-04-22 13:36:19 +00:00 zatwierdzone przez GitHub
commit 4f50d878b8
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
12 zmienionych plików z 363 dodań i 7 usunięć

Wyświetl plik

@ -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
},
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -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)",

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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 },
}),

Wyświetl plik

@ -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)

Wyświetl plik

@ -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",

Wyświetl plik

@ -1,3 +1,4 @@
export { PerformanceTracker } from './lib/PerformanceTracker'
export {
areArraysShallowEqual,
compact,

Wyświetl plik

@ -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
}
}

Wyświetl plik

@ -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