refactor and improve perf

This commit is contained in:
Ryan Di 2025-04-09 11:11:13 +10:00
parent 13ce3cd166
commit 2032c857fe

View file

@ -6,7 +6,6 @@ import {
pointFrom, pointFrom,
} from "@excalidraw/math"; } from "@excalidraw/math";
import { simplify } from "points-on-curve";
import { getElementsInGroup } from "@excalidraw/element/groups"; import { getElementsInGroup } from "@excalidraw/element/groups";
import { getElementShape } from "@excalidraw/element/shapes"; import { getElementShape } from "@excalidraw/element/shapes";
@ -32,11 +31,15 @@ import type { AnimationFrameHandler } from "../animation-frame-handler";
import type App from "../components/App"; import type App from "../components/App";
// just enough to form a segment; this is sufficient for eraser
const POINTS_ON_TRAIL = 2;
export class EraserTrail extends AnimatedTrail { export class EraserTrail extends AnimatedTrail {
private elementsToErase: Set<ExcalidrawElement["id"]> = new Set(); private elementsToErase: Set<ExcalidrawElement["id"]> = new Set();
private groupsToErase: Set<ExcalidrawElement["id"]> = new Set(); private groupsToErase: Set<ExcalidrawElement["id"]> = new Set();
private elementsSegments: Map<string, LineSegment<GlobalPoint>[]> = new Map(); private segmentsCache: Map<string, LineSegment<GlobalPoint>[]> = new Map();
private shapesCache: Map<string, GeometricShape<GlobalPoint>> = new Map(); private geometricShapesCache: Map<string, GeometricShape<GlobalPoint>> =
new Map();
constructor(animationFrameHandler: AnimationFrameHandler, app: App) { constructor(animationFrameHandler: AnimationFrameHandler, app: App) {
super(animationFrameHandler, app, { super(animationFrameHandler, app, {
@ -65,9 +68,7 @@ export class EraserTrail extends AnimatedTrail {
} }
startPath(x: number, y: number): void { startPath(x: number, y: number): void {
// clear any existing trails just in case
this.endPath(); this.endPath();
super.startPath(x, y); super.startPath(x, y);
this.elementsToErase.clear(); this.elementsToErase.clear();
} }
@ -85,23 +86,32 @@ export class EraserTrail extends AnimatedTrail {
.getCurrentTrail() .getCurrentTrail()
?.originalPoints?.map((p) => pointFrom<GlobalPoint>(p[0], p[1])); ?.originalPoints?.map((p) => pointFrom<GlobalPoint>(p[0], p[1]));
eraserPath = eraserPath?.slice(eraserPath.length - 20); // for efficiency and avoid unnecessary calculations,
// take only POINTS_ON_TRAIL points to form some number of segments
eraserPath = eraserPath?.slice(eraserPath.length - POINTS_ON_TRAIL);
if (!eraserPath || eraserPath.length === 0) {
return [];
}
const visibleElementsMap = arrayToMap(this.app.visibleElements); const visibleElementsMap = arrayToMap(this.app.visibleElements);
if (eraserPath) { const pathSegments = eraserPath.reduce((acc, point, index) => {
const simplifiedPath = simplify( if (index === 0) {
eraserPath, return acc;
5 / this.app.state.zoom.value, }
) as GlobalPoint[]; acc.push(lineSegment(eraserPath[index - 1], point));
return acc;
}, [] as LineSegment<GlobalPoint>[]);
for (const element of this.app.visibleElements) { for (const element of this.app.visibleElements) {
// restore only if already added to the to-be-erased set
if (restoreToErase && this.elementsToErase.has(element.id)) { if (restoreToErase && this.elementsToErase.has(element.id)) {
const intersects = eraserTest( const intersects = eraserTest(
simplifiedPath, pathSegments,
element, element,
this.elementsSegments, this.segmentsCache,
this.shapesCache, this.geometricShapesCache,
visibleElementsMap, visibleElementsMap,
this.app, this.app,
); );
@ -136,10 +146,10 @@ export class EraserTrail extends AnimatedTrail {
} }
} else if (!restoreToErase && !this.elementsToErase.has(element.id)) { } else if (!restoreToErase && !this.elementsToErase.has(element.id)) {
const intersects = eraserTest( const intersects = eraserTest(
simplifiedPath, pathSegments,
element, element,
this.elementsSegments, this.segmentsCache,
this.shapesCache, this.geometricShapesCache,
visibleElementsMap, visibleElementsMap,
this.app, this.app,
); );
@ -175,7 +185,6 @@ export class EraserTrail extends AnimatedTrail {
} }
} }
} }
}
return Array.from(this.elementsToErase); return Array.from(this.elementsToErase);
} }
@ -185,12 +194,12 @@ export class EraserTrail extends AnimatedTrail {
super.clearTrails(); super.clearTrails();
this.elementsToErase.clear(); this.elementsToErase.clear();
this.groupsToErase.clear(); this.groupsToErase.clear();
this.elementsSegments.clear(); this.segmentsCache.clear();
} }
} }
const eraserTest = ( const eraserTest = (
path: GlobalPoint[], pathSegments: LineSegment<GlobalPoint>[],
element: ExcalidrawElement, element: ExcalidrawElement,
elementsSegments: ElementsSegmentsMap, elementsSegments: ElementsSegmentsMap,
shapesCache = new Map<string, GeometricShape<GlobalPoint>>(), shapesCache = new Map<string, GeometricShape<GlobalPoint>>(),
@ -204,7 +213,7 @@ const eraserTest = (
shapesCache.set(element.id, shape); shapesCache.set(element.id, shape);
} }
const lastPoint = path[path.length - 1]; const lastPoint = pathSegments[pathSegments.length - 1][1];
if (shouldTestInside(element) && isPointInShape(lastPoint, shape)) { if (shouldTestInside(element) && isPointInShape(lastPoint, shape)) {
return true; return true;
} }
@ -216,14 +225,6 @@ const eraserTest = (
elementsSegments.set(element.id, elementSegments); elementsSegments.set(element.id, elementSegments);
} }
const pathSegments = path.reduce((acc, point, index) => {
if (index === 0) {
return acc;
}
acc.push(lineSegment(path[index - 1], point));
return acc;
}, [] as LineSegment<GlobalPoint>[]);
return pathSegments.some((pathSegment) => return pathSegments.some((pathSegment) =>
elementSegments?.some( elementSegments?.some(
(elementSegment) => (elementSegment) =>