Improvements to performance

This commit is contained in:
alonso.torres 2021-01-19 18:44:32 +01:00
parent 9cfefbdb86
commit 27e83342f9
9 changed files with 92 additions and 62 deletions

View file

@ -26,6 +26,10 @@
#?(:cljs (and (not (nil? v)) (js/isFinite v)) #?(:cljs (and (not (nil? v)) (js/isFinite v))
:clj (and (not (nil? v)) (Double/isFinite v)))) :clj (and (not (nil? v)) (Double/isFinite v))))
(defn finite
[v default]
(if (finite? v) v default))
(defn abs (defn abs
[v] [v]
#?(:cljs (js/Math.abs v) #?(:cljs (js/Math.abs v)

View file

@ -88,14 +88,29 @@
[component] [component]
(get-in component [:objects (:id component)])) (get-in component [:objects (:id component)]))
;; Implemented with transient for performance
(defn get-children (defn get-children
"Retrieve all children ids recursively for a given object" "Retrieve all children ids recursively for a given object"
[id objects] [id objects]
;; TODO: find why does this sometimes come as a list instead of vector
(let [shapes (vec (get-in objects [id :shapes]))] (loop [result (transient [])
(if shapes pending (transient [])
(d/concat shapes (mapcat #(get-children % objects) shapes)) next id]
[]))) (let [children (get-in objects [next :shapes] [])
length (count children)]
(loop [i 0]
(when (< i length)
(let [child (nth children i)]
(conj! result child)
(conj! pending child)
(recur (inc i))))))
(let [length (count pending)]
(if (not= length 0)
(let [next (get pending (dec length))]
(pop! pending)
(recur result pending next))
(persistent! result)))))
(defn get-children-objects (defn get-children-objects
"Retrieve all children objects recursively for a given object" "Retrieve all children objects recursively for a given object"

View file

@ -11,6 +11,7 @@
(:require (:require
["slate" :as slate :refer [Editor Node Transforms Text]] ["slate" :as slate :refer [Editor Node Transforms Text]]
["slate-react" :as rslate] ["slate-react" :as rslate]
[app.common.math :as mth]
[app.common.attrs :as attrs] [app.common.attrs :as attrs]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.pages :as cp] [app.common.pages :as cp]
@ -217,6 +218,9 @@
(= (-> selected first :type) :text)) (= (-> selected first :type) :text))
(assoc-in [:workspace-local :edition] (-> selected first :id))))))) (assoc-in [:workspace-local :edition] (-> selected first :id)))))))
(defn not-changed? [old-dim new-dim]
(> (mth/abs (- old-dim new-dim)) 0.1))
(defn resize-text [id new-width new-height] (defn resize-text [id new-width new-height]
(ptk/reify ::resize-text (ptk/reify ::resize-text
ptk/WatchEvent ptk/WatchEvent
@ -238,12 +242,13 @@
(and (= :fixed grow-type) overflow-text (<= new-height shape-height)) (and (= :fixed grow-type) overflow-text (<= new-height shape-height))
(conj (update-overflow-text id false)) (conj (update-overflow-text id false))
(and (or (not= shape-width new-width) (not= shape-height new-height)) (and (or (not-changed? shape-width new-width) (not-changed? shape-height new-height))
(= grow-type :auto-width)) (= grow-type :auto-width))
(conj (dwt/update-dimensions [id] :width new-width) (conj (dwt/update-dimensions [id] :width new-width)
(dwt/update-dimensions [id] :height new-height)) (dwt/update-dimensions [id] :height new-height))
(and (not= shape-height new-height) (= grow-type :auto-height)) (and (not-changed? shape-height new-height)
(= grow-type :auto-height))
(conj (dwt/update-dimensions [id] :height new-height)))] (conj (dwt/update-dimensions [id] :height new-height)))]
(if (not (empty? events)) (if (not (empty? events))

View file

@ -13,6 +13,7 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[app.common.data :as d] [app.common.data :as d]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.pages.helpers :as helpers]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.util.storage :refer [storage]] [app.util.storage :refer [storage]]
[app.util.debug :refer [debug? debug-exclude-events logjs]])) [app.util.debug :refer [debug? debug-exclude-events logjs]]))

View file

@ -14,6 +14,7 @@
(def embed-ctx (mf/create-context false)) (def embed-ctx (mf/create-context false))
(def render-ctx (mf/create-context nil)) (def render-ctx (mf/create-context nil))
(def def-ctx (mf/create-context false)) (def def-ctx (mf/create-context false))
(def ghost-ctx (mf/create-context false))
(def current-route (mf/create-context nil)) (def current-route (mf/create-context nil))
(def current-team-id (mf/create-context nil)) (def current-team-id (mf/create-context nil))

View file

@ -21,6 +21,7 @@
[app.main.streams :as ms] [app.main.streams :as ms]
[app.main.ui.cursors :as cur] [app.main.ui.cursors :as cur]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.main.ui.context :as muc]
[app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.image :as image] [app.main.ui.shapes.image :as image]
[app.main.ui.shapes.rect :as rect] [app.main.ui.shapes.rect :as rect]
@ -52,12 +53,9 @@
false false
(let [o-shape (obj/get op "shape") (let [o-shape (obj/get op "shape")
n-frame (obj/get np "frame") n-frame (obj/get np "frame")
o-frame (obj/get op "frame") o-frame (obj/get op "frame")]
n-ghost (obj/get np "ghost?")
o-ghost (obj/get op "ghost?")]
(and (identical? n-shape o-shape) (and (identical? n-shape o-shape)
(identical? n-frame o-frame) (identical? n-frame o-frame))))))
(identical? n-ghost o-ghost))))))
(defn make-is-moving-ref (defn make-is-moving-ref
[id] [id]
@ -73,7 +71,7 @@
[props] [props]
(let [shape (obj/get props "shape") (let [shape (obj/get props "shape")
frame (obj/get props "frame") frame (obj/get props "frame")
ghost? (obj/get props "ghost?") ghost? (mf/use-ctx muc/ghost-ctx)
shape (-> (geom/transform-shape shape) shape (-> (geom/transform-shape shape)
(geom/translate-to-frame frame)) (geom/translate-to-frame frame))
opts #js {:shape shape opts #js {:shape shape
@ -84,14 +82,14 @@
moving-iref (mf/use-memo (mf/deps (:id shape)) (make-is-moving-ref (:id shape))) moving-iref (mf/use-memo (mf/deps (:id shape)) (make-is-moving-ref (:id shape)))
moving? (mf/deref moving-iref) moving? (mf/deref moving-iref)
svg-element? (and (= (:type shape) :svg-raw) svg-element? (and (= (:type shape) :svg-raw)
(not= :svg (get-in shape [:content :tag])))] (not= :svg (get-in shape [:content :tag])))
hide-moving? (and (not ghost?) moving?)]
(when (and shape (when (and shape (not (:hidden shape)))
(or ghost? (not moving?))
(not (:hidden shape)))
[:* [:*
(if-not svg-element? (if-not svg-element?
[:g.shape-wrapper {:style {:cursor (if alt? cur/duplicate nil)}} [:g.shape-wrapper {:style {:display (when hide-moving? "none")
:cursor (if alt? cur/duplicate nil)}}
(case (:type shape) (case (:type shape)
:path [:> path/path-wrapper opts] :path [:> path/path-wrapper opts]
:text [:> text/text-wrapper opts] :text [:> text/text-wrapper opts]

View file

@ -22,7 +22,8 @@
[app.util.timers :as ts] [app.util.timers :as ts]
[beicon.core :as rx] [beicon.core :as rx]
[okulary.core :as l] [okulary.core :as l]
[rumext.alpha :as mf])) [rumext.alpha :as mf]
[app.main.ui.context :as muc]))
(defn- frame-wrapper-factory-equals? (defn- frame-wrapper-factory-equals?
[np op] [np op]
@ -100,14 +101,15 @@
(mf/fnc deferred (mf/fnc deferred
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [tmp (mf/useState false) (let [ghost? (mf/use-ctx muc/ghost-ctx)
tmp (mf/useState false)
^boolean render? (aget tmp 0) ^boolean render? (aget tmp 0)
^js set-render (aget tmp 1)] ^js set-render (aget tmp 1)]
(mf/use-layout-effect (mf/use-layout-effect
(fn [] (fn []
(let [sem (ts/schedule-on-idle #(set-render true))] (let [sem (ts/schedule-on-idle #(set-render true))]
#(rx/dispose! sem)))) #(rx/dispose! sem))))
(if (unchecked-get props "ghost?") (if ghost?
(mf/create-element component props) (mf/create-element component props)
(when render? (mf/create-element component props)))))) (when render? (mf/create-element component props))))))
@ -120,7 +122,7 @@
[props] [props]
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
objects (unchecked-get props "objects") objects (unchecked-get props "objects")
ghost? (unchecked-get props "ghost?") ghost? (mf/use-ctx muc/ghost-ctx)
moving-iref (mf/use-memo (mf/deps (:id shape)) moving-iref (mf/use-memo (mf/deps (:id shape))
#(make-is-moving-ref (:id shape))) #(make-is-moving-ref (:id shape)))
@ -136,15 +138,16 @@
handle-context-menu (we/use-context-menu shape) handle-context-menu (we/use-context-menu shape)
handle-double-click (use-select-shape shape) handle-double-click (use-select-shape shape)
handle-mouse-down (we/use-mouse-down shape)] handle-mouse-down (we/use-mouse-down shape)
(when (and shape hide-moving? (and (not ghost?) moving?)]
(or ghost? (not moving?))
(not (:hidden shape))) (when (and shape (not (:hidden shape)))
[:g {:class (when selected? "selected") [:g.frame-wrapper {:class (when selected? "selected")
:on-context-menu handle-context-menu :style {:display (when hide-moving? "none")}
:on-double-click handle-double-click :on-context-menu handle-context-menu
:on-mouse-down handle-mouse-down} :on-double-click handle-double-click
:on-mouse-down handle-mouse-down}
[:& frame-title {:frame shape}] [:& frame-title {:frame shape}]

View file

@ -49,6 +49,7 @@
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [{:keys [id name x y width height grow-type] :as shape} (unchecked-get props "shape") (let [{:keys [id name x y width height grow-type] :as shape} (unchecked-get props "shape")
ghost? (mf/use-ctx muc/ghost-ctx)
selected-iref (mf/use-memo (mf/deps (:id shape)) selected-iref (mf/use-memo (mf/deps (:id shape))
#(refs/make-selected-ref (:id shape))) #(refs/make-selected-ref (:id shape)))
selected? (mf/deref selected-iref) selected? (mf/deref selected-iref)
@ -73,14 +74,15 @@
(mf/use-callback (mf/use-callback
(mf/deps id) (mf/deps id)
(fn [entries] (fn [entries]
(when (seq entries) (when (and (not ghost?) (seq entries))
;; RequestAnimationFrame so the "loop limit error" error is not thrown ;; RequestAnimationFrame so the "loop limit error" error is not thrown
;; https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded ;; https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
(timers/raf (timers/raf
#(let [width (obj/get-in entries [0 "contentRect" "width"]) #(let [width (obj/get-in entries [0 "contentRect" "width"])
height (obj/get-in entries [0 "contentRect" "height"])] height (obj/get-in entries [0 "contentRect" "height"])]
(log/debug :msg "Resize detected" :shape-id id :width width :height height) (when (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
(st/emit! (dwt/resize-text id (mth/ceil width) (mth/ceil height)))))))) (do (log/debug :msg "Resize detected" :shape-id id :width width :height height)
(st/emit! (dwt/resize-text id (mth/ceil width) (mth/ceil height))))))))))
text-ref-cb text-ref-cb
(mf/use-callback (mf/use-callback
@ -96,11 +98,12 @@
(mf/use-effect (mf/use-effect
(mf/deps @paragraph-ref handle-resize-text grow-type) (mf/deps @paragraph-ref handle-resize-text grow-type)
(fn [] (fn []
(when-let [paragraph-node @paragraph-ref] (when (not ghost?)
(let [observer (js/ResizeObserver. handle-resize-text)] (when-let [paragraph-node @paragraph-ref]
(log/debug :msg "Attach resize observer" :shape-id id :shape-name name) (let [observer (js/ResizeObserver. handle-resize-text)]
(.observe observer paragraph-node) (log/debug :msg "Attach resize observer" :shape-id id :shape-name name)
#(.disconnect observer))))) (.observe observer paragraph-node)
#(.disconnect observer))))))
[:> shape-container {:shape shape} [:> shape-container {:shape shape}

View file

@ -162,7 +162,6 @@
(let [hover (unchecked-get props "hover") (let [hover (unchecked-get props "hover")
selected (unchecked-get props "selected") selected (unchecked-get props "selected")
ids (unchecked-get props "ids") ids (unchecked-get props "ids")
ghost? (unchecked-get props "ghost?")
edition (unchecked-get props "edition") edition (unchecked-get props "edition")
data (mf/deref refs/workspace-page) data (mf/deref refs/workspace-page)
objects (:objects data) objects (:objects data)
@ -180,17 +179,14 @@
(if (= (:type item) :frame) (if (= (:type item) :frame)
[:& frame-wrapper {:shape item [:& frame-wrapper {:shape item
:key (:id item) :key (:id item)
:objects objects :objects objects}]
:ghost? ghost?}]
[:& shape-wrapper {:shape item [:& shape-wrapper {:shape item
:key (:id item) :key (:id item)}]))]
:ghost? ghost?}]))]
(when (not ghost?) [:& shape-outlines {:objects objects
[:& shape-outlines {:objects objects :selected selected
:selected selected :hover hover
:hover hover :edition edition}]]))
:edition edition}])]))
(mf/defc ghost-frames (mf/defc ghost-frames
{::mf/wrap-props false} {::mf/wrap-props false}
@ -206,18 +202,22 @@
(map gsh/transform-shape)) (map gsh/transform-shape))
selrect (->> (into [] xf sobjects) selrect (->> (into [] xf sobjects)
(gsh/selection-rect))] (gsh/selection-rect))
[:svg.ghost
{:x (:x selrect)
:y (:y selrect)
:width (:width selrect)
:height (:height selrect)
:style {:pointer-events "none"}}
[:g {:transform (str/fmt "translate(%s,%s)" (- (:x selrect-orig)) (- (:y selrect-orig)))} transform (when (and (mth/finite? (:x selrect-orig))
[:& frames (mth/finite? (:y selrect-orig)))
{:ids selected (str/fmt "translate(%s,%s)" (- (:x selrect-orig)) (- (:y selrect-orig))))]
:ghost? true}]]])) [:& (mf/provider ctx/ghost-ctx) {:value true}
[:svg.ghost
{:x (mth/finite (:x selrect) 0)
:y (mth/finite (:y selrect) 0)
:width (mth/finite (:width selrect) 100)
:height (mth/finite (:height selrect) 100)
:style {:pointer-events "none"}}
[:g {:transform transform}
[:& frames
{:ids selected}]]]]))
(defn format-viewbox [vbox] (defn format-viewbox [vbox]
(str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0))
@ -655,9 +655,9 @@
:selected selected :selected selected
:edition edition}] :edition edition}]
(when (= :move (:transform local)) [:g {:style {:display (when (not= :move (:transform local)) "none")}}
[:& ghost-frames {:modifiers (:modifiers local) [:& ghost-frames {:modifiers (:modifiers local)
:selected selected}]) :selected selected}]]
(when (seq selected) (when (seq selected)
[:& selection-handlers {:selected selected [:& selection-handlers {:selected selected