Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: are cursors more update-efficient than a massive atomic hash-map? #47

Open
alexandergunnarson opened this issue Jun 12, 2015 · 10 comments

Comments

@alexandergunnarson
Copy link

I often tend to put my application state in an all-encompassing one-or-two-level-deep hash-map wrapped in an atom. Let's say that a DOM element :div#a reactively (i.e., via the rx macro) changes its color based on (-> @state :div#b :style :color). Now let's also say that :div#c reactively changes its color based on (-> @state :div#d :style :color). Div A depends on div B, and C on D, but [A and B] do not affect [C and D] at all, and vice versa.

Now suppose that (swap! state assoc-in [:div#b :style :color] :white) is called. Because the value of state is atomically modified, are the colors of divs A and C considered "dirty" and thus the values must be recalculated by calling the functions which rx created for each of them respectively? Or does freactive know that only div B's color was changed and so C's does not need to be recomputed?

This brings me to the question in the title: would cursors be more efficient in this case? Instead of writing code like so:

[:div#a {:style {:color (rx (-> @state :div#b :style :color))}}]

...should I write code like this?:

[:div#a {:style {:color (let [c (cursor state (comp :color :style :div#b)]
                          (rx @c)

That is, would cursor c only be invalidated if div B's color changed and not on every modification to state?

Thanks for your time!

@aaronc
Copy link
Owner

aaronc commented Jun 12, 2015

Yes, cursors should be more efficient if used correctly. Especially the new cursor implementation.

You can write (cursor state [:div#b :style :color]) instead of the comp if you like.

Although, it's not documented yet (haven't had the time) there is a fn in freactive.core assoc-in! that would be more efficient than (swap! state assoc-in ...). The functions designed for transients assoc! and dissoc! as well as freactive.core's update!, assoc-in! and update-in! all work with cursors and eliminate unnecessary notifications - this is the most efficient way. Using swap! + cursors naively should have some benefits over naive rx's too, but only at deeper levels of nesting.

@aaronc
Copy link
Owner

aaronc commented Jun 12, 2015

Also, (rx @c) is redundant. Just bind c directly as in [:div c].

@alexandergunnarson
Copy link
Author

Wow, thanks so much for the impressively quick reply! I really appreciate the helpful hints - I'll make good use of them.

Also, I really have so much respect for what you've accomplished in this library - about how easy-to-use and well-reasoned it all is, and about how you respond so quickly to new issues, etc. If you don't mind me asking, is this a side project, part of a master's project, part of a work assignment - how did this come about?

@aaronc
Copy link
Owner

aaronc commented Jun 12, 2015

Mostly to support commercial projects. I hacked together freactive.dom
initially from frustrations with om and reagent not working quite as
smoothly as I had hoped. Currently trying to put a commercial app into
production with this.

On Fri, Jun 12, 2015 at 6:52 PM, alexandergunnarson <
[email protected]> wrote:

Wow, thanks so much for the impressively quick reply! I really appreciate
the helpful hints - I'll make good use of them.

Also, I really have so much respect for what you've accomplished in this
library - about how easy-to-use and well-reasoned it all is, and about how
you respond so quickly to new issues, etc. If you don't mind me asking, is
this a side project, part of a master's project, part of a work assignment

  • how did this come about?


Reply to this email directly or view it on GitHub
#47 (comment).

@alexandergunnarson
Copy link
Author

Yeah, I had frustrations with reagent, even though it really is the simplest of all reactive ClojureScript UI libraries that I know of (besides freactive), and I never even tried om because I took one look at it and it just shouted "incidental complexity". I'm also trying to put a commercial app into production with this, but it may take a while because I'm the sole developer at the moment.

@aaronc
Copy link
Owner

aaronc commented Jun 12, 2015

Good luck! Curious - are there particular frustrations you had with reagent
that you feel are solved with freactive?

On Fri, Jun 12, 2015 at 7:07 PM, alexandergunnarson <
[email protected]> wrote:

Yeah, I had frustrations with reagent, even though it really is the
simplest of all reactive ClojureScript UI libraries that I know of (besides
freactive), and I never even tried om because I took one look at it and it
just shouted "incidental complexity". I'm also trying to put a commercial
app into production with this, but it may take a while because I'm the sole
developer at the moment.


Reply to this email directly or view it on GitHub
#47 (comment).

@pkobrien
Copy link

Can you explain why (rx @c) is redundant in this case?

@aaronc
Copy link
Owner

aaronc commented Jun 19, 2015

It's not only redundant, it adds overhead. Any IWatchable is bindable
directly.
On Fri, Jun 19, 2015 at 4:26 PM pkobrien [email protected] wrote:

Can you explain why (rx @c) is redundant in this case?


Reply to this email directly or view it on GitHub
#47 (comment).

@pkobrien
Copy link

In my experiments I'm having trouble with cursors that aren't "leaf nodes" or that point to something like a js/Date. object. The code in question is here: https://github.com/pkobrien/ing/blob/93aeda2b2fb6c89a5ba214ad4b7e38c5d2af485b/styling/src/app/core.cljs

Here is a simplified portion:

(defonce app-state
  (r/atom
   {:app-name "Styling"
    :mouse-pos {:x nil
                :y nil}
    }))

(defonce rc-mouse-pos
  (r/cursor app-state :mouse-pos))

(defonce rc-mouse-pos-x
  (r/cursor app-state [:mouse-pos :x]))

(defonce rc-mouse-pos-y
  (r/cursor app-state [:mouse-pos :y]))

(defn- listen-to-mousemove! []
  (dom/listen!
   js/window "mousemove"
   (fn [e]
     (assoc! rc-mouse-pos :x (.-clientX e) :y (.-clientY e)))))

(defn app-html []
  [:div
    [:p "Mouse position: " (rx (str "(" (:x @rc-mouse-pos) ", " (:y @rc-mouse-pos) ")"))]
    [:p "Mouse position: " (str "(" (:x @rc-mouse-pos) ", " (:y @rc-mouse-pos) ")")]  ; NOT working
    [:p "Mouse position: " (str "(" (:x rc-mouse-pos) ", " (:y rc-mouse-pos) ")")]  ; NOT working
    [:p "Mouse pos: " (str (vals @rc-mouse-pos))]  ; NOT working
    [:p "Mouse X: " rc-mouse-pos-x]
    [:p "Mouse Y: " rc-mouse-pos-y]
   ])

(dom/mount! "app" (app-html))

Based on this example it looks like rc-mouse-pos-x and rc-mouse-pos-y (leaf nodes) can be bound directly, but rc-mouse-pos (interior node) cannot.

@aaronc
Copy link
Owner

aaronc commented Jun 22, 2015

Yeah so, in order to bind a ref it has to be a direct child of an element. (str (vals @rc-mouse-pos)) for example just deref's rc-mouse-pos once and statically inserts the value in the element - there's no reactive binding. You need something like (rx (str (vals @rc-mouse-pos))). It's the same in reagent for example, it's just that everything in reagent is captured under big component bindings with the [my-component-fn ...] syntax (also supported by freactive but not really recommended). freactive is encourages being more specific and as a result can be more efficient... but it might also be a little more typing. For me, I like the simplicity of direct binding and it allows for more complex performance scenarios - in simple cases you probably won't notice much difference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants