[fix] Batch tick events (#3181)

This PR fixes an issue where events happening on tick were not batched. 

![Kapture 2024-03-17 at 22 49
52](https://github.com/tldraw/tldraw/assets/23072548/2bcfa335-a38f-46c4-a3f3-434cac61b6ce)

We were listening to the `tick` event directly from the state node,
rather than passing the event into the state chart at the top. This
meant that it was bypassing the regular state chart rules, which was
what got me looking at this; but then I noticed that we also weren't
batching the changes, either. This causes computed stuff to re-compute
after each atom is updated within the `onTick` handler, which can be a
LOT.

Before:
<img width="1557" alt="image"
src="https://github.com/tldraw/tldraw/assets/23072548/ba8791f2-faec-463d-945a-9f5920826aab">

After:
<img width="1204" alt="image"
src="https://github.com/tldraw/tldraw/assets/23072548/a00f8e4a-caca-406a-89a2-8cff0e01b642">

It's not game breaking but it's important enough to hotfix at least in
the dot com.

### Change Type

<!--  Please select a 'Scope' label ️ -->

- [x] `sdk` — Changes the tldraw SDK
- [ ] `dotcom` — Changes the tldraw.com web app
- [ ] `docs` — Changes to the documentation, examples, or templates.
- [ ] `vs code` — Changes to the vscode plugin
- [ ] `internal` — Does not affect user-facing stuff

<!--  Please select a 'Type' label ️ -->

- [x] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [ ] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [ ] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [ ] `dunno` — I don't know


### Test Plan

1. Select many shapes.
2. Resize them.

### Release Notes

- Fix a performance issue effecting resizing multiple shapes.
pull/3198/head^2
Steve Ruiz 2024-03-18 14:33:36 +00:00 zatwierdzone przez GitHub
rodzic 176fdb5425
commit b9b5bd5b81
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
11 zmienionych plików z 87 dodań i 78 usunięć

Wyświetl plik

@ -1782,7 +1782,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// (undocumented)
onRightClick?: TLEventHandlers['onRightClick'];
// (undocumented)
onTick?: TLTickEventHandler;
onTick?: TLEventHandlers['onTick'];
// (undocumented)
onTripleClick?: TLEventHandlers['onTripleClick'];
// (undocumented)
@ -2102,13 +2102,15 @@ export interface TLEventHandlers {
// (undocumented)
onRightClick: TLPointerEvent;
// (undocumented)
onTick: TLTickEvent;
// (undocumented)
onTripleClick: TLClickEvent;
// (undocumented)
onWheel: TLWheelEvent;
}
// @public (undocumented)
export type TLEventInfo = TLCancelEventInfo | TLClickEventInfo | TLCompleteEventInfo | TLInterruptEventInfo | TLKeyboardEventInfo | TLPinchEventInfo | TLPointerEventInfo | TLWheelEventInfo;
export type TLEventInfo = TLCancelEventInfo | TLClickEventInfo | TLCompleteEventInfo | TLInterruptEventInfo | TLKeyboardEventInfo | TLPinchEventInfo | TLPointerEventInfo | TLTickEventInfo | TLWheelEventInfo;
// @public (undocumented)
export interface TLEventMap {
@ -2155,7 +2157,7 @@ export interface TLEventMap {
export type TLEventMapHandler<T extends keyof TLEventMap> = (...args: TLEventMap[T]) => void;
// @public (undocumented)
export type TLEventName = 'cancel' | 'complete' | 'interrupt' | 'wheel' | TLCLickEventName | TLKeyboardEventName | TLPinchEventName | TLPointerEventName;
export type TLEventName = 'cancel' | 'complete' | 'interrupt' | 'tick' | 'wheel' | TLCLickEventName | TLKeyboardEventName | TLPinchEventName | TLPointerEventName;
// @public (undocumented)
export type TLExitEventHandler = (info: any, to: string) => void;
@ -2571,10 +2573,7 @@ export type TLSvgOptions = {
};
// @public (undocumented)
export type TLTickEvent = (elapsed: number) => void;
// @public (undocumented)
export type TLTickEventHandler = () => void;
export type TLTickEvent = (info: TLTickEventInfo) => void;
// @public
export interface TLUserPreferences {

Wyświetl plik

@ -34154,8 +34154,12 @@
},
{
"kind": "Reference",
"text": "TLTickEventHandler",
"canonicalReference": "@tldraw/editor!TLTickEventHandler:type"
"text": "TLEventHandlers",
"canonicalReference": "@tldraw/editor!TLEventHandlers:interface"
},
{
"kind": "Content",
"text": "['onTick']"
},
{
"kind": "Content",
@ -34168,7 +34172,7 @@
"name": "onTick",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
"endIndex": 3
},
"isStatic": false,
"isProtected": false,
@ -37534,6 +37538,34 @@
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/editor!TLEventHandlers#onTick:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "onTick: "
},
{
"kind": "Reference",
"text": "TLTickEvent",
"canonicalReference": "@tldraw/editor!TLTickEvent:type"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "onTick",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/editor!TLEventHandlers#onTripleClick:member",
@ -37665,6 +37697,15 @@
"kind": "Content",
"text": " | "
},
{
"kind": "Reference",
"text": "TLTickEventInfo",
"canonicalReference": "@tldraw/editor!~TLTickEventInfo:type"
},
{
"kind": "Content",
"text": " | "
},
{
"kind": "Reference",
"text": "TLWheelEventInfo",
@ -37680,7 +37721,7 @@
"name": "TLEventInfo",
"typeTokenRange": {
"startIndex": 1,
"endIndex": 16
"endIndex": 18
}
},
{
@ -38133,7 +38174,7 @@
},
{
"kind": "Content",
"text": "'cancel' | 'complete' | 'interrupt' | 'wheel' | "
"text": "'cancel' | 'complete' | 'interrupt' | 'tick' | 'wheel' | "
},
{
"kind": "Reference",
@ -41562,7 +41603,16 @@
},
{
"kind": "Content",
"text": "(elapsed: number) => void"
"text": "(info: "
},
{
"kind": "Reference",
"text": "TLTickEventInfo",
"canonicalReference": "@tldraw/editor!~TLTickEventInfo:type"
},
{
"kind": "Content",
"text": ") => void"
},
{
"kind": "Content",
@ -41574,33 +41624,7 @@
"name": "TLTickEvent",
"typeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "TypeAlias",
"canonicalReference": "@tldraw/editor!TLTickEventHandler:type",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export type TLTickEventHandler = "
},
{
"kind": "Content",
"text": "() => void"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/editor/src/lib/editor/types/event-types.ts",
"releaseTag": "Public",
"name": "TLTickEventHandler",
"typeTokenRange": {
"startIndex": 1,
"endIndex": 2
"endIndex": 4
}
},
{

Wyświetl plik

@ -223,7 +223,6 @@ export {
type TLPointerEventName,
type TLPointerEventTarget,
type TLTickEvent,
type TLTickEventHandler,
type TLWheelEvent,
type TLWheelEventInfo,
type UiEvent,

Wyświetl plik

@ -637,6 +637,8 @@ export class Editor extends EventEmitter<TLEventMap> {
this.updateRenderingBounds()
this.on('tick', this.tick)
requestAnimationFrame(() => {
this._tickManager.start()
})
@ -8380,6 +8382,12 @@ export class Editor extends EventEmitter<TLEventMap> {
])
}
/** @internal */
private tick = (elapsed = 0) => {
this.dispatch({ type: 'misc', name: 'tick', elapsed })
this.scribbles.tick(elapsed)
}
/**
* Dispatch a cancel event.
*

Wyświetl plik

@ -2,7 +2,6 @@ import { TLScribble, VecModel } from '@tldraw/tlschema'
import { Vec } from '../../primitives/Vec'
import { uniqueId } from '../../utils/uniqueId'
import { Editor } from '../Editor'
import { TLTickEvent } from '../types/event-types'
type ScribbleItem = {
id: string
@ -41,26 +40,12 @@ export class ScribbleManager {
next: null,
}
this.scribbleItems.set(id, item)
if (this.state === 'paused') {
this.resume()
}
return item
}
resume() {
this.state = 'running'
this.editor.addListener('tick', this.tick)
}
pause() {
this.editor.removeListener('tick', this.tick)
this.state = 'paused'
}
reset() {
this.editor.updateInstanceState({ scribbles: [] })
this.scribbleItems.clear()
this.pause()
}
/**
@ -99,7 +84,8 @@ export class ScribbleManager {
* @param elapsed - The number of milliseconds since the last tick.
* @public
*/
tick: TLTickEvent = (elapsed) => {
tick = (elapsed: number) => {
if (this.scribbleItems.size === 0) return
this.editor.batch(() => {
this.scribbleItems.forEach((item) => {
// let the item get at least eight points before
@ -190,11 +176,6 @@ export class ScribbleManager {
}))
.slice(-5), // limit to three as a minor sanity check
})
// If we've removed all the scribbles, stop ticking
if (this.scribbleItems.size === 0) {
this.pause()
}
})
}
}

Wyświetl plik

@ -7,7 +7,6 @@ import {
TLEventInfo,
TLExitEventHandler,
TLPinchEventInfo,
TLTickEventHandler,
} from '../types/event-types'
type TLStateNodeType = 'branch' | 'leaf' | 'root'
@ -158,7 +157,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
enter = (info: any, from: string) => {
this._isActive.set(true)
this.onEnter?.(info, from)
if (this.onTick) this.editor.on('tick', this.onTick)
if (this.children && this.initial && this.getIsActive()) {
const initial = this.children[this.initial]
this._current.set(initial)
@ -169,8 +168,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// todo: move this logic into transition
exit = (info: any, from: string) => {
this._isActive.set(false)
if (this.onTick) this.editor.off('tick', this.onTick)
this.onExit?.(info, from)
if (!this.getIsActive()) {
this.getCurrent()?.exit(info, from)
}
@ -211,8 +210,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
onCancel?: TLEventHandlers['onCancel']
onComplete?: TLEventHandlers['onComplete']
onInterrupt?: TLEventHandlers['onInterrupt']
onTick?: TLEventHandlers['onTick']
onEnter?: TLEnterEventHandler
onExit?: TLExitEventHandler
onTick?: TLTickEventHandler
}

Wyświetl plik

@ -39,6 +39,7 @@ export type TLEventName =
| 'cancel'
| 'complete'
| 'interrupt'
| 'tick'
/** @public */
export interface TLBaseEventInfo {
@ -99,6 +100,8 @@ export type TLCancelEventInfo = { type: 'misc'; name: 'cancel' }
export type TLCompleteEventInfo = { type: 'misc'; name: 'complete' }
/** @public */
export type TLInterruptEventInfo = { type: 'misc'; name: 'interrupt' }
/** @public */
export type TLTickEventInfo = { type: 'misc'; name: 'tick'; elapsed: number }
/** @public */
export type TLEventInfo =
@ -110,6 +113,7 @@ export type TLEventInfo =
| TLCancelEventInfo
| TLCompleteEventInfo
| TLInterruptEventInfo
| TLTickEventInfo
/** @public */
export type TLPointerEvent = (info: TLPointerEventInfo) => void
@ -127,6 +131,8 @@ export type TLCancelEvent = (info: TLCancelEventInfo) => void
export type TLCompleteEvent = (info: TLCompleteEventInfo) => void
/** @public */
export type TLInterruptEvent = (info: TLInterruptEventInfo) => void
/** @public */
export type TLTickEvent = (info: TLTickEventInfo) => void
/** @public */
export type UiEvent =
@ -137,8 +143,6 @@ export type UiEvent =
| TLCancelEvent
| TLCompleteEvent
/** @public */
export type TLTickEventHandler = () => void
/** @public */
export type TLEnterEventHandler = (info: any, from: string) => void
/** @public */
@ -161,6 +165,7 @@ export interface TLEventHandlers {
onCancel: TLCancelEvent
onComplete: TLCompleteEvent
onInterrupt: TLInterruptEvent
onTick: TLTickEvent
}
/** @public */
@ -183,7 +188,5 @@ export const EVENT_NAME_MAP: Record<
double_click: 'onDoubleClick',
triple_click: 'onTripleClick',
quadruple_click: 'onQuadrupleClick',
tick: 'onTick',
}
/** @public */
export type TLTickEvent = (elapsed: number) => void

Wyświetl plik

@ -114,7 +114,6 @@ import { TLStore } from '@tldraw/editor';
import { TLStoreWithStatus } from '@tldraw/editor';
import { TLSvgOptions } from '@tldraw/editor';
import { TLTextShape } from '@tldraw/editor';
import { TLTickEventHandler } from '@tldraw/editor';
import { TLUnknownShape } from '@tldraw/editor';
import { TLVideoShape } from '@tldraw/editor';
import { UnionValidator } from '@tldraw/editor';

Wyświetl plik

@ -13,7 +13,6 @@ import {
TLPointerEventInfo,
TLShape,
TLShapeId,
TLTickEventHandler,
Vec,
moveCameraWhenCloseToEdge,
pointInPolygon,
@ -67,7 +66,7 @@ export class Brushing extends StateNode {
this.editor.updateInstanceState({ brush: null })
}
override onTick: TLTickEventHandler = () => {
override onTick = () => {
moveCameraWhenCloseToEdge(this.editor)
if (this.isDirty) {
this.isDirty = false

Wyświetl plik

@ -13,7 +13,6 @@ import {
TLShape,
TLShapeId,
TLShapePartial,
TLTickEventHandler,
Vec,
VecLike,
areAnglesCompatible,
@ -74,7 +73,7 @@ export class Resizing extends StateNode {
this.updateShapes()
}
override onTick: TLTickEventHandler = () => {
override onTick = () => {
moveCameraWhenCloseToEdge(this.editor)
if (!this.isDirty) return
this.isDirty = false

Wyświetl plik

@ -10,7 +10,6 @@ import {
TLPointerEventInfo,
TLShape,
TLShapePartial,
TLTickEventHandler,
Vec,
compact,
isPageId,
@ -95,7 +94,7 @@ export class Translating extends StateNode {
this.dragAndDropManager.clear()
}
override onTick: TLTickEventHandler = () => {
override onTick = () => {
this.dragAndDropManager.updateDroppingNode(
this.snapshot.movingShapes,
this.updateParentTransforms