Add better workspace file indexing strategy

Improve file indexes initialization on workspace.

Instead of initialize indexes for all pages only initialize
indexes for the loaded page.
This commit is contained in:
Andrey Antukh
2025-09-25 09:56:39 +02:00
parent e1519f0ee4
commit b9030fcc73
15 changed files with 330 additions and 281 deletions

View File

@@ -6,14 +6,29 @@
(ns app.common.files.indices (ns app.common.files.indices
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.uuid :as uuid])) [app.common.uuid :as uuid]))
(defn- generate-index
"An optimized algorithm for calculate parents index that walk from top
to down starting from a provided shape-id. Usefull when you want to
create an index for the whole objects or subpart of the tree."
[index objects shape-id parents]
(let [shape (get objects shape-id)
index (assoc index shape-id parents)
parents (cons shape-id parents)]
(reduce (fn [index shape-id]
(generate-index index objects shape-id parents))
index
(:shapes shape))))
(defn generate-child-all-parents-index (defn generate-child-all-parents-index
"Creates an index where the key is the shape id and the value is a set "Creates an index where the key is the shape id and the value is a set
with all the parents" with all the parents"
([objects] ([objects]
(generate-child-all-parents-index objects (vals objects))) (generate-index {} objects uuid/zero []))
([objects shapes] ([objects shapes]
(let [shape->entry (let [shape->entry
@@ -24,24 +39,25 @@
(defn create-clip-index (defn create-clip-index
"Retrieves the mask information for an object" "Retrieves the mask information for an object"
[objects parents-index] [objects parents-index]
(let [retrieve-clips (let [get-clip-parents
(fn [shape]
(let [shape-id (dm/get-prop shape :id)]
(cond-> []
(or (and (cfh/frame-shape? shape)
(not (:show-content shape))
(not= uuid/zero shape-id))
(cfh/bool-shape? shape))
(conj shape)
(:masked-group shape)
(conj (get objects (->> shape :shapes first))))))
xform
(comp (map (d/getf objects))
(mapcat get-clip-parents))
populate-with-clips
(fn [parents] (fn [parents]
(let [lookup-object (fn [id] (get objects id)) (into [] xform parents))]
get-clip-parents
(fn [shape]
(cond-> []
(or (and (= :frame (:type shape))
(not (:show-content shape))
(not= uuid/zero (:id shape)))
(cfh/bool-shape? shape))
(conj shape)
(:masked-group shape) (d/update-vals parents-index populate-with-clips)))
(conj (get objects (->> shape :shapes first)))))]
(into []
(comp (map lookup-object)
(mapcat get-clip-parents))
parents)))]
(-> parents-index
(update-vals retrieve-clips))))

View File

@@ -52,7 +52,7 @@
(->> (rx/from changes) (->> (rx/from changes)
(rx/merge-map (fn [[page-id changes]] (rx/merge-map (fn [[page-id changes]]
(log/debug :hint "update-indexes" :page-id page-id :changes (count changes)) (log/debug :hint "update-indexes" :page-id page-id :changes (count changes))
(mw/ask! {:cmd :index/update-page-index (mw/ask! {:cmd :index/update
:page-id page-id :page-id page-id
:changes changes}))) :changes changes})))
(rx/catch (fn [cause] (rx/catch (fn [cause]

View File

@@ -21,7 +21,6 @@
[app.common.types.component :as ctc] [app.common.types.component :as ctc]
[app.common.types.fills :as types.fills] [app.common.types.fills :as types.fills]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.main.data.changes :as dch] [app.main.data.changes :as dch]
@@ -70,7 +69,6 @@
[app.main.features.pointer-map :as fpmap] [app.main.features.pointer-map :as fpmap]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.router :as rt] [app.main.router :as rt]
[app.main.worker :as mw]
[app.render-wasm :as wasm] [app.render-wasm :as wasm]
[app.render-wasm.api :as api] [app.render-wasm.api :as api]
[app.util.dom :as dom] [app.util.dom :as dom]
@@ -84,7 +82,7 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
(log/set-level! :debug) (log/set-level! :trace)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Workspace Initialization ;; Workspace Initialization
@@ -158,18 +156,9 @@
(->> (fpmap/resolve-file file) (->> (fpmap/resolve-file file)
(rx/map :data) (rx/map :data)
(rx/map process-fills) (rx/map process-fills)
(rx/mapcat (rx/map
(fn [{:keys [pages-index] :as data}] (fn [data]
(->> (rx/from (seq pages-index)) (assoc file :data (d/removem (comp t/pointer? val) data))))))
(rx/mapcat
(fn [[id page]]
(let [page (update page :objects ctst/start-page-index)]
(->> (mw/ask! {:cmd :index/initialize-page-index :page page})
(rx/map (fn [_] [id page]))))))
(rx/reduce conj {})
(rx/map (fn [pages-index]
(let [data (assoc data :pages-index pages-index)]
(assoc file :data (d/removem (comp t/pointer? val) data))))))))))
(defn- check-libraries-synchronozation (defn- check-libraries-synchronozation
[file-id libraries] [file-id libraries]
@@ -280,6 +269,8 @@
(ptk/reify ::fetch-bundle (ptk/reify ::fetch-bundle
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ stream] (watch [_ _ stream]
(log/debug :hint "fetch bundle" :file-id (dm/str file-id))
(let [stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)] (let [stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)]
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features}) (->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
(get-file-object-thumbnails file-id)) (get-file-object-thumbnails file-id))
@@ -288,6 +279,7 @@
(fn [[file thumbnails]] (fn [[file thumbnails]]
(->> (resolve-file file) (->> (resolve-file file)
(rx/map (fn [file] (rx/map (fn [file]
(log/trace :hint "file resolved" :file-id file-id)
{:file file {:file file
:file-id file-id :file-id file-id
:features features :features features
@@ -357,6 +349,10 @@
(rx/map deref) (rx/map deref)
(rx/mapcat (rx/mapcat
(fn [{:keys [file]}] (fn [{:keys [file]}]
(log/debug :hint "bundle fetched"
:team-id (dm/str team-id)
:file-id (dm/str file-id))
(rx/of (dpj/initialize-project (:project-id file)) (rx/of (dpj/initialize-project (:project-id file))
(dwn/initialize team-id file-id) (dwn/initialize team-id file-id)
(dwsl/initialize-shape-layout) (dwsl/initialize-shape-layout)

View File

@@ -83,7 +83,7 @@
(rx/of (dwsh/add-shape shape {:no-select? (= tool :curve)})) (rx/of (dwsh/add-shape shape {:no-select? (= tool :curve)}))
(if (cfh/frame-shape? shape) (if (cfh/frame-shape? shape)
(rx/concat (rx/concat
(->> (mw/ask! {:cmd :selection/query (->> (mw/ask! {:cmd :index/query-selection
:page-id page-id :page-id page-id
:rect (:selrect shape) :rect (:selrect shape)
:include-frames? true :include-frames? true

View File

@@ -15,6 +15,7 @@
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.main.data.changes :as dch] [app.main.data.changes :as dch]
@@ -29,6 +30,7 @@
[app.main.errors] [app.main.errors]
[app.main.features :as features] [app.main.features :as features]
[app.main.router :as rt] [app.main.router :as rt]
[app.main.worker :as mw]
[app.render-wasm.shape :as wasm.shape] [app.render-wasm.shape :as wasm.shape]
[app.util.http :as http] [app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@@ -56,16 +58,21 @@
(some? metadata) (cf/resolve-file-media metadata) (some? metadata) (cf/resolve-file-media metadata)
(some? fill-image) (cf/resolve-file-media fill-image)))))) (some? fill-image) (cf/resolve-file-media fill-image))))))
(defn- get-page-cache
[state file-id page-id]
(dm/get-in state [:workspace-cache [file-id page-id]]))
(defn- initialize-page* (defn- initialize-page*
"Second phase of page initialization, once we know the page is "Second phase of page initialization, once we know the page is
available in the state" available in the state"
[file-id page-id page] [file-id page-id]
(ptk/reify ::initialize-page* (ptk/reify ::initialize-page*
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
;; selection; when user abandon the current page, the selection is lost (let [state (dsh/update-page state file-id page-id #(update % :objects ctst/start-page-index))
(let [local (dm/get-in state [:workspace-cache [file-id page-id]] default-workspace-local)] page (dsh/lookup-page state file-id page-id)
local (or (get-page-cache state file-id page-id) default-workspace-local)]
(-> state (-> state
(assoc :current-page-id page-id) (assoc :current-page-id page-id)
(assoc :workspace-local (assoc local :selected (d/ordered-set))) (assoc :workspace-local (assoc local :selected (d/ordered-set)))
@@ -75,11 +82,16 @@
(update :workspace-layout layout/load-layout-flags) (update :workspace-layout layout/load-layout-flags)
(update :workspace-global layout/load-layout-state)))) (update :workspace-global layout/load-layout-state))))
ptk/EffectEvent ptk/WatchEvent
(effect [_ _ _] (watch [_ state _]
(let [uris (into #{} xf:collect-file-media (:objects page))] (let [page (dsh/lookup-page state file-id page-id)
(->> (rx/from uris) uris (into #{} xf:collect-file-media (:objects page))]
(rx/subs! #(http/fetch-data-uri % false))))))) (rx/merge
(->> (rx/from uris)
(rx/map #(http/fetch-data-uri % false))
(rx/ignore))
(->> (mw/ask! {:cmd :index/initialize :page page})
(rx/ignore)))))))
(defn initialize-page (defn initialize-page
[file-id page-id] [file-id page-id]
@@ -89,9 +101,9 @@
(ptk/reify ::initialize-page (ptk/reify ::initialize-page
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(if-let [page (dsh/lookup-page state file-id page-id)] (if (dsh/lookup-page state file-id page-id)
(rx/concat (rx/concat
(rx/of (initialize-page* file-id page-id page) (rx/of (initialize-page* file-id page-id)
(dwth/watch-state-changes file-id page-id) (dwth/watch-state-changes file-id page-id)
(dwl/watch-component-changes)) (dwl/watch-component-changes))
(let [profile (:profile state) (let [profile (:profile state)

View File

@@ -344,7 +344,7 @@
(if (some? selrect) (if (some? selrect)
(->> (ask-worker (->> (ask-worker
{:cmd :selection/query {:cmd :index/query-selection
:page-id page-id :page-id page-id
:rect selrect :rect selrect
:include-frames? true :include-frames? true

View File

@@ -84,7 +84,7 @@
(let [value (get point coord) (let [value (get point coord)
vbox @refs/vbox vbox @refs/vbox
ranges [[(- value (/ 0.5 zoom)) (+ value (/ 0.5 zoom))]]] ranges [[(- value (/ 0.5 zoom)) (+ value (/ 0.5 zoom))]]]
(->> (mw/ask! {:cmd :snaps/range-query (->> (mw/ask! {:cmd :index/query-snap
:page-id page-id :page-id page-id
:frame-id frame-id :frame-id frame-id
:axis coord :axis coord
@@ -101,7 +101,7 @@
(mapv #(vector (- % snap-accuracy) (mapv #(vector (- % snap-accuracy)
(+ % snap-accuracy)))) (+ % snap-accuracy))))
vbox @refs/vbox] vbox @refs/vbox]
(->> (mw/ask! {:cmd :snaps/range-query (->> (mw/ask! {:cmd :index/query-snap
:page-id page-id :page-id page-id
:frame-id frame-id :frame-id frame-id
:axis coord :axis coord
@@ -217,7 +217,7 @@
(defn select-shapes-area (defn select-shapes-area
[page-id frame-id selected objects area] [page-id frame-id selected objects area]
(->> (mw/ask! {:cmd :selection/query (->> (mw/ask! {:cmd :index/query-selection
:page-id page-id :page-id page-id
:frame-id frame-id :frame-id frame-id
:include-frames? true :include-frames? true

View File

@@ -194,7 +194,7 @@
(if (mf/ref-val hover-disabled-ref) (if (mf/ref-val hover-disabled-ref)
(rx/of nil) (rx/of nil)
(->> (mw/ask-buffered! (->> (mw/ask-buffered!
{:cmd :selection/query {:cmd :index/query-selection
:page-id page-id :page-id page-id
:rect rect :rect rect
:include-frames? true :include-frames? true

View File

@@ -16,8 +16,6 @@
[app.worker.import] [app.worker.import]
[app.worker.index] [app.worker.index]
[app.worker.messages :as wm] [app.worker.messages :as wm]
[app.worker.selection]
[app.worker.snaps]
[app.worker.thumbnails] [app.worker.thumbnails]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[promesa.core :as p])) [promesa.core :as p]))

View File

@@ -9,26 +9,69 @@
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.changes :as ch] [app.common.files.changes :as ch]
[app.common.geom.rect :as grc]
[app.common.logging :as log]
[app.common.time :as ct]
[app.worker.impl :as impl] [app.worker.impl :as impl]
[app.worker.selection :as selection]
[app.worker.snap :as snap]
[okulary.core :as l])) [okulary.core :as l]))
(log/set-level! :info)
(defonce state (l/atom {:pages-index {}})) (defonce state (l/atom {:pages-index {}}))
(defmethod impl/handler :index/initialize-page-index (defmethod impl/handler :index/initialize
[{:keys [page] :as message}] [{:keys [page] :as message}]
(swap! state update :pages-index assoc (:id page) page) (let [tpoint (ct/tpoint-ms)]
(impl/handler (assoc message :cmd :selection/initialize-page-index)) (try
(impl/handler (assoc message :cmd :snaps/initialize-page-index))) (swap! state update :pages-index assoc (:id page) page)
(swap! state update ::selection selection/add-page page)
(swap! state update ::snap snap/add-page page)
(defmethod impl/handler :index/update-page-index (finally
(let [elapsed (tpoint)]
(log/dbg :hint "page indexed" :id (:id page) :elapsed elapsed ::log/sync? true))))
nil))
(defmethod impl/handler :index/update
[{:keys [page-id changes] :as message}] [{:keys [page-id changes] :as message}]
(let [tpoint (ct/tpoint-ms)]
(try
(let [old-page (dm/get-in @state [:pages-index page-id])
new-page (-> state
(swap! ch/process-changes changes false)
(dm/get-in [:pages-index page-id]))]
(let [old-page (dm/get-in @state [:pages-index page-id]) (swap! state update ::snap snap/update-page old-page new-page)
new-page (-> state (swap! state update ::selection selection/update-page old-page new-page))
(swap! ch/process-changes changes false) (finally
(dm/get-in [:pages-index page-id])) (let [elapsed (tpoint)]
message (assoc message (log/dbg :hint "page index updated" :id page-id :elapsed elapsed ::log/sync? true))))
:old-page old-page nil))
:new-page new-page)]
(impl/handler (assoc message :cmd :selection/update-page-index)) ;; FIXME: schema
(impl/handler (assoc message :cmd :snaps/update-page-index))))
(defmethod impl/handler :index/query-snap
[{:keys [page-id frame-id axis ranges bounds] :as message}]
(if-let [index (get @state ::snap)]
(let [match-bounds?
(fn [[_ data]]
(some #(or (= :guide (:type %))
(= :layout (:type %))
(grc/contains-point? bounds (:pt %))) data))
xform
(comp (mapcat #(snap/query index page-id frame-id axis %))
(distinct)
(filter match-bounds?))]
(into [] xform ranges))
[]))
;; FIXME: schema
(defmethod impl/handler :index/query-selection
[message]
(if-let [index (get @state ::selection)]
(selection/query index message)
[]))

View File

@@ -17,46 +17,62 @@
[app.common.types.modifiers :as ctm] [app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.util.quadtree :as qdt] [app.util.quadtree :as qdt]
[app.worker.impl :as impl] [clojure.set :as set]))
[clojure.set :as set]
[okulary.core :as l]))
;; FIXME: performance shape & rect static props ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; IMPL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:const padding-percent 0.10) (def ^:const padding-percent 0.10)
(defonce state (l/atom {})) (defn- index-shape
"A reducing function that ads a shape to the index"
[objects parents-index clip-index index shape]
(let [bounds
(cond
(and ^boolean (cfh/text-shape? shape)
^boolean (some? (:position-data shape))
^boolean (d/not-empty? (:position-data shape)))
(gst/shape->bounds shape)
(defn make-index-shape :else
[objects parents-index clip-parents-index] (grc/points->rect (:points shape)))
(fn [index shape]
(let [{:keys [x y width height]}
(cond
(and ^boolean (cfh/text-shape? shape)
^boolean (some? (:position-data shape))
^boolean (d/not-empty? (:position-data shape)))
(gst/shape->bounds shape)
:else bound
(grc/points->rect (:points shape))) #js {:x (dm/get-prop bounds :x)
:y (dm/get-prop bounds :y)
:width (dm/get-prop bounds :width)
:height (dm/get-prop bounds :height)}
shape-bound #js {:x x :y y :width width :height height} shape-id
(dm/get-prop shape :id)
parents (get parents-index (:id shape)) frame-id
clip-parents (get clip-parents-index (:id shape)) (dm/get-prop shape :frame-id)
frame (when (and (not= :frame (:type shape)) shape-type
(not= (:frame-id shape) uuid/zero)) (dm/get-prop shape :type)
(get objects (:frame-id shape)))]
(qdt/insert index
(:id shape)
shape-bound
(assoc shape
:frame frame
:clip-parents clip-parents
:parents parents)))))
(defn objects-bounds parents
(get parents-index shape-id)
clip-parents
(get clip-index shape-id)
frame
(when (and (not= :frame shape-type)
(not= frame-id uuid/zero))
(get objects frame-id))]
(qdt/insert index
shape-id
bound
(assoc shape
:frame frame
:clip-parents clip-parents
:parents parents))))
(defn- objects-bounds
"Calculates the bounds of the quadtree given a objects map." "Calculates the bounds of the quadtree given a objects map."
[objects] [objects]
(-> objects (-> objects
@@ -64,7 +80,7 @@
vals vals
gsh/shapes->rect)) gsh/shapes->rect))
(defn add-padding-bounds (defn- add-padding-bounds
"Adds a padding to the bounds defined as a percent in the constant `padding-percent`. "Adds a padding to the bounds defined as a percent in the constant `padding-percent`.
For a value of 0.1 will add a 20% width increase (2 x padding)" For a value of 0.1 will add a 20% width increase (2 x padding)"
[bounds] [bounds]
@@ -81,41 +97,48 @@
(defn- create-index (defn- create-index
[objects] [objects]
(let [shapes (-> objects (dissoc uuid/zero) vals) (let [parents-index (cfi/generate-child-all-parents-index objects)
parents-index (cfi/generate-child-all-parents-index objects) clip-index (cfi/create-clip-index objects parents-index)
clip-parents-index (cfi/create-clip-index objects parents-index) root-shapes (cfh/get-immediate-children objects uuid/zero)
bounds (-> root-shapes gsh/shapes->rect add-padding-bounds)
root-shapes (cfh/get-immediate-children objects uuid/zero)
bounds (-> root-shapes gsh/shapes->rect add-padding-bounds)
index-shape (make-index-shape objects parents-index clip-parents-index)
initial-quadtree (qdt/create (clj->js bounds))
index (reduce index-shape initial-quadtree shapes)]
index (reduce-kv #(index-shape objects parents-index clip-index %1 %3)
(qdt/create (clj->js bounds))
(dissoc objects uuid/zero))]
{:index index :bounds bounds})) {:index index :bounds bounds}))
;; FIXME: optimize
(defn- update-index (defn- update-index
[{index :index :as data} old-objects new-objects] [{index :index :as data} old-objects new-objects]
(let [changes? (fn [id] (let [object-changed?
(not= (get old-objects id) (fn [id]
(get new-objects id))) (not= (get old-objects id)
(get new-objects id)))
changed-ids (into #{} changed-ids
(comp (filter #(not= % uuid/zero)) (into #{}
(filter changes?) (comp (filter #(not= % uuid/zero))
(mapcat #(into [%] (cfh/get-children-ids new-objects %)))) (filter object-changed?)
(set/union (set (keys old-objects)) (mapcat #(into [%] (cfh/get-children-ids new-objects %))))
(set (keys new-objects))))
shapes (->> changed-ids (mapv #(get new-objects %)) (filterv (comp not nil?))) (set/union (set (keys old-objects))
parents-index (cfi/generate-child-all-parents-index new-objects shapes) (set (keys new-objects))))
clip-parents-index (cfi/create-clip-index new-objects parents-index)
new-index (qdt/remove-all index changed-ids) shapes
(->> changed-ids
(map #(get new-objects %))
(filterv (comp not nil?)))
index-shape (make-index-shape new-objects parents-index clip-parents-index) parents-index
index (reduce index-shape new-index shapes)] (cfi/generate-child-all-parents-index new-objects shapes)
clip-index
(cfi/create-clip-index new-objects parents-index)
index
(reduce #(index-shape new-objects parents-index clip-index %1 %2)
(qdt/remove-all index changed-ids)
shapes)]
(assoc data :index index))) (assoc data :index index)))
@@ -231,35 +254,36 @@
(map :id)) (map :id))
result))) result)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PUBLIC API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod impl/handler :selection/initialize-page-index (defn add-page
[{:keys [page] :as message}] "Add a page index to the state"
(letfn [(add-page [state {:keys [id objects] :as page}] [state {:keys [id objects] :as page}]
(assoc state id (create-index objects)))] (assoc state id (create-index objects)))
(swap! state add-page page)
nil))
(defmethod impl/handler :selection/update-page-index (defn update-page
[{:keys [page-id old-page new-page] :as message}] "Update page index on the state"
(swap! state update page-id [state old-page new-page]
(fn [index] (let [page-id (get old-page :id)]
(let [old-objects (:objects old-page) (update state page-id
new-objects (:objects new-page) (fn [index]
old-bounds (:bounds index) (let [old-objects (:objects old-page)
new-bounds (objects-bounds new-objects)] new-objects (:objects new-page)
old-bounds (:bounds index)
new-bounds (objects-bounds new-objects)]
;; If the new bounds are contained within the old bounds ;; If the new bounds are contained within the old bounds
;; we can update the index. Otherwise we need to ;; we can update the index. Otherwise we need to
;; re-create it. ;; re-create it.
(if (and (some? index) (if (and (some? index)
(grc/contains-rect? old-bounds new-bounds)) (grc/contains-rect? old-bounds new-bounds))
(update-index index old-objects new-objects) (update-index index old-objects new-objects)
(create-index new-objects))))) (create-index new-objects)))))))
nil)
(defmethod impl/handler :selection/query (defn query
[{:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?] [index {:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?]
:or {full-frame? false include-frames? false clip-children? true using-selrect? false} :or {full-frame? false include-frames? false clip-children? true using-selrect? false}}]
:as message}] (when-let [index (get index page-id)]
(when-let [index (get @state page-id)]
(query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?))) (query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children? using-selrect?)))

View File

@@ -4,7 +4,7 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.util.snap-data (ns app.worker.snap
"Data structure that holds and retrieves the data to make the snaps. "Data structure that holds and retrieves the data to make the snaps.
Internally is implemented with a balanced binary tree that queries by range. Internally is implemented with a balanced binary tree that queries by range.
https://en.wikipedia.org/wiki/Range_tree" https://en.wikipedia.org/wiki/Range_tree"

View File

@@ -1,40 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.worker.snaps
(:require
[app.common.geom.rect :as grc]
[app.util.snap-data :as sd]
[app.worker.impl :as impl]
[okulary.core :as l]))
(defonce state (l/atom {}))
;; Public API
(defmethod impl/handler :snaps/initialize-page-index
[{:keys [page] :as message}]
(swap! state sd/add-page page)
nil)
(defmethod impl/handler :snaps/update-page-index
[{:keys [old-page new-page] :as message}]
(swap! state sd/update-page old-page new-page)
nil)
(defmethod impl/handler :snaps/range-query
[{:keys [page-id frame-id axis ranges bounds] :as message}]
(let [match-bounds?
(fn [[_ data]]
(some #(or (= :guide (:type %))
(= :layout (:type %))
(grc/contains-point? bounds (:pt %))) data))]
(->> (into []
(comp (mapcat #(sd/query @state page-id frame-id axis %))
(distinct))
ranges)
(filter match-bounds?))))

View File

@@ -18,7 +18,7 @@
[frontend-tests.tokens.token-form-test] [frontend-tests.tokens.token-form-test]
[frontend-tests.util-range-tree-test] [frontend-tests.util-range-tree-test]
[frontend-tests.util-simple-math-test] [frontend-tests.util-simple-math-test]
[frontend-tests.util-snap-data-test])) [frontend-tests.worker-snap-test]))
(enable-console-print!) (enable-console-print!)
@@ -30,6 +30,8 @@
(defn init (defn init
[] []
(t/run-tests (t/run-tests
'frontend-tests.basic-shapes-test
'frontend-tests.data.workspace-colors-test
'frontend-tests.helpers-shapes-test 'frontend-tests.helpers-shapes-test
'frontend-tests.logic.comp-remove-swap-slots-test 'frontend-tests.logic.comp-remove-swap-slots-test
'frontend-tests.logic.components-and-tokens 'frontend-tests.logic.components-and-tokens
@@ -38,13 +40,11 @@
'frontend-tests.logic.groups-test 'frontend-tests.logic.groups-test
'frontend-tests.logic.pasting-in-containers-test 'frontend-tests.logic.pasting-in-containers-test
'frontend-tests.plugins.context-shapes-test 'frontend-tests.plugins.context-shapes-test
'frontend-tests.util-range-tree-test 'frontend-tests.tokens.import-export-test
'frontend-tests.util-snap-data-test
'frontend-tests.util-simple-math-test
'frontend-tests.basic-shapes-test
'frontend-tests.data.workspace-colors-test
'frontend-tests.tokens.logic.token-actions-test 'frontend-tests.tokens.logic.token-actions-test
'frontend-tests.tokens.logic.token-data-test 'frontend-tests.tokens.logic.token-data-test
'frontend-tests.tokens.import-export-test
'frontend-tests.tokens.style-dictionary-test 'frontend-tests.tokens.style-dictionary-test
'frontend-tests.tokens.token-form-test)) 'frontend-tests.tokens.token-form-test
'frontend-tests.util-range-tree-test
'frontend-tests.util-simple-math-test
'frontend-tests.worker-snap-test))

View File

@@ -4,12 +4,12 @@
;; ;;
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns frontend-tests.util-snap-data-test (ns frontend-tests.worker-snap-test
(:require (:require
[app.common.files.builder :as fb] [app.common.files.builder :as fb]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.util.snap-data :as sd] [app.worker.snap :as snap]
[cljs.pprint :refer [pprint]] [cljs.pprint :refer [pprint]]
[cljs.test :as t :include-macros true])) [cljs.test :as t :include-macros true]))
@@ -21,9 +21,9 @@
(fn [] (fn []
(uuid/custom 123456789 (swap! counter inc))))) (uuid/custom 123456789 (swap! counter inc)))))
(t/deftest test-create-index (t/deftest create-index
(t/testing "Create empty data" (t/testing "Create empty data"
(let [data (sd/make-snap-data)] (let [data (snap/make-snap-data)]
(t/is (some? data)))) (t/is (some? data))))
(t/testing "Add empty page (only root-frame)" (t/testing "Add empty page (only root-frame)"
@@ -32,8 +32,8 @@
(fb/add-page {:name "Page 1"}) (fb/add-page {:name "Page 1"})
(fb/get-current-page)) (fb/get-current-page))
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page))] (snap/add-page page))]
(t/is (some? data)))) (t/is (some? data))))
(t/testing "Create simple shape on root" (t/testing "Create simple shape on root"
@@ -48,10 +48,10 @@
:height 100})) :height 100}))
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
result-x (sd/query data (:id page) uuid/zero :x [0 100])] result-x (snap/query data (:id page) uuid/zero :x [0 100])]
(t/is (some? data)) (t/is (some? data))
@@ -82,11 +82,11 @@
page (fb/get-current-page state) page (fb/get-current-page state)
;; frame-id (::fb/last-id file) ;; frame-id (::fb/last-id file)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])] result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
(t/is (some? data)) (t/is (some? data))
(t/is (= (count result-zero-x) 3)) (t/is (= (count result-zero-x) 3))
@@ -116,11 +116,11 @@
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])] result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
(t/is (some? data)) (t/is (some? data))
(t/is (= (count result-zero-x) 3)) (t/is (= (count result-zero-x) 3))
@@ -137,13 +137,13 @@
frame-id (::fb/last-id state) frame-id (::fb/last-id state)
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100]) result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100]) result-frame-x (snap/query data (:id page) frame-id :x [0 100])
result-frame-y (sd/query data (:id page) frame-id :y [0 100])] result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
(t/is (some? data)) (t/is (some? data))
;; We can snap in the root ;; We can snap in the root
@@ -168,13 +168,13 @@
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100]) result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100]) result-frame-x (snap/query data (:id page) frame-id :x [0 100])
result-frame-y (sd/query data (:id page) frame-id :y [0 100])] result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
(t/is (some? data)) (t/is (some? data))
;; We can snap in the root ;; We can snap in the root
@@ -185,7 +185,7 @@
(t/is (= (count result-frame-x) 1)) (t/is (= (count result-frame-x) 1))
(t/is (= (count result-frame-y) 0))))) (t/is (= (count result-frame-y) 0)))))
(t/deftest test-update-index (t/deftest update-index
(t/testing "Create frame on root and then remove it." (t/testing "Create frame on root and then remove it."
(let [state (-> (fb/create-state) (let [state (-> (fb/create-state)
(fb/add-file {:name "Test"}) (fb/add-file {:name "Test"})
@@ -200,17 +200,17 @@
shape-id (::fb/last-id state) shape-id (::fb/last-id state)
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
state (-> state state (-> state
(fb/delete-shape shape-id)) (fb/delete-shape shape-id))
new-page (fb/get-current-page state) new-page (fb/get-current-page state)
data (sd/update-page data page new-page) data (snap/update-page data page new-page)
result-x (sd/query data (:id page) uuid/zero :x [0 100]) result-x (snap/query data (:id page) uuid/zero :x [0 100])
result-y (sd/query data (:id page) uuid/zero :y [0 100])] result-y (snap/query data (:id page) uuid/zero :y [0 100])]
(t/is (some? data)) (t/is (some? data))
(t/is (= (count result-x) 0)) (t/is (= (count result-x) 0))
@@ -231,16 +231,16 @@
page (fb/get-current-page state) page (fb/get-current-page state)
;; frame-id (::fb/last-id state) ;; frame-id (::fb/last-id state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
state (fb/delete-shape state shape-id) state (fb/delete-shape state shape-id)
new-page (fb/get-current-page state) new-page (fb/get-current-page state)
data (sd/update-page data page new-page) data (snap/update-page data page new-page)
result-x (sd/query data (:id page) uuid/zero :x [0 100]) result-x (snap/query data (:id page) uuid/zero :x [0 100])
result-y (sd/query data (:id page) uuid/zero :y [0 100])] result-y (snap/query data (:id page) uuid/zero :y [0 100])]
(t/is (some? data)) (t/is (some? data))
(t/is (= (count result-x) 0)) (t/is (= (count result-x) 0))
@@ -263,16 +263,16 @@
state (fb/close-board state) state (fb/close-board state)
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
state (fb/delete-shape state shape-id) state (fb/delete-shape state shape-id)
new-page (fb/get-current-page state) new-page (fb/get-current-page state)
data (sd/update-page data page new-page) data (snap/update-page data page new-page)
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100])] result-frame-x (snap/query data (:id page) frame-id :x [0 100])]
(t/is (some? data)) (t/is (some? data))
(t/is (= (count result-zero-x) 3)) (t/is (= (count result-zero-x) 3))
@@ -291,18 +291,18 @@
frame-id (::fb/last-id state) frame-id (::fb/last-id state)
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
new-page (-> (fb/delete-guide state guide-id) new-page (-> (fb/delete-guide state guide-id)
(fb/get-current-page)) (fb/get-current-page))
data (sd/update-page data page new-page) data (snap/update-page data page new-page)
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100]) result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100]) result-frame-x (snap/query data (:id page) frame-id :x [0 100])
result-frame-y (sd/query data (:id page) frame-id :y [0 100])] result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
(t/is (some? data)) (t/is (some? data))
;; We can snap in the root ;; We can snap in the root
@@ -325,17 +325,17 @@
guide-id (::fb/last-id file) guide-id (::fb/last-id file)
page (fb/get-current-page file) page (fb/get-current-page file)
data (-> (sd/make-snap-data) (sd/add-page page)) data (-> (snap/make-snap-data) (snap/add-page page))
new-page (-> (fb/delete-guide file guide-id) new-page (-> (fb/delete-guide file guide-id)
(fb/get-current-page)) (fb/get-current-page))
data (sd/update-page data page new-page) data (snap/update-page data page new-page)
result-zero-x (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y (sd/query data (:id page) uuid/zero :y [0 100]) result-zero-y (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x (sd/query data (:id page) frame-id :x [0 100]) result-frame-x (snap/query data (:id page) frame-id :x [0 100])
result-frame-y (sd/query data (:id page) frame-id :y [0 100])] result-frame-y (snap/query data (:id page) frame-id :y [0 100])]
(t/is (some? data)) (t/is (some? data))
;; We can snap in the root ;; We can snap in the root
(t/is (= (count result-zero-x) 0)) (t/is (= (count result-zero-x) 0))
@@ -358,8 +358,8 @@
frame-id (::fb/last-id state) frame-id (::fb/last-id state)
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
state (fb/update-shape state frame-id state (fb/update-shape state frame-id
(fn [shape] (fn [shape]
@@ -370,12 +370,12 @@
new-page (fb/get-current-page state) new-page (fb/get-current-page state)
data (sd/update-page data page new-page) data (snap/update-page data page new-page)
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100]) result-frame-x-1 (snap/query data (:id page) frame-id :x [0 100])
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300]) result-zero-x-2 (snap/query data (:id page) uuid/zero :x [200 300])
result-frame-x-2 (sd/query data (:id page) frame-id :x [200 300])] result-frame-x-2 (snap/query data (:id page) frame-id :x [200 300])]
(t/is (some? data)) (t/is (some? data))
(t/is (= (count result-zero-x-1) 0)) (t/is (= (count result-zero-x-1) 0))
@@ -396,8 +396,8 @@
shape-id (::fb/last-id state) shape-id (::fb/last-id state)
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) data (-> (snap/make-snap-data)
(sd/add-page page)) (snap/add-page page))
state (fb/update-shape state shape-id state (fb/update-shape state shape-id
(fn [shape] (fn [shape]
@@ -408,10 +408,10 @@
new-page (fb/get-current-page state) new-page (fb/get-current-page state)
;; FIXME: update ;; FIXME: update
data (sd/update-page data page new-page) data (snap/update-page data page new-page)
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [200 300])] result-zero-x-2 (snap/query data (:id page) uuid/zero :x [200 300])]
(t/is (some? data)) (t/is (some? data))
(t/is (= (count result-zero-x-1) 0)) (t/is (= (count result-zero-x-1) 0))
@@ -432,22 +432,22 @@
frame-id (::fb/last-id state) frame-id (::fb/last-id state)
page (fb/get-current-page state) page (fb/get-current-page state)
data (-> (sd/make-snap-data) (sd/add-page page)) data (-> (snap/make-snap-data) (snap/add-page page))
new-page (-> (fb/update-guide state (assoc guide :position 150)) new-page (-> (fb/update-guide state (assoc guide :position 150))
(fb/get-current-page)) (fb/get-current-page))
data (sd/update-page data page new-page) data (snap/update-page data page new-page)
result-zero-x-1 (sd/query data (:id page) uuid/zero :x [0 100]) result-zero-x-1 (snap/query data (:id page) uuid/zero :x [0 100])
result-zero-y-1 (sd/query data (:id page) uuid/zero :y [0 100]) result-zero-y-1 (snap/query data (:id page) uuid/zero :y [0 100])
result-frame-x-1 (sd/query data (:id page) frame-id :x [0 100]) result-frame-x-1 (snap/query data (:id page) frame-id :x [0 100])
result-frame-y-1 (sd/query data (:id page) frame-id :y [0 100]) result-frame-y-1 (snap/query data (:id page) frame-id :y [0 100])
result-zero-x-2 (sd/query data (:id page) uuid/zero :x [0 200]) result-zero-x-2 (snap/query data (:id page) uuid/zero :x [0 200])
result-zero-y-2 (sd/query data (:id page) uuid/zero :y [0 200]) result-zero-y-2 (snap/query data (:id page) uuid/zero :y [0 200])
result-frame-x-2 (sd/query data (:id page) frame-id :x [0 200]) result-frame-x-2 (snap/query data (:id page) frame-id :x [0 200])
result-frame-y-2 (sd/query data (:id page) frame-id :y [0 200])] result-frame-y-2 (snap/query data (:id page) frame-id :y [0 200])]
(t/is (some? data)) (t/is (some? data))