This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-11-23
Channels
- # announcements (26)
- # babashka (8)
- # babashka-sci-dev (3)
- # beginners (93)
- # biff (44)
- # calva (1)
- # cider (7)
- # clj-kondo (13)
- # cljdoc (1)
- # clojure (121)
- # clojure-australia (2)
- # clojure-europe (18)
- # clojure-nl (1)
- # clojure-norway (5)
- # clojure-uk (1)
- # clojurescript (35)
- # conjure (1)
- # core-async (2)
- # datalevin (6)
- # datomic (28)
- # emacs (25)
- # events (1)
- # fulcro (5)
- # introduce-yourself (2)
- # jobs (8)
- # leiningen (2)
- # off-topic (13)
- # other-languages (1)
- # podcasts-discuss (1)
- # polylith (7)
- # rdf (6)
- # re-frame (1)
- # reagent (53)
- # releases (3)
- # rewrite-clj (7)
- # scittle (5)
- # shadow-cljs (63)
- # specter (1)
- # squint (5)
- # tools-build (5)
- # xtdb (7)
Hey all! I’ve got a question about how to manage state in a simulation I’m running.
I’ve got some code running a fast simulation with a few parameters that I can change; in the hot loop, the simulation is reading the parameters from a ratom
.
I’ve also got a few sliders that update the value of the ratom
when they move.
What I want to do is write the simulation component in such a way that it can
• take the ratom
as input
• dereference the ratom in the hot loop to get its values
• NOT ACTUALLY RE-RENDER when the ratom value changes, as this triggers a recompile of the function called in the hot loop etc
I don’t want to recalculate updater
when !params
changes… I am fine if the parameters below re-render but the expensive thing is recalculating updater
.
(defn PhaseVectors [!params items]
(let [dt 3e-2
;;
updater (Lagrangian-collector state-deriv* [0 0 0]
{:compile? true
:parameters !params})]
[:<>
[box/Area
{:width 16
:height 16
:channels 2
:items items
:centeredX true
:centeredY true
:live true
:expr
(fn [emit q p _ _ t]
(updater #js [0 (normalize (+ q t)) p] (* dt (+ 0.01 (dec items))) dt emit))}]
[box/Vector
{:color 0x3090ff
:size 5
:end true}]]))
I mean, when the contents of the !params
ratom change. I guess it DOES have to recalculate if a different ratom
is supplied.
You should be able wrap a dereference in (binding [reagent.ratom/*ratom-context* nil] @foo)
to disable deref registering listener to the atom.
But, there might be better designs.
Does it have to be a ratom at all?
Or hmm, you want to trigger something when the ratom value changes, but not just the render?
reaction
might be useful also
@U061V0GG2 I have a few sliders that I want to re-render, since they are controlled by the value of !params
;
that’s the only reason I want it to be a ratom
@U061V0GG2 actually something weirder is going on since I am getting re-renders even when the args to my component aren’t changing. digging more
whoops, with-let
solves my problem vs let
!
Q: in this block, is the expectation that !params
will only evaluate clj->js
when !clj-params
changes?
(r/with-let [!clj-params (r/atom [9.8 1 1])
!params (r/track clj->js @!clj-params)]
...)
I'd write this as (reaction (clj>-js @!clj-params))
Easier to understand how reaction
works than track
I don't remember what track does it it given parametrs? That might not work
(defn get-params [a] (clj->js @a))
(r/track get-params !clj-params)
Might work differently, as the deref is now inside the fn call@U061V0GG2 what is happening is that if I do a reaction
or track
, and then I display a component like
[:pre (pr-str @!params)]
at the top level, then I can see in my console that the reaction is only called with @!params changes(reaction
(prn "converting")
(clj->js @!clj-params))
When dereffing the atom on with-let on r/track
call, it is maybe dereferenced only once on the with-let form and the track
doesn't do anything
but if I DON’T do that, and I pass my reaction in to the hot loop where it’s derefed, then I get thousands of converting
outputs to the console
when @!params
has not changed
@U061V0GG2 oh yeah that makes sense if track is not a macro
the caching behavior is definitely what I want here
@U061V0GG2 maybe a bug?
yeah, there is definitely something going on where deref calls to a reaction are slow until I do something like that :pre
block that forces it to cache somehow
There isn't enough examples for me to say what is the problem
I’ll try to get a repro
You need to be careful to construct reaction
in with-let
or form-2 component, each reaction
form creates a new reaction object. Same as creating new r/atom
.
Compared to r/track
which does some strange caching so that you can call it directly in the render code.
@U061V0GG2 yup I was careful, there’s no change between these two cases
(defn Repro []
(r/with-let [!clj-params (r/atom [9.8 1 1])
!params (reaction
(prn "converting")
(clj->js @!clj-params))]
(js/setInterval (fn [] @!params) 200)
(fn []
[:<>
[:pre (pr-str @!clj-params)]
[:pre (pr-str @!params)]
[:input
{:type :range
:min 1
:max 10
:step 1
:value (nth @!clj-params 0)
:on-change
(fn [target]
(let [v (.. target -target -value)
v (js/parseFloat v)]
@!params
(swap! !clj-params assoc 0 v)))}]])))
BOOM! Got a repro @U061V0GG2
If you comment out [:pre (pr-str @!params)]
and reload,
you’ll see lots of converting calls
and another converting
whenever you move the slider. If you uncomment, then you’ll ONLY see converting
when you move the slider
@U061V0GG2 maybe I am missing something here though about where calls are allowed to occur
with-let and fn
inside it doesn't make sense
Not sure what it does \o/ with-let generates probably something similar to form-2 component internally
@U061V0GG2 you mean the setinterval?
(defn Repro []
(r/with-let [!clj-params (r/atom [9.8 1 1])
!params (r/reaction
(prn "converting")
(clj->js @!clj-params))
_interval (js/setInterval (fn [] @!params) 200)]
[:<>
the setinterval is just there to trigger a deref in the same way that it’s happening in my animation loop in my bigger example
but I would guess that your repro there has the same “bug”?
I can see the problem, but not sure if it is a bug. If the pr-str deref to the reaction value is removed, the reaction is only dereferenced inside event handlers, not in render loop or other "reactive context".
exactly
So if the ratom is updated from input, something should be updated, if the render loop updates it, there is no need to rerender?
what I was hoping is that the first dereference from ANYWHERE would cache the value if !clj-params
does not change
and I am doing it this way because
1. my current code wants a js array in the animation loop, not a clj vector
2. if I use (r/atom #js […])
and try to update it from my sliders with aset
, then reagent doesn’t trigger a re-render, since my mutation means there’s no equality change
I can also fix this by changing my animation code to take a clojure vector here
https://github.com/reagent-project/reagent/blob/master/src/reagent/ratom.cljs#L459-L462
(defn Repro []
(r/with-let [!clj-params (r/atom [9.8 1 1])
!params (ratom/make-reaction
(fn []
(prn "converting")
(clj->js @!clj-params))
:auto-run true)
interval (js/setInterval (fn [] @!params) 200)]
[:<>
[:pre (pr-str @!clj-params)]
; [:pre (pr-str @!params)]
[:input
{:type :range
:min 1
:max 10
:step 1
:value (nth @!clj-params 0)
:on-change
(fn [target]
(let [v (.. target -target -value)
v (js/parseFloat v)]
@!params
(swap! !clj-params assoc 0 v)))}]]
(finally
(js/clearInterval interval))))
If reaction is derefferenced in non-reactive context and it doesn't use :auto-run mode, the value is not cached or something...
I might also suggest looking into React hooks, useEffect
can be easier to control which changes trigger the fn call: https://reactjs.org/docs/hooks-effect.html
oh yeah, that’s a good idea