perf: reuse masonry after cancelled resize

Issue: #322
User-Visible: no
This commit is contained in:
Matysh
2026-08-27 15:38:32 +03:00
parent 9b7a700fd6
commit 1edf5cb3a7
5 changed files with 47 additions and 15 deletions
File diff suppressed because one or more lines are too long
+10
View File
@@ -199,6 +199,9 @@ if (target) {
// The second gesture leaves the circle by much more than its hit radius.
// Pointer capture must keep the real browser stream alive; Esc then cancels
// the already-visible overlay without a second persistence write.
await page.evaluate(() => {
window.__resizeWallUnionBefore = window.__card._wallUnionGeometry();
});
const outsideDistance = Math.max(120, target.hitWidth * 2);
await page.mouse.move(...target.start);
await page.mouse.down();
@@ -211,11 +214,16 @@ if (target) {
await page.mouse.up();
await settle();
check('resize_pointer.escape_restores_config', await persistedGeometry(), stableAfterUndo);
check('resize_pointer.escape_reuses_pre_drag_wall_union', await page.evaluate(() =>
window.__card._wallUnionGeometry() === window.__resizeWallUnionBefore), true);
await page.waitForTimeout(650);
check('resize_pointer.escape_zero_extra_write', await page.evaluate(() => window.__resizeWrites.length), writesBefore + 2);
// A different pointer cannot take over an active drag. Losing capture for
// the owning pointer is an abort, never a commit of the visible preview.
await page.evaluate(() => {
window.__resizeWallUnionBefore = window.__card._wallUnionGeometry();
});
await page.mouse.move(...target.start);
await page.mouse.down();
await page.evaluate(({ x, y }) => {
@@ -246,6 +254,8 @@ if (target) {
await settle();
check('resize_pointer.capture_loss_restores_dom', await domHasSharedX(400), true);
check('resize_pointer.capture_loss_restores_config', await persistedGeometry(), stableAfterUndo);
check('resize_pointer.capture_loss_reuses_pre_drag_wall_union', await page.evaluate(() =>
window.__card._wallUnionGeometry() === window.__resizeWallUnionBefore), true);
await page.waitForTimeout(650);
check('resize_pointer.capture_loss_zero_extra_write', await page.evaluate(() => window.__resizeWrites.length), writesBefore + 2);
}
+2 -2
View File
File diff suppressed because one or more lines are too long
+11 -11
View File
@@ -2,7 +2,7 @@
"version": 1,
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"sourceFingerprint": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceFingerprint": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"captureScriptSha256": "ce2e9542fed9dade3085be87d16f69adb2ac8262893ad78ad966b1b9673f2983",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -14,7 +14,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "36223106c073f07d8cc3ecf8eaab37192ebb2687daba65c5c21047d0b7890de0"
},
"view-touch": {
@@ -25,7 +25,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "41e3ba67f8db0e98f26f484293af83ef937c369ca5ca6a59a3350d8954c906f4"
},
"space-create": {
@@ -36,7 +36,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "0dc024726327f60f4a9ceaf3044381691f81f1090af81d1812e870f22d9343ba"
},
"room-contour-close": {
@@ -47,7 +47,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "44cfaa95bd51e2cd628400e28db0ad8b2f0cd904385845bd402494f3f5c0d93c"
},
"plan-context-tray": {
@@ -58,7 +58,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "e0662429b423fb74151b583dcc2c8635b001b637d03bbf7a6b16aec46399c3f8"
},
"device-editor": {
@@ -69,7 +69,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "d0ffd31ce80bfde21ab75da356a5fc1af38246f2b301030880320620c228d89d"
},
"device-display-preview": {
@@ -80,7 +80,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "2cdabae1f89c3286e4fac0ce30f757ee1690b707ab8a5488748b7cd420626160"
},
"background-editor": {
@@ -91,7 +91,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "30147bb00a90eea7136b4cee30995f6e6a9217b5132f3e8d3ad7471413b1af8a"
},
"room-card": {
@@ -102,7 +102,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
},
"device-info": {
@@ -113,7 +113,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "b02ada1b0cc255adaac71a7493e154c2ce867dce60a65ab1b3cfdf39058605f2",
"sourceSha256": "d3f9271c3a64836abe0b0907e6f33c8c78cca844bc1cdb5f96d198d664f457bb",
"imageSha256": "dd492f53150b7149085daada5cce9eeae9bde9e7ea1d86679a54b3041f72f517"
}
}
+22
View File
@@ -1684,6 +1684,9 @@ class HouseplanCard extends LitElement {
d: number;
changed: string[];
rejectNotified: boolean;
/** Exact pre-drag masonry retained so Cancel can restore the real frame
* without rebuilding the same expensive boolean union under a new epoch. */
wallUnionBefore: ReturnType<typeof wallBodiesUnionPath>;
} | null = null;
private _rszEligibilityCache: {
key: string;
@@ -9016,11 +9019,15 @@ class HouseplanCard extends LitElement {
capturePointer(ev);
const plan = resolution.plan;
const start = this._svgPoint(ev);
const wallUnionKey = `${this._space}|${this._cfgEpoch}|${rooms.length}`;
const wallUnionBefore = this._wallUnionCache?.key === wallUnionKey
? this._wallUnionCache.value : null;
this._rszDrag = {
pid: ev.pointerId, start: [start[0], start[1]], roomId, plan,
opts: this._rszOptsFor(plan.a, plan.b),
rooms, openings: this._rszOpenings(), snap: this._rszSnapshot(),
moved: false, d: 0, changed: [...plan.roomIds], rejectNotified: false,
wallUnionBefore,
};
}
@@ -9126,12 +9133,27 @@ class HouseplanCard extends LitElement {
private _rszCancelDrag(): void {
const g = this._rszDrag;
if (!g) return;
const snapshotStillCurrent = this._rszSnapshot() === g.snap;
this._rszDrag = null;
this._rszLive = null;
// HP-1550-01/-03: a cancel just drops the overlay — the real config was
// never touched, so there is nothing to restore, no undo step and no write
this._rszPreview = null;
this._cfgEpoch++;
if (snapshotStillCurrent && g.wallUnionBefore) {
// The server-backed geometry did not change during the gesture. The
// preview advanced the structural epoch and temporarily selected its
// candidate union, but Cancel returns to the byte-identical pre-drag
// config. Alias that already-proved result under the fresh epoch instead
// of paying another O(n log n) polygon union on the next paint.
const space = this._spaceModel();
if (space) {
const key = `${this._space}|${this._cfgEpoch}|${space.rooms.length}`;
const entry = { key, value: g.wallUnionBefore };
lruWrite(this._wallUnionPool, key, entry, 8);
this._wallUnionCache = entry;
}
}
this.requestUpdate();
}