It appears that disposal of reactive listeners is working properly now - my little performance test seems to demonstrate this.

This commit is contained in:
Aaron Craelius 2014-11-15 03:02:54 -05:00
parent 3d959161b1
commit d24ddf0125
4 changed files with 247 additions and 124 deletions

View file

@ -7,9 +7,20 @@
[org.clojure/clojurescript "0.0-2371"]]
:profiles
{:dev
{:plugins [[com.cemerick/austin "0.1.5"]]
{:plugins [[com.cemerick/austin "0.1.5"]
[lein-cljsbuild "1.0.3"] ;; 1.0.3 is a requirement
[lein-figwheel "0.1.5-SNAPSHOT"]]
:dependencies
[[com.cemerick/clojurescript.test "0.3.1"]]}}
[[com.cemerick/clojurescript.test "0.3.1"]
[figwheel "0.1.5-SNAPSHOT"]]
:resource-paths ["example"]
:cljsbuild {:builds [{:id "example"
:source-paths ["src/clojure" "test"]
:compiler {:output-to "resources/public/js/compiled/app.js"
:output-dir "resources/public/js/compiled/out"
:optimizations :none
:source-map true}}]}
}}
:source-paths ["src/clojure"]
:javac-options ["-Xlint:unchecked"]
:java-source-paths ["src/java"])

View file

@ -1,8 +1,11 @@
(ns freactive.experimental.dom2)
(ns freactive.experimental.dom2
(:require [freactive.core :as r]
[goog.object])
(:require-macros [freactive.macros :refer [rx]]))
;; ## Core Defintions
(def ^:private element-spec-lookup #js {})
(defonce ^:private element-state-lookup {})
(defprotocol IElementSpec
(-get-virtual-dom [x]))
@ -14,27 +17,42 @@
(defn- dom-node? [x]
(> (.-nodeType x) 0))
(defn- get-virtual-dom [x]
(cond
(dom-node? x)
(get-virtual-dom (aget element-spec-lookup x))
(string? x) x
(vector? x) x
:default (-get-virtual-dom x)))
(defn- get-element-state [x]
(get element-state-lookup x))
(defn- set-element-spec! [dom-node spec]
(aset element-spec-lookup dom-node spec))
(defn- get-virtual-dom [x]
(when x
(cond
(dom-node? x)
(when-let [state (get-element-state x)]
(get-virtual-dom (.-element-spec state)))
(string? x) x
(vector? x) x
:default (-get-virtual-dom x))))
(defn- reset-element-spec! [dom-node spec]
(set! (.-element-spec (get-element-state dom-node)) spec))
(defn- init-element-state! [dom-node element-spec]
(let [state #js {:disposed false :element-spec element-spec :childstates {}}]
(set! element-state-lookup (assoc element-state-lookup dom-node state))
state))
(defn- register-with-parent-state [parent-state child state]
(set! (.-childstates parent-state) (assoc (.-childstates parent-state) child state)))
(defn- unregister-from-parent-state [parent-state child]
(set! (.-childstates parent-state) (dissoc (.-childstates parent-state) child)))
(defn- get-element-spec [x]
(if (dom-node? x)
(aget element-spec-lookup x)
(when-let [state (aget element-state-lookup x)]
(.-element-spec state))
x))
(defn- get-transition [x transition-name]
(let [spec (get-element-spec x)]
(when-not (string? spec)
(get (meta spec) transition-name))))
(defprotocol IHasDOMNode
(-get-dom-node [x]))
@ -46,13 +64,68 @@
(defprotocol IRemove
(-remove! [x]))
(defn- remove-dom-node [x])
(defn- dispose-node
([dom-node]
(dispose-node dom-node (get-element-state dom-node)))
([dom-node state]
;(println "diposing" dom-node state)
(when state
(set! (.-disposed state) true)
(js-delete element-state-lookup dom-node)
(doseq [[child state] (.-childstates state)]
(dispose-node child state)))))
(defn- remove-dom-node [dom-node]
(let [state (aget element-state-lookup dom-node)]
(dispose-node dom-node state)
(when-let [parent (.-parentNode dom-node)]
(.removeChild parent dom-node)
(when state
(js-delete (.-parent-state state) state)))))
(defn remove! [x]
(if (dom-node? x)
(remove-dom-node x)
(-remove! x)))
;; ## Defining Transitions
(defn- wrap-element-spec [elem-spec]
(if (string? elem-spec)
(ElementSpec. elem-spec)
elem-spec))
(defn with-transitions [elem-spec transitions]
(if (satisfies? IDeref elem-spec)
(rx (with-transitions @elem-spec transitions))
(vary-meta (wrap-element-spec elem-spec) merge transitions)))
(defn- get-transition [x transition-name]
(let [spec (get-element-spec x)]
(when-not (string? spec)
(get (meta spec) transition-name))))
(defn- chain-transition [elem-spec transition-name transition-fn chain-fn]
(if (satisfies? IDeref elem-spec)
(rx (chain-transition @elem-spec transition-name transition-fn chain-fn))
(let [cur-transition-fn (get-transition elem-spec transition-name)
transition-fn (if cur-transition-fn
(chain-fn cur-transition-fn transition-fn)
transition-fn)]
(with-transitions elem-spec {transition-name transition-fn}))))
(defn prepend-transition [elem-spec transition-name transition-fn]
(chain-transition elem-spec transition-name transition-fn
(fn [cur-tx new-tx]
(fn [elem on-complete]
(new-tx elem (fn [elem _] (cur-tx elem on-complete)))))) )
(defn append-transition [elem-spec transition-name transition-fn]
(chain-transition elem-spec transition-name transition-fn
(fn [cur-tx new-tx]
(fn [elem on-complete]
(cur-tx elem (fn [elem _] (new-tx elem on-complete)))))) )
;; ## Polyfills
(defn request-animation-frame [f]
@ -68,7 +141,7 @@
(let [attr-name (name attr-name)]
(.setAttribute elem attr-name attr-value)))
(defn- on-value-ref-invalidated* [set-fn element attr-name ref]
(defn- on-value-ref-invalidated* [set-fn element attr-name ref node-state]
(when-let [[add-watch* remove-watch*] (r/get-add-remove-watch* ref)]
(let [f (fn on-value-ref-invalidated
([key ref _ _]
@ -78,7 +151,7 @@
(remove-watch* ref key)
(request-animation-frame
(fn [_]
(when (.-parentNode element)
(when-not (.-disposed node-state)
(add-watch* ref key on-value-ref-invalidated)
(set-fn element attr-name @ref))
)
@ -90,29 +163,29 @@
(add-watch* ref [element attr-name] f)))
(set-fn element attr-name @ref))
(defn bind-style-prop! [element attr-name attr-value]
(defn bind-style-prop! [element attr-name attr-value node-state]
(if (satisfies? cljs.core/IDeref attr-value)
(on-value-ref-invalidated* set-style-prop! element attr-name attr-value)
(on-value-ref-invalidated* set-style-prop! element attr-name attr-value node-state)
(set-style-prop! element attr-name attr-value)))
(defn listen! [element evt-name handler]
(.addEventListener element evt-name handler))
(defn bind-attr! [element attr-name attr-value]
(defn bind-attr! [element attr-name attr-value node-state]
(let [attr-name (name attr-name)]
(cond
(= "style" attr-name)
(do
(assert (map? attr-value))
(doseq [[p v] attr-value]
(bind-style-prop! element p v)))
(bind-style-prop! element p v node-state)))
(= [\o \n \-] (take 3 attr-name))
(listen! element (.substring attr-name 3) attr-value)
:default
(if (satisfies? cljs.core/IDeref attr-value)
(on-value-ref-invalidated* set-attr! element attr-name attr-value)
(on-value-ref-invalidated* set-attr! element attr-name attr-value node-state)
(set-attr! element attr-name attr-value)))))
;; From hiccup.compiler:
@ -135,7 +208,6 @@
(declare build-element)
(defn- replace-child [parent new-elem-spec cur-elem]
(let [cur-dom-node (get-dom-node cur-elem)
new-virtual-dom (get-virtual-dom new-elem-spec)]
@ -144,23 +216,54 @@
(= (.-nodeType cur-dom-node) 3))
(do
(set! (.-textContent cur-dom-node) new-virtual-dom)
(set-element-spec! cur-dom-node new-elem-spec)
(reset-element-spec! cur-dom-node new-elem-spec)
cur-elem)
(let [new-elem (build-element new-elem-spec)]
(.replaceChild
(get-dom-node parent)
(get-dom-node new-elem)
cur-dom-node)
(dispose-node cur-dom-node)
new-elem))))
(defn- append-child [parent new-elem]
;;(println "appending" new-elem "to" parent)
(let [new-elem (build-element new-elem)]
(.appendChild
(get-dom-node parent)
(get-dom-node new-elem))
new-elem)
new-elem))
(defn- replace-or-append-child [parent new-elem cur-elem]
(let [new-elem
(if cur-elem
(replace-child parent new-elem cur-elem)
(append-child parent new-elem))]
(when-let [parent-state (get-element-state parent)]
;(println "parent-state" parent parent-state)
(let [state (get-element-state new-elem)]
(set! (.-parent-state state) parent-state)
(register-with-parent-state parent-state new-elem state)))
new-elem))
(defn- do-show-element [parent new-elem cur-elem]
(when new-elem
(let [show (get-transition new-elem :on-show)
new-elem (replace-or-append-child parent new-elem cur-elem)]
(when show
(show new-elem nil)
new-elem)
new-elem)))
(defn- transition-element
([parent new-elem cur-elem]
;(println "transitioning" parent new-elem cur-elem)
(if cur-elem
(if-let [hide (get-transition cur-elem :on-hide)]
(hide cur-elem
(do-show-element parent new-elem cur-elem))
(do-show-element parent new-elem cur-elem))
(do-show-element parent new-elem cur-elem))))
(defn- clear-children! [parent]
(let [dom-node (get-dom-node parent)]
(loop []
@ -169,84 +272,71 @@
(.removeChild dom-node last-child)
(recur))))))
(defn- replace-or-append-child [parent new-elem cur-elem]
(if cur-elem
(replace-child parent new-elem cur-elem)
(append-child parent new-elem)))
;; Reactive Element Handling
(defn- do-show-element [parent new-elem cur-elem]
(let [show (get-transition new-elem :show)
new-elem (replace-or-append-child parent new-elem cur-elem)]
(when show
(show new-elem nil))
new-elem))
(deftype ReactiveElement [parent cur-element dirty updating disposed invalidate]
IRemove
(-remove! [this]
(set! (.-disposed this) true)
(when-not updating
(remove! @cur-element))
(when-let [parent-state (get-element-state parent)]
(unregister-from-parent-state parent-state this))))
(defn- transition-element
([parent new-elem cur-elem]
(if cur-elem
(if-let [hide (get-transition cur-elem :hide)]
(hide cur-elem
(do-show-element parent new-elem cur-elem))
(do-show-element parent new-elem cur-elem))
(do-show-element parent new-elem cur-elem))))
;(defn remove-child! [parent child]
; (if (instance? DerefElement child)
; (cancel-and-remove-deref-element parent child)
; (let [parent (as-dom-node parent)
; child (as-dom-node child)]
; (.removeChild parent child))))
;
(defn- append-deref-child [parent child-ref]
(if-let [[add-watch* remove-watch*] (r/get-add-remove-watch* child-ref)]
(let [cur-elem (atom nil)
cancellation-token (atom false)
f
(let [state (ReactiveElement. parent nil false false false nil)
animate
(fn animate [x]
;(println "animating")
(if (.-disposed state)
(remove! (.-cur-element state))
(do
(set! (.-dirty state) false)
(add-watch* child-ref state (.-invalidate state))
(let [new-elem @child-ref
cur (.-cur-element state)]
;(println "cur" cur)
(when (or (not cur)
(not= (get-virtual-dom cur) (get-virtual-dom new-elem)))
(set! (.-cur-element state)
(transition-element
parent
(append-transition new-elem :on-show
(fn [elem _]
(set! (.-updating state) false)
(if (.-disposed state)
(remove! elem)
(when
(.-dirty state)
(request-animation-frame animate)))))
cur)))))))
invalidate
(fn on-child-ref-invalidated
([key child-ref _ _]
(on-child-ref-invalidated key child-ref))
([cur-elem child-ref]
(remove-watch* child-ref cur-elem)
(request-animation-frame
(fn [x]
(when (and ;;(.-parentNode parent)
(not @cancellation-token)
)
(add-watch* child-ref cur-elem on-child-ref-invalidated)
(let [new-elem (as-element-spec @child-ref)
cur @cur-elem]
(when (or (not cur)
(not= (get-virtual-dom cur) (get-virtual-dom new-elem)))
(reset! cur-elem
(transition-element parent
;new-elem
(append-transition new-elem :show
(fn [elem _]
(when @cancellation-token
(-remove elem))))
cur)))))))))]
(reset! cur-elem (transition-element parent [:span] nil))
(f cur-elem child-ref)
(ReactiveElement. child-ref cur-elem cancellation-token))
(when-not (.-disposed state)
;(println "invalidating")
(set! (.-dirty state) true)
(when-not (.-updating state)
;(println "updating")
(set! (.-updating state) true)
(request-animation-frame animate)))))]
(set! (.-invalidate state) invalidate)
(set! (.-cur-element state) (transition-element parent (or @child-ref [:span]) nil))
(when-let [parent-state (get-element-state parent)]
(register-with-parent-state parent-state state state))
(add-watch* child-ref state invalidate)
state)
(transition-element parent @child-ref nil)))
(deftype ReactiveElement [spec-ref cur-elem cancellation-token]
IRemove
(-remove [_]
(reset! cancellation-token true)
(-remove @cur-elem))
;; Building Elements
IPrintWithWriter
(-pr-writer [a writer opts]
(-write writer "#<ReactiveElement: ")
(pr-writer cur-elem writer opts)
(-write writer " ")
(pr-writer spec-ref writer opts)
(-write writer " ")
(pr-writer cancellation-token writer opts)
(-write writer ">")))
(defn- append-child! [parent child]
(defn append-child! [parent child]
(cond
(satisfies? IDeref child)
(append-deref-child parent child)
@ -266,15 +356,23 @@
(let [virtual-dom (get-virtual-dom elem-spec)
node
(if (string? virtual-dom)
(.createTextNode js/document virtual-dom)
(let [node (.createTextNode js/document virtual-dom)]
(init-element-state! node elem-spec)
node)
(let [node (create-dom-node (first virtual-dom))
state (init-element-state! node elem-spec)
attrs? (second virtual-dom)
attrs (when (map? attrs?) attrs?)
children (if attrs (nnext virtual-dom) (next virtual-dom))]
(doseq [[k v] attrs]
(bind-attr! node k v))
(bind-attr! node k v state))
(when children
(append-children! node children))
node))]
(set-element-spec! node elem-spec)
node))
(defn mount! [element child]
;(println "mount!" element child)
(when-let [last-child (.-lastChild (get-dom-node element))]
(remove! last-child))
(append-child! element child))

View file

@ -2,7 +2,7 @@
(defn bench [f]
(time
(dotimes [i 30000]
(dotimes [i 100000]
(f))))
(defprotocol ITest1
@ -12,6 +12,10 @@
ITest1
(-do-something [this]))
(extend-protocol ITest1
js/Object
(-do-something [_]))
(defmulti test1 type)
(defn test1-string [x])
@ -29,10 +33,14 @@
(def js0 #js {})
(def js1 #js [])
(def clj0 {})
(bench #(set! (.-x js0) 0))
(bench #(aset js1 0 0))
(bench #(assoc clj0 :x 0))
(def element-spec-lookup #js {})
@ -108,3 +116,5 @@
(remove-dom-node x)
(-remove! x)))
()

View file

@ -1,12 +1,13 @@
(ns freactive.dom-perf
(:refer-clojure :exclude [atom])
(:require
[freactive.experimental.dom :as dom]
[freactive.core :refer [atom cursor]])
[freactive.experimental.dom2 :as dom]
[freactive.core :refer [atom cursor]]
[figwheel.client :as fw :include-macros true])
(:require-macros
[freactive.macros :refer [rx]]))
(defonce root (dom/append! (dom/get-body) [:div#root]))
(enable-console-print!)
(defonce mouse-x (atom 0))
@ -31,7 +32,7 @@
(defn circle [x y]
[:svg/circle {:cx x :cy y :r 2 :stroke "black" :fill "black"}])
(defonce n (atom 7))
(defonce n (atom 4))
(defn left-offset-x [i]
(rx (* (inc i) (/ @mouse-x (inc @n)))))
@ -49,35 +50,38 @@
[:div
[:svg/svg
{:width "100%" :height "100%"
{:width "100%" :height "100%"
:style {:position "absolute" :left 0 :top 0 }}
(circle mouse-x mouse-y)
(rx [:svg/g (for [i (range @n)] (circle (left-offset-x i) mouse-y))])
(rx [:svg/g (for [i (range @n)] (circle (right-offset-x i) mouse-y))])
(rx [:svg/g (for [j (range @n)] (circle mouse-x (top-offset-y j)))])
(rx [:svg/g (for [j (range @n)] (circle mouse-x (bottom-offset-y j)))])
(rx [:svg/g (for [i (range @n) j (range @n)]
(circle mouse-x mouse-y)
(rx [:svg/g (for [i (range @n)] (circle (left-offset-x i) mouse-y))])
(rx [:svg/g (for [i (range @n)] (circle (right-offset-x i) mouse-y))])
(rx [:svg/g (for [j (range @n)] (circle mouse-x (top-offset-y j)))])
(rx [:svg/g (for [j (range @n)] (circle mouse-x (bottom-offset-y j)))])
(rx [:svg/g (for [i (range @n) j (range @n)]
(circle (left-offset-x i) (top-offset-y j)))])
(rx [:svg/g (for [i (range @n) j (range @n)]
(rx [:svg/g (for [i (range @n) j (range @n)]
(circle (right-offset-x i) (top-offset-y j)))])
(rx [:svg/g (for [i (range @n) j (range @n)]
(rx [:svg/g (for [i (range @n) j (range @n)]
(circle (left-offset-x i) (bottom-offset-y j)))])
(rx [:svg/g (for [i (range @n) j (range @n)]
(rx [:svg/g (for [i (range @n) j (range @n)]
(circle (right-offset-x i) (bottom-offset-y j)))])
[:svg/circle {:fill "red" :on-mousedown (fn [e] (swap! n dec))
:r 8 :cx 8 :cy 8}]
[:svg/circle {:fill "green" :on-mousedown (fn [e] (swap! n inc))
:r 8 :cx 24 :cy 8}]
]
[:span (rx (str @mouse-x ", " @mouse-y))
". n = " (rx (str @n))
". complexity = " (rx (str (let [n* @n n* (+ 1 (* 2 n*))]
(* n* n*))))
[:button {:on-click (fn [e]
(println e)
(swap! n dec))} "-"]
[:button {:on-click (fn [e]
(println e)
(swap! n inc))} "+"]
]
])
]])
(dom/mount! root (view))
(dom/mount! (.getElementById js/document "root") (view))
(fw/watch-and-reload
;; :websocket-url "ws://localhost:3449/figwheel-ws" default
;;:jsload-callback (fn [] (print "reloaded"))
)