♻️ Look up head components once and pair switch shapes first

- find-ref-shape, find-content-ref-shape, find-ref-component and the
  walk that finds the container of a main each found a head's file
  data and component the same way: the local file or a library, then
  the component, deleted or not. ctf/head-component gives both, or nil
  when they are not there.
- ctf/get-component-shape, the child of a component by id, shared its
  name with ctn/get-component-shape, the top copy root of a shape; it
  is get-component-child.
- generate-keep-touched found, in one reduce, the shape in the new
  copy that each touched child of the old copy relates to, carried the
  child's overrides into it or kept a swapped child in its place, and
  collected the parents of the related shapes in a binding named
  parents-of-swapped, though it held the parent of every related
  shape. It now pairs the shapes first (the old child, the main shape
  it refers to, the related new shape, and whether the old child was
  swapped), then carries each pair in a reduce, and gives the parents
  from the pairs. The pairing reads the page as it was before the
  switch, as the reduce did, so the result is the same.

No behavior change: the golden masters and the suites are unchanged.

Benchmark (lower quantile, ms; baseline / now / ratio):
  deepest-edit-through-three-levels    35.2 /  16.5 / 0.47
  edit-through-a-chain-of-dependents   68.9 /  34.4 / 0.50
  library-update-into-copies           83.1 /  37.7 / 0.45
  main-edit-among-many-shapes           4.8 /   2.6 / 0.53
  main-edit-into-10-copies              1.8 /   0.7 / 0.42
  main-edit-into-100-copies            15.4 /   8.4 / 0.55
  main-edit-into-1000-copies          153.1 /  77.9 / 0.51
  variant-switch-into-copies           32.1 /  18.2 / 0.57

AI-assisted-by: claude-opus-5-5
This commit is contained in:
alonso.torres 2026-10-15 00:15:10 +02:00
parent fa193a07f6
commit a54124536c
4 changed files with 63 additions and 88 deletions

View File

@ -241,7 +241,7 @@
When walking up through nested component copies, each intermediate component
may live in a different library/file. We resolve the right file-data for each
component via `ctf/find-component-file` so we can locate components that span
component via `ctf/head-component` so we can locate components that span
multiple libraries."
[container-inst shape-inst shape-main library file libraries component]
(loop [shape-inst' shape-inst
@ -251,17 +251,11 @@
(if (some? (ctn/get-shape container (:id shape-main)))
container
(let [parent (ctn/get-shape container-inst (:parent-id shape-inst'))
shape-inst' (ctn/get-head-shape (:objects container-inst) parent)
next-file (some-> shape-inst'
:component-file
(->> (ctf/find-component-file file libraries)))
next-data (some-> next-file :data)
component' (when next-data
(or (ctkl/get-component next-data (:component-id shape-inst'))
(ctkl/get-deleted-component next-data (:component-id shape-inst'))))]
(if (some? component')
(recur shape-inst' next-data component')
nil))))))
shape-inst' (ctn/get-head-shape (:objects container-inst) parent)]
(when-let [[next-data component'] (when (some? shape-inst')
(ctf/head-component file libraries shape-inst'
:include-deleted? true))]
(recur shape-inst' next-data component')))))))
(defn- generate-sync-shape-direct
"Generate changes to synchronize one shape that is the root of a component

View File

@ -374,49 +374,31 @@
;; Creates a map to quickly find a child of the orig-ref-shape by its shape-path
o-ref-shapes-p-map (into {} (map (juxt :id :shape-path)) o-ref-shapes-wp)
;; Process each touched children of the original-shape
[changes parents-of-swapped]
(reduce
(fn [[changes parent-of-swapped] orig-child-touched]
(let [;; The switched shape itself relates to the root of the new one
root? (= (:id orig-child-touched) (:id original-shape))
;; Each touched child of the original-shape, with the shape it refers
;; to in the main and the child of the new-shape that has the same
;; shape-path; the switched shape itself relates to the new root
pairs
(keep (fn [old]
(let [root? (= (:id old) (:id original-shape))
old-ref (if root?
orig-base-ref-shape
(find-shape-ref-child-of container libraries old (:id orig-base-ref-shape)))
new (get new-shapes-map (get o-ref-shapes-p-map (:id old-ref)))]
(when new
{:old old :old-ref old-ref :new new
:swapped? (and (not root?) (ctk/swapped? old))})))
orig-touched)
swapped? (and (not root?) (ctk/swapped? orig-child-touched))
;; orig-child-touched is in a copy. Get the referenced shape on the main component
orig-ref-shape (if root?
orig-base-ref-shape
(find-shape-ref-child-of container libraries orig-child-touched (:id orig-base-ref-shape)))
orig-ref-id (:id orig-ref-shape)
;; Get the shape path of the referenced main
shape-path (get o-ref-shapes-p-map orig-ref-id)
;; Get its related shape in the children of new-shape: the one that
;; has the same shape-path
related-shape-in-new (get new-shapes-map shape-path)
parents-of-swapped (if related-shape-in-new
(conj parent-of-swapped (:parent-id related-shape-in-new))
parent-of-swapped)
;; If there is a related shape, keep its data
changes
(if related-shape-in-new
(if swapped?
;; If the orig-child-touched was swapped, keep it
(keep-swapped-item changes related-shape-in-new orig-child-touched
ldata page libraries)
;; If the orig-child-touched wasn't swapped, copy
;; the touched attributes into it
(clsw/update-attrs-on-switch
changes related-shape-in-new orig-child-touched
new-shape original-shape orig-ref-shape container))
changes)]
[changes parents-of-swapped]))
[changes []]
orig-touched)]
[changes parents-of-swapped]))
;; A swapped child is kept as it is, and the others carry their
;; touched attributes into their new child
changes
(reduce (fn [changes {:keys [old old-ref new swapped?]}]
(if swapped?
(keep-swapped-item changes new old ldata page libraries)
(clsw/update-attrs-on-switch changes new old new-shape original-shape old-ref container)))
changes
pairs)]
[changes (mapv (comp :parent-id :new) pairs)]))
(defn- shape-index
"Position of `shape-id` among the children of `parent-id`; 0 when it is not

View File

@ -326,6 +326,17 @@
file
(get libraries component-file)))
(defn head-component
"The file data and the component of the head `head`, found in the local
`file` or in `libraries`, as `[file-data component]`; nil when they are
not there."
[file libraries head & {:keys [include-deleted?]}]
(let [file-data (:data (find-component-file file libraries (:component-file head)))
component (when (some? file-data)
(ctkl/get-component file-data (:component-id head) include-deleted?))]
(when (some? component)
[file-data component])))
(defn get-component
"Retrieve a component from a library."
[libraries library-id component-id & {:keys [include-deleted?] :or {include-deleted? false}}]
@ -373,7 +384,7 @@
(ctn/get-shape (:main-instance-id component)))
(ctk/get-deleted-component-root component)))
(defn get-component-shape
(defn get-component-child
"Retrieve one shape in the component by id. If with-context? is true, add the
file and container where the shape resides in its metadata."
[file-data component shape-id & {:keys [with-context?] :or {with-context? false}}]
@ -400,7 +411,7 @@
"Retrieve the shape in the component that is referenced by the instance shape."
[file-data component shape & {:keys [with-context?] :or {with-context? false}}]
(when (:shape-ref shape)
(get-component-shape file-data component (:shape-ref shape) :with-context? with-context?)))
(get-component-child file-data component (:shape-ref shape) :with-context? with-context?)))
(defn get-content-ref-shape
"Retrieve the shape in the component whose content the instance shape
@ -421,31 +432,25 @@
"Locate the nearest component in the local file or libraries, and retrieve the shape
referenced by the instance shape."
[file container libraries shape & {:keys [include-deleted? with-context?] :or {include-deleted? false with-context? false}}]
(let [find-ref-shape-in-head
(fn [head-shape]
(let [component-file (find-component-file file libraries (:component-file head-shape))
component (when (some? component-file)
(ctkl/get-component (:data component-file) (:component-id head-shape) include-deleted?))]
(when (some? component)
(get-ref-shape (:data component-file) component shape :with-context? with-context?))))]
(some find-ref-shape-in-head (ctn/get-parent-heads (:objects container) shape))))
(some (fn [head]
(when-let [[file-data component] (head-component file libraries head
:include-deleted? include-deleted?)]
(get-ref-shape file-data component shape :with-context? with-context?)))
(ctn/get-parent-heads (:objects container) shape)))
(defn find-content-ref-shape
"Retrieve the shape whose content the instance shape follows: the main root
of its own component for a swapped head, the referenced shape otherwise."
[file container libraries shape & {:keys [include-deleted? with-context?] :or {include-deleted? false with-context? false}}]
(if (ctk/swapped? shape)
(let [component-file (find-component-file file libraries (:component-file shape))
file-data (:data component-file)
component (when (some? component-file)
(ctkl/get-component file-data (:component-id shape) include-deleted?))
root (when (some? component)
(get-component-root file-data component))]
(cond-> root
(and (some? root) with-context?)
(with-meta {:file {:id (:id file-data)
:data file-data}
:container (get-component-container file-data component)})))
(when-let [[file-data component] (head-component file libraries shape
:include-deleted? include-deleted?)]
(let [root (get-component-root file-data component)]
(cond-> root
(and (some? root) with-context?)
(with-meta {:file {:id (:id file-data)
:data file-data}
:container (get-component-container file-data component)}))))
(find-ref-shape file container libraries shape
:include-deleted? include-deleted?
:with-context? with-context?)))
@ -523,18 +528,12 @@
"Locate the nearest component in the local file or libraries that is referenced by the
instance shape."
[file page libraries shape & {:keys [include-deleted?] :or {include-deleted? false}}]
(let [find-ref-component-in-head
(fn [head-shape]
(let [component-file (find-component-file file libraries (:component-file head-shape))
component (when (some? component-file)
(ctkl/get-component (:data component-file)
(:component-id head-shape)
include-deleted?))]
(when (some? component)
(when (get-ref-shape (:data component-file) component shape)
component))))]
(some find-ref-component-in-head (ctn/get-parent-copy-heads (:objects page) shape))))
(some (fn [head]
(when-let [[file-data component] (head-component file libraries head
:include-deleted? include-deleted?)]
(when (get-ref-shape file-data component shape)
component)))
(ctn/get-parent-copy-heads (:objects page) shape)))
(declare find-remote-shape*)

View File

@ -249,7 +249,7 @@
(t/is (= #{(thi/id :main) (thi/id :main-child)}
(set (map :id (ctf/get-component-shapes data component)))))
(t/testing "a shape of the component, with the component as its container"
(let [child (ctf/get-component-shape data component (thi/id :main-child)
(let [child (ctf/get-component-child data component (thi/id :main-child)
:with-context? true)]
(t/is (= (thi/id :main-child) (:id child)))
(t/is (= :component (:type (:container (meta child)))))))