perf(snapping): reduce line point snap scanning
This commit is contained in:
@@ -1944,7 +1944,6 @@ export class LinearElementEditor {
|
|||||||
const { snapOffset, snapLines: nextSnapLines } = snapLinearElementPoint(
|
const { snapOffset, snapLines: nextSnapLines } = snapLinearElementPoint(
|
||||||
app.scene.getNonDeletedElements(),
|
app.scene.getNonDeletedElements(),
|
||||||
element,
|
element,
|
||||||
pointIndex,
|
|
||||||
pointFrom<GlobalPoint>(effectiveGridX, effectiveGridY),
|
pointFrom<GlobalPoint>(effectiveGridX, effectiveGridY),
|
||||||
app,
|
app,
|
||||||
event,
|
event,
|
||||||
@@ -1997,7 +1996,6 @@ export class LinearElementEditor {
|
|||||||
const { snapOffset, snapLines } = snapLinearElementPoint(
|
const { snapOffset, snapLines } = snapLinearElementPoint(
|
||||||
app.scene.getNonDeletedElements(),
|
app.scene.getNonDeletedElements(),
|
||||||
element,
|
element,
|
||||||
pointIndex,
|
|
||||||
pointFrom(originalPointerX, originalPointerY),
|
pointFrom(originalPointerX, originalPointerY),
|
||||||
app,
|
app,
|
||||||
event,
|
event,
|
||||||
|
|||||||
+173
-101
@@ -130,6 +130,11 @@ export type SnapLine = PointSnapLine | GapSnapLine | PointerSnapLine;
|
|||||||
export class SnapCache {
|
export class SnapCache {
|
||||||
private static referenceSnapPoints: GlobalPoint[] | null = null;
|
private static referenceSnapPoints: GlobalPoint[] | null = null;
|
||||||
|
|
||||||
|
private static linearElementPointReferenceSnapPoints: {
|
||||||
|
editingElementId: ExcalidrawElement["id"];
|
||||||
|
snapPoints: GlobalPoint[];
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
private static visibleGaps: {
|
private static visibleGaps: {
|
||||||
verticalGaps: Gap[];
|
verticalGaps: Gap[];
|
||||||
horizontalGaps: Gap[];
|
horizontalGaps: Gap[];
|
||||||
@@ -143,6 +148,27 @@ export class SnapCache {
|
|||||||
return SnapCache.referenceSnapPoints;
|
return SnapCache.referenceSnapPoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static setLinearElementPointReferenceSnapPoints = (
|
||||||
|
editingElementId: ExcalidrawElement["id"],
|
||||||
|
snapPoints: GlobalPoint[] | null,
|
||||||
|
) => {
|
||||||
|
SnapCache.linearElementPointReferenceSnapPoints = snapPoints
|
||||||
|
? {
|
||||||
|
editingElementId,
|
||||||
|
snapPoints,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
public static getLinearElementPointReferenceSnapPoints = (
|
||||||
|
editingElementId: ExcalidrawElement["id"],
|
||||||
|
) => {
|
||||||
|
return SnapCache.linearElementPointReferenceSnapPoints?.editingElementId ===
|
||||||
|
editingElementId
|
||||||
|
? SnapCache.linearElementPointReferenceSnapPoints.snapPoints
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
public static setVisibleGaps = (
|
public static setVisibleGaps = (
|
||||||
gaps: {
|
gaps: {
|
||||||
verticalGaps: Gap[];
|
verticalGaps: Gap[];
|
||||||
@@ -158,6 +184,7 @@ export class SnapCache {
|
|||||||
|
|
||||||
public static destroy = () => {
|
public static destroy = () => {
|
||||||
SnapCache.referenceSnapPoints = null;
|
SnapCache.referenceSnapPoints = null;
|
||||||
|
SnapCache.linearElementPointReferenceSnapPoints = null;
|
||||||
SnapCache.visibleGaps = null;
|
SnapCache.visibleGaps = null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -654,10 +681,46 @@ export const getReferenceSnapPoints = (
|
|||||||
.flatMap((elementGroup) => getElementsCorners(elementGroup, elementsMap));
|
.flatMap((elementGroup) => getElementsCorners(elementGroup, elementsMap));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getExternalReferenceSnapPointsForLinearElementPoint = (
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
editingElement: ExcalidrawLinearElement,
|
||||||
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) => {
|
||||||
|
const cachedReferenceSnapPoints =
|
||||||
|
SnapCache.getLinearElementPointReferenceSnapPoints(editingElement.id);
|
||||||
|
|
||||||
|
const externalReferenceSnapPoints =
|
||||||
|
cachedReferenceSnapPoints ??
|
||||||
|
getReferenceSnapPoints(elements, [editingElement], appState, elementsMap);
|
||||||
|
|
||||||
|
if (!cachedReferenceSnapPoints) {
|
||||||
|
SnapCache.setLinearElementPointReferenceSnapPoints(
|
||||||
|
editingElement.id,
|
||||||
|
externalReferenceSnapPoints,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return externalReferenceSnapPoints;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelfReferenceSnapPointsForLinearElementPoint = (
|
||||||
|
editingElement: ExcalidrawLinearElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
selectedPointsIndices?: readonly number[],
|
||||||
|
) => {
|
||||||
|
return LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
editingElement as NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap,
|
||||||
|
{
|
||||||
|
excludePointsIndices: selectedPointsIndices,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getReferenceSnapPointsForLinearElementPoint = (
|
export const getReferenceSnapPointsForLinearElementPoint = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
editingElement: ExcalidrawLinearElement,
|
editingElement: ExcalidrawLinearElement,
|
||||||
editingPointIndex: number,
|
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
options: {
|
options: {
|
||||||
@@ -665,50 +728,77 @@ export const getReferenceSnapPointsForLinearElementPoint = (
|
|||||||
selectedPointsIndices?: readonly number[];
|
selectedPointsIndices?: readonly number[];
|
||||||
} = {},
|
} = {},
|
||||||
) => {
|
) => {
|
||||||
const { includeSelfPoints = false } = options;
|
const externalReferenceSnapPoints =
|
||||||
|
getExternalReferenceSnapPointsForLinearElementPoint(
|
||||||
// Get all reference elements (excluding the one being edited)
|
elements,
|
||||||
const referenceElements = getReferenceElements(
|
editingElement,
|
||||||
elements,
|
appState,
|
||||||
[editingElement],
|
|
||||||
appState,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
const allSnapPoints: GlobalPoint[] = [];
|
|
||||||
|
|
||||||
// Add snap points from all reference elements
|
|
||||||
const referenceGroups = getMaximumGroups(
|
|
||||||
referenceElements,
|
|
||||||
elementsMap,
|
|
||||||
).filter(
|
|
||||||
(elementsGroup) =>
|
|
||||||
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const elementGroup of referenceGroups) {
|
|
||||||
allSnapPoints.push(...getElementsCorners(elementGroup, elementsMap));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include other points from the same linear element when creating new points or in editing mode
|
|
||||||
if (includeSelfPoints) {
|
|
||||||
const elementPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
|
||||||
editingElement as NonDeleted<ExcalidrawLinearElement>,
|
|
||||||
elementsMap,
|
elementsMap,
|
||||||
{
|
|
||||||
excludePointsIndices: options.selectedPointsIndices,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
allSnapPoints.push(...elementPoints);
|
|
||||||
|
if (!options.includeSelfPoints) {
|
||||||
|
return externalReferenceSnapPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
return allSnapPoints;
|
return externalReferenceSnapPoints.concat(
|
||||||
|
getSelfReferenceSnapPointsForLinearElementPoint(
|
||||||
|
editingElement,
|
||||||
|
elementsMap,
|
||||||
|
options.selectedPointsIndices,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addNearestPointSnaps = (
|
||||||
|
referenceSnapPoints: readonly GlobalPoint[],
|
||||||
|
pointerPosition: GlobalPoint,
|
||||||
|
nearestSnapsX: Snaps,
|
||||||
|
nearestSnapsY: Snaps,
|
||||||
|
minOffset: Vector2D,
|
||||||
|
) => {
|
||||||
|
for (const referencePoint of referenceSnapPoints) {
|
||||||
|
const offsetX = referencePoint[0] - pointerPosition[0];
|
||||||
|
const offsetY = referencePoint[1] - pointerPosition[1];
|
||||||
|
const absOffsetX = Math.abs(offsetX);
|
||||||
|
const absOffsetY = Math.abs(offsetY);
|
||||||
|
|
||||||
|
if (absOffsetX > minOffset.x && absOffsetY > minOffset.y) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absOffsetX <= minOffset.x) {
|
||||||
|
if (absOffsetX < minOffset.x) {
|
||||||
|
nearestSnapsX.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nearestSnapsX.push({
|
||||||
|
type: "point",
|
||||||
|
points: [pointerPosition, referencePoint],
|
||||||
|
offset: offsetX,
|
||||||
|
});
|
||||||
|
|
||||||
|
minOffset.x = absOffsetX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absOffsetY <= minOffset.y) {
|
||||||
|
if (absOffsetY < minOffset.y) {
|
||||||
|
nearestSnapsY.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nearestSnapsY.push({
|
||||||
|
type: "point",
|
||||||
|
points: [pointerPosition, referencePoint],
|
||||||
|
offset: offsetY,
|
||||||
|
});
|
||||||
|
|
||||||
|
minOffset.y = absOffsetY;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const snapLinearElementPoint = (
|
export const snapLinearElementPoint = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
editingElement: ExcalidrawLinearElement,
|
editingElement: ExcalidrawLinearElement,
|
||||||
editingPointIndex: number,
|
|
||||||
pointerPosition: GlobalPoint,
|
pointerPosition: GlobalPoint,
|
||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
event: KeyboardModifiersObject,
|
event: KeyboardModifiersObject,
|
||||||
@@ -737,48 +827,31 @@ export const snapLinearElementPoint = (
|
|||||||
const nearestSnapsX: Snaps = [];
|
const nearestSnapsX: Snaps = [];
|
||||||
const nearestSnapsY: Snaps = [];
|
const nearestSnapsY: Snaps = [];
|
||||||
|
|
||||||
// Get reference snap points (all elements except the current point)
|
addNearestPointSnaps(
|
||||||
const referenceSnapPoints = getReferenceSnapPointsForLinearElementPoint(
|
getExternalReferenceSnapPointsForLinearElementPoint(
|
||||||
elements,
|
elements,
|
||||||
editingElement,
|
editingElement,
|
||||||
editingPointIndex,
|
app.state,
|
||||||
app.state,
|
elementsMap,
|
||||||
elementsMap,
|
),
|
||||||
options,
|
pointerPosition,
|
||||||
|
nearestSnapsX,
|
||||||
|
nearestSnapsY,
|
||||||
|
minOffset,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Find nearest snaps
|
if (options.includeSelfPoints) {
|
||||||
for (const referencePoint of referenceSnapPoints) {
|
addNearestPointSnaps(
|
||||||
const offsetX = referencePoint[0] - pointerPosition[0];
|
getSelfReferenceSnapPointsForLinearElementPoint(
|
||||||
const offsetY = referencePoint[1] - pointerPosition[1];
|
editingElement,
|
||||||
|
elementsMap,
|
||||||
if (Math.abs(offsetX) <= minOffset.x) {
|
options.selectedPointsIndices,
|
||||||
if (Math.abs(offsetX) < minOffset.x) {
|
),
|
||||||
nearestSnapsX.length = 0;
|
pointerPosition,
|
||||||
}
|
nearestSnapsX,
|
||||||
|
nearestSnapsY,
|
||||||
nearestSnapsX.push({
|
minOffset,
|
||||||
type: "point",
|
);
|
||||||
points: [pointerPosition, referencePoint],
|
|
||||||
offset: offsetX,
|
|
||||||
});
|
|
||||||
|
|
||||||
minOffset.x = Math.abs(offsetX);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Math.abs(offsetY) <= minOffset.y) {
|
|
||||||
if (Math.abs(offsetY) < minOffset.y) {
|
|
||||||
nearestSnapsY.length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
nearestSnapsY.push({
|
|
||||||
type: "point",
|
|
||||||
points: [pointerPosition, referencePoint],
|
|
||||||
offset: offsetY,
|
|
||||||
});
|
|
||||||
|
|
||||||
minOffset.y = Math.abs(offsetY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapOffset = {
|
const snapOffset = {
|
||||||
@@ -790,37 +863,36 @@ export const snapLinearElementPoint = (
|
|||||||
let pointSnapLines: SnapLine[] = [];
|
let pointSnapLines: SnapLine[] = [];
|
||||||
|
|
||||||
if (snapOffset.x !== 0 || snapOffset.y !== 0) {
|
if (snapOffset.x !== 0 || snapOffset.y !== 0) {
|
||||||
// Recalculate snap lines with the snapped position
|
|
||||||
const snappedPosition = pointFrom<GlobalPoint>(
|
const snappedPosition = pointFrom<GlobalPoint>(
|
||||||
pointerPosition[0] + snapOffset.x,
|
pointerPosition[0] + snapOffset.x,
|
||||||
pointerPosition[1] + snapOffset.y,
|
pointerPosition[1] + snapOffset.y,
|
||||||
);
|
);
|
||||||
|
|
||||||
const snappedSnapsX: Snaps = [];
|
const snappedSnapsX = nearestSnapsX
|
||||||
const snappedSnapsY: Snaps = [];
|
.filter(
|
||||||
|
(
|
||||||
|
snap,
|
||||||
|
): snap is PointSnap =>
|
||||||
|
snap.type === "point" && isCloseTo(snap.offset, snapOffset.x, 0.01),
|
||||||
|
)
|
||||||
|
.map((snap) => ({
|
||||||
|
type: "point" as const,
|
||||||
|
points: [snappedPosition, snap.points[1]] as [GlobalPoint, GlobalPoint],
|
||||||
|
offset: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
// Find the reference points that we're snapping to
|
const snappedSnapsY = nearestSnapsY
|
||||||
for (const referencePoint of referenceSnapPoints) {
|
.filter(
|
||||||
const offsetX = referencePoint[0] - snappedPosition[0];
|
(
|
||||||
const offsetY = referencePoint[1] - snappedPosition[1];
|
snap,
|
||||||
|
): snap is PointSnap =>
|
||||||
// Only include points that we're actually snapping to
|
snap.type === "point" && isCloseTo(snap.offset, snapOffset.y, 0.01),
|
||||||
if (isCloseTo(offsetX, 0, 0.01)) {
|
)
|
||||||
snappedSnapsX.push({
|
.map((snap) => ({
|
||||||
type: "point",
|
type: "point" as const,
|
||||||
points: [snappedPosition, referencePoint],
|
points: [snappedPosition, snap.points[1]] as [GlobalPoint, GlobalPoint],
|
||||||
offset: 0,
|
offset: 0,
|
||||||
});
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
if (isCloseTo(offsetY, 0, 0.01)) {
|
|
||||||
snappedSnapsY.push({
|
|
||||||
type: "point",
|
|
||||||
points: [snappedPosition, referencePoint],
|
|
||||||
offset: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pointSnapLines = createPointSnapLines(snappedSnapsX, snappedSnapsY);
|
pointSnapLines = createPointSnapLines(snappedSnapsX, snappedSnapsY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10462,8 +10462,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.lassoTrail.endPath();
|
this.lassoTrail.endPath();
|
||||||
this.previousPointerMoveCoords = null;
|
this.previousPointerMoveCoords = null;
|
||||||
|
|
||||||
SnapCache.setReferenceSnapPoints(null);
|
SnapCache.destroy();
|
||||||
SnapCache.setVisibleGaps(null);
|
|
||||||
|
|
||||||
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
|
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user