perf(editor): avoid rebuilding id sets on every resize (#12180)

perf: avoid rebuilding simultaneously-updated id set per element on multi-element resize

updateBoundElements() rebuilt a Set of all simultaneouslyUpdated ids on
every call. Multi-element resize/rotate call it once per selected element
with the same array, making each pointer move O(n^2). Cache the set per
array in a WeakMap.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nicolas Oga
2026-09-27 09:01:38 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 974f054baf
commit 84e3f5a40c
+19 -2
View File
@@ -1541,10 +1541,27 @@ const doesNeedUpdate = (
);
};
// PERF: callers updating many elements invoke `updateBoundElements()` for each
// of them with the same `simultaneouslyUpdated` array, so cache the id set
// per array to avoid rebuilding it on every call (O(n^2) on multi-element
// transforms)
const simultaneouslyUpdatedElementIdsCache = new WeakMap<
readonly ExcalidrawElement[],
ReadonlySet<ExcalidrawElement["id"]>
>();
const getSimultaneouslyUpdatedElementIds = (
simultaneouslyUpdated: readonly ExcalidrawElement[] | undefined,
): Set<ExcalidrawElement["id"]> => {
return new Set((simultaneouslyUpdated || []).map((element) => element.id));
): ReadonlySet<ExcalidrawElement["id"]> => {
if (!simultaneouslyUpdated) {
return new Set();
}
let ids = simultaneouslyUpdatedElementIdsCache.get(simultaneouslyUpdated);
if (!ids) {
ids = new Set(simultaneouslyUpdated.map((element) => element.id));
simultaneouslyUpdatedElementIdsCache.set(simultaneouslyUpdated, ids);
}
return ids;
};
export const getHeadingForElbowArrowSnap = (