From d592995314f9a348667144aa51bfd1c396a49a29 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Sat, 11 May 2024 09:35:16 +0100 Subject: [PATCH] add cache manager, some caches --- packages/editor/api-report.md | 2 + packages/editor/src/lib/editor/Editor.ts | 9 +++ .../src/lib/editor/managers/CacheManager.ts | 28 ++++++++++ .../src/lib/shapes/line/LineShapeUtil.tsx | 55 +++++++++---------- .../src/lib/shapes/note/NoteShapeUtil.tsx | 13 +++-- .../src/lib/shapes/text/TextShapeUtil.tsx | 14 +++-- packages/utils/api-report.md | 1 + packages/utils/src/lib/cache.ts | 7 +++ 8 files changed, 92 insertions(+), 37 deletions(-) create mode 100644 packages/editor/src/lib/editor/managers/CacheManager.ts diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index c63729310..2b5d20ccd 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -82,6 +82,7 @@ import { useQuickReactor } from '@tldraw/state'; import { useReactor } from '@tldraw/state'; import { useValue } from '@tldraw/state'; import { VecModel } from '@tldraw/tlschema'; +import { WeakCache } from '@tldraw/utils'; import { whyAmIRunning } from '@tldraw/state'; // @public @@ -699,6 +700,7 @@ export class Editor extends EventEmitter { }; bringForward(shapes: TLShape[] | TLShapeId[]): this; bringToFront(shapes: TLShape[] | TLShapeId[]): this; + readonly caches: CacheManager; cancel(): this; cancelDoubleClick(): void; // @internal (undocumented) diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index cf3f8a750..334dd2d22 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -123,6 +123,7 @@ import { notVisibleShapes } from './derivations/notVisibleShapes' import { parentsToChildren } from './derivations/parentsToChildren' import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage' import { getSvgJsx } from './getSvgJsx' +import { CacheManager } from './managers/CacheManager' import { ClickManager } from './managers/ClickManager' import { EnvironmentManager } from './managers/EnvironmentManager' import { HistoryManager } from './managers/HistoryManager' @@ -297,6 +298,7 @@ export class Editor extends EventEmitter { this.environment = new EnvironmentManager(this) this.scribbles = new ScribbleManager(this) + this.caches = new CacheManager(this) // Cleanup @@ -711,6 +713,13 @@ export class Editor extends EventEmitter { */ readonly sideEffects: SideEffectManager + /** + * A manager for weak map caches. + * + * @public + */ + readonly caches: CacheManager + /** * The current HTML element containing the editor. * diff --git a/packages/editor/src/lib/editor/managers/CacheManager.ts b/packages/editor/src/lib/editor/managers/CacheManager.ts new file mode 100644 index 000000000..8bf7199f9 --- /dev/null +++ b/packages/editor/src/lib/editor/managers/CacheManager.ts @@ -0,0 +1,28 @@ +import { WeakCache } from '@tldraw/utils' +import { Editor } from '../Editor' + +export class CacheManager { + constructor(public editor: Editor) {} + + private caches = new Map>() + + createCache(name: string) { + const cache = new WeakCache() + this.caches.set(name, cache) + return cache + } + + get(name: string): WeakCache { + return this.caches.get(name) as WeakCache + } + + clear(name: string) { + const cache = this.caches.get(name) + if (!cache) throw Error(`Cache ${name} not found`) + cache.clear() + } + + clearAll() { + this.caches.clear() + } +} diff --git a/packages/tldraw/src/lib/shapes/line/LineShapeUtil.tsx b/packages/tldraw/src/lib/shapes/line/LineShapeUtil.tsx index fd1f970a6..8b0f27e76 100644 --- a/packages/tldraw/src/lib/shapes/line/LineShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/line/LineShapeUtil.tsx @@ -10,7 +10,6 @@ import { TLOnHandleDragHandler, TLOnResizeHandler, Vec, - WeakCache, getIndexBetween, getIndices, lineShapeMigrations, @@ -30,8 +29,6 @@ import { getSvgPathForLineGeometry, } from './components/svg' -const handlesCache = new WeakCache() - /** @public */ export class LineShapeUtil extends ShapeUtil { static override type = 'line' as const @@ -63,33 +60,31 @@ export class LineShapeUtil extends ShapeUtil { } override getHandles(shape: TLLineShape) { - return handlesCache.get(shape.props, () => { - const spline = getGeometryForLineShape(shape) + const spline = getGeometryForLineShape(shape) - const points = linePointsToArray(shape) - const results: TLHandle[] = points.map((point) => ({ - ...point, - id: point.index, - type: 'vertex', + const points = linePointsToArray(shape) + const results: TLHandle[] = points.map((point) => ({ + ...point, + id: point.index, + type: 'vertex', + canSnap: true, + })) + + for (let i = 0; i < points.length - 1; i++) { + const index = getIndexBetween(points[i].index, points[i + 1].index) + const segment = spline.segments[i] + const point = segment.midPoint() + results.push({ + id: index, + type: 'create', + index, + x: point.x, + y: point.y, canSnap: true, - })) + }) + } - for (let i = 0; i < points.length - 1; i++) { - const index = getIndexBetween(points[i].index, points[i + 1].index) - const segment = spline.segments[i] - const point = segment.midPoint() - results.push({ - id: index, - type: 'create', - index, - x: point.x, - y: point.y, - canSnap: true, - }) - } - - return results.sort(sortByIndex) - }) + return results.sort(sortByIndex) } // Events @@ -164,7 +159,8 @@ export class LineShapeUtil extends ShapeUtil { return { points, getSelfSnapPoints: (handle) => { - const index = this.getHandles(shape) + const index = this.editor + .getShapeHandles(shape)! .filter((h) => h.type === 'vertex') .findIndex((h) => h.id === handle.id)! @@ -175,7 +171,8 @@ export class LineShapeUtil extends ShapeUtil { // We want to skip the segments that include the handle, so // find the index of the handle that shares the same index property // as the initial dragging handle; this catches a quirk of create handles - const index = this.getHandles(shape) + const index = this.editor + .getShapeHandles(shape)! .filter((h) => h.type === 'vertex') .findIndex((h) => h.id === handle.id)! diff --git a/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx b/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx index 670d4965f..573475a6f 100644 --- a/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx @@ -11,7 +11,6 @@ import { TLShape, TLShapeId, Vec, - WeakCache, getDefaultColorTheme, noteShapeMigrations, noteShapeProps, @@ -372,10 +371,16 @@ function getNoteLabelSize(editor: Editor, shape: TLNoteShape) { } } -const labelSizesForNote = new WeakCache>() - function getLabelSize(editor: Editor, shape: TLNoteShape) { - return labelSizesForNote.get(shape, () => getNoteLabelSize(editor, shape)) + let cache = editor.caches.get>( + '@tldraw/noteLabelSize' + ) + if (!cache) { + cache = editor.caches.createCache>( + '@tldraw/noteLabelSize' + ) + } + return cache.get(shape, () => getNoteLabelSize(editor, shape)) } function useNoteKeydownHandler(id: TLShapeId) { diff --git a/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx b/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx index 7597acff5..45d99b313 100644 --- a/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx @@ -11,7 +11,6 @@ import { TLShapeUtilFlag, TLTextShape, Vec, - WeakCache, getDefaultColorTheme, preventDefault, textShapeMigrations, @@ -28,8 +27,6 @@ import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../shared/default-shape-c import { getFontDefForExport } from '../shared/defaultStyleDefs' import { resizeScaled } from '../shared/resizeScaled' -const sizeCache = new WeakCache() - /** @public */ export class TextShapeUtil extends ShapeUtil { static override type = 'text' as const @@ -50,7 +47,16 @@ export class TextShapeUtil extends ShapeUtil { } getMinDimensions(shape: TLTextShape) { - return sizeCache.get(shape.props, (props) => getTextSize(this.editor, props)) + const { editor } = this + let cache = editor.caches.get( + '@tldraw/textShapeSize' + ) + if (!cache) { + cache = editor.caches.createCache( + '@tldraw/textShapeSize' + ) + } + return cache.get(shape.props, (props) => getTextSize(this.editor, props)) } getGeometry(shape: TLTextShape) { diff --git a/packages/utils/api-report.md b/packages/utils/api-report.md index 66d93dfcb..974dcfd1b 100644 --- a/packages/utils/api-report.md +++ b/packages/utils/api-report.md @@ -349,6 +349,7 @@ export function warnDeprecatedGetter(name: string): void; // @public export class WeakCache { + clear(): void; get

(item: P, cb: (item: P) => V): NonNullable; items: WeakMap; } diff --git a/packages/utils/src/lib/cache.ts b/packages/utils/src/lib/cache.ts index 2ae38674e..90012693d 100644 --- a/packages/utils/src/lib/cache.ts +++ b/packages/utils/src/lib/cache.ts @@ -20,4 +20,11 @@ export class WeakCache { return this.items.get(item)! } + + /** + * Clear the cache. (Technically we create a new WeakMap, but the old one will get cleaned up by the GC eventually.) + */ + clear() { + this.items = new WeakMap() + } }