This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-03-14
Channels
- # announcements (3)
- # babashka-sci-dev (22)
- # beginners (6)
- # calva (36)
- # cljsrn (1)
- # clojure (59)
- # clojure-europe (31)
- # clojure-france (3)
- # clojure-gamedev (1)
- # clojure-nl (1)
- # clojure-norway (1)
- # clojure-uk (4)
- # clojurescript (6)
- # conjure (1)
- # cursive (11)
- # data-oriented-programming (1)
- # datahike (2)
- # docker (8)
- # duct (4)
- # emacs (1)
- # figwheel-main (5)
- # kaocha (1)
- # leiningen (8)
- # lsp (64)
- # malli (10)
- # membrane (5)
- # nrepl (11)
- # off-topic (5)
- # portal (6)
- # quil (9)
- # reagent (62)
- # reitit (15)
- # releases (3)
- # ring-swagger (2)
- # shadow-cljs (36)
- # specter (2)
- # tools-deps (21)
I'm trying to profile my app and seeing a ton of G_number
with reason "rendered for the first time" what can I do to locate the problem?
Those G__number
look like they're coming from (gensym)
. Why it's there - no clue.
But the problem is not those tiny components but rather the nyancad.mosaic.editor.schematic_elements
component - it takes the longest time by itself.
Well okay I have a pretty good idea what the problem is, I'm using higher order functions as reagent components, so I think those are just the names of anonymous functions you're seeing I'm guessing.
This is schematic_elements
(defn schematic-elements [schem]
[:<>
(for [[k v] schem
:when (= "wire" (:cell v))]
^{:key k} [(get-model ::bg v) k v])
(for [[k v] schem
:when (not= "wire" (:cell v))]
^{:key k} [(get-model ::bg v) k v])
(for [[k v] schem]
^{:key k} [(get-model ::sym v) k v])
(for [[k v] schem]
^{:key k} [(get-model ::conn v) k v])])
Yeah, I know where the repo is. :) Looking at the overall code right now. Try using JS profiler instead of the React one and see what takes most of the time.
Also, why are you using #'
in CLJS? I know that it's "that one weird trick" to make some functions REPL-friendly, but that's in CLJ.
Because that function is an advance declaration that gets defined later I think so without that it'd just store undefined in the map I think
Tried with set!
- works without #'
just fine as well.
But I would definitely reconsider that approach. Using the namespace as dynamic data storage is not great.
You can definitely move most if not all that stuff out of init
. You run init
right after loading the editor's JS bundle anyway, and initializing those particular vars doesn't require anything that's not available right then and there. When the namespace is being loaded, all that js/window ...
stuff is already available. Unless you also call init
with some custom arguments somewhere.
Yea the idea is that I can re-init the app with a different design. Primarily when embedding in another app. It used to be statically initialized indeed.
Though it's not currently used in an embedded context, so I could consider statically initializing and using set!
if you absolutely need to reinitialise.
I don't see schematic-elements
in the JS profiler at all: https://share.firefox.dev/35X5IPd
Then I'd gather all such "almost static" values in a single ctx
map and pass it around wherever it's needed. This would also have a benefit if being able to embed multiple editors within the same app.
The avoid having to write long things like (get-in @...)
, just extract all them in their own getters. It's actually a decent rule of thumb - whenever you see yourself repeating (get-in x [:a :b :c])
more than a couple of times, extract it. Same with assoc
et al.
Those extracted functions would of course accept the ctx
context as the first argument. Then working with all such values will become easy.
> I don't see schematic-elements
in the JS profiler at all: https://share.firefox.dev/35X5IPd
That's a very neat web page, I had no idea Firefox had something like that.
With that being said, I have absolutely no clue what I'm looking at. Nothing there seem to even mention your code - there's only
stuff. Feels like you managed to profile the DevTools? :)
lol yea it's the fancy new debugger somehow I have no idea how it works
Alright, I managed to find some CLJS code being mentioned in there, with all file names replaced with <URL>
.
But that code is definitely in the minority, it's almost not there.
Is it just the built-in FF profiler or something else?
yea apparently they replaced the profiler with this
I'll try what chrome says
Seems like
takes a lot of time.
And maybe it's because of your higher-order functions. Reagent can't cache components for them, so it creates a new React class every single time you use partial
in Hiccup.
Right yea in Chrome I also really don't see my own code show up
To fix that, instead of creating and returning a new function every time, you can return the same function along with an argument vector/map the eventual Hiccup vector will need. The code that needs to build a Hiccup vector out of it will then conj
something to that vector or assoc
something to that map and simply do (into [component] args) or
[component args]`.
I'd go with the map option since combining arguments and creating Hiccup vectors becomes incredibly easy.
This morning I was trying to do stuff with Conda and CouchDB and everyone is just telling me what not to do but not giving a useful way out. And here you are "oh I know where your code is so I'm taking a look" haha quite a contrast.
Uh... I removed all the #' and now all my symbols are gone, so that doesn't work... I mean it does if you live reload but not from a fresh start I guess
But using vectors instead of HOF made a ton of stuff not render at least.
Regarding #'
- weird. Maybe I tested incorrectly. Either way, I would definitely recommend getting rid of all that stuff with declare
, set!
, #'
.
> But using vectors instead of HOF made a ton of stuff not render at least.
In a good way? Or do you mean that some things are just missing?
In a good way
Not that performance is a lot better... still spending a lot of time in the function itself supposedly.
But according to the chrome profiler it really isn't my function itself that's taking all the time
On the reagent side there is sill some stuff that's rerendering because "props changed" which I'm not sure why that is exactly so lots more digging to do
The upside is, I only noticed this after a day of testing adding endless amounts of wires to fix some bugs. Under normal use the delays should be much more reasonable. But still, making it faster is always good. In particular because my target audience is kinda conservative and skeptical about web technology.
Just as an extra 2 c of information - (fn [])
is not equal to (fn [])
. So maybe you're passing dynamically constructed functions around, that would make some components re-render without a real need.
Yea probably most of these rerenders are HOF hacks I shouldn't be doing.
Also, you're profiling a dev build - the results will differ significantly from a prod build.
E.g. in your case as-element
takes a huge amount of time, relatively speaking, because it uses prewalk
at some point. In prod, it will simply call str
instead.
ah yea when will I learn not to profile dev builds :face_palm:
I do it all the time myself. :D When something is slow, it's will often be slow regardless of the build.
> Profiling support requires either a development or profiling build of React v16.5+.
Anyway enough profiling for today...
Yeah, I rarely use React profiling tools. Only when I know exactly that the problem is on that side and not with some algorithm somewhere.
React profiling will help you figure out why a component is being re-rendered. JS profiling will help you with everything else.
I have a re-frame app that I'm trying to add cytoscape to. On an event firing I want to add elements and edges to a graph in cytoscape. I tried this with a form-3 reagent function.
(defn graph []
(rg/create-class
{:component-did-update
(fn []
(let [cy (cytoscape #js {:container (.getElementById js/document "cy"),
:layout #js {:name "grid" :rows 2 :columns 2}})
elems @(rf/subscribe [:elements])]
(js/console.log "here!!!")
(.add cy elems)))
:reagent-render
(fn []
[:div#cy])}))
The problem here is, this bit:
cy (cytoscape #js {:container (.getElementById js/document "cy"),
:layout #js {:name "grid" :rows 2 :columns 2}})
Gives me a new instance of cytoscape each time, therefore anything I add just gets added to a new empty cytoscape graph.
I thought I could just have a re-frame reg-fx
, that I could then use from my reg-event-db, something like:
(rf/reg-fx
:scroll-current-code-into-view
(fn [elem last-elem]
;; do other stuff here, like find last item I added and change its style, etc...
(.add cy elem)))
But then at that point cy
is undefined.
I thought I could have a def
, something like:
(def cy (cytoscape #js {:container (.getElementById js/document "cy")
:layout #js {:name "grid" :rows 2 :columns 2}}))
But where do I put this, if I put at the bottom of app.cljs
, I get an error in the console. I presume because it's looking for cy
element in the dom before the DOM is ready.
The error:
main.js:2231 An error occurred when loading exfn.app.js
TypeError: Cannot read properties of null (reading 'className')
at Renderer.BRp$f.init (cytoscape.cjs.js:26944:9)
at (cytoscape.cjs.js:26924:3)
at new Renderer (cytoscape.cjs.js:31699:7)
at Core.initRenderer (cytoscape.cjs.js:14454:28)
at new Core (cytoscape.cjs.js:18642:3)
at cytoscape (cytoscape.cjs.js:31886:12)
at eval (app.cljs:123:57)
at eval (<anonymous>)
at Object.goog.globalEval (main.js:836:21)
at Object.env.evalLoad (main.js:2229:12)
Also tried a defonce
but again, no bueno.
I could have it in a form-3 function, and then have it re-render on each change to my graph elements, but that is potentially re-rendering the graph a LOT (I'd like to updating the graph every 25 ms or so, at least). So I think it will be a lot faster if I can use the cytoscape functions like add
on an existing graph.
Help, I'm confused 😕You're on the right path with rg/create-class
:
(defn graph []
(let [cy (cytoscape ...)]
(rg/create-class ...)))
^ that's a common pattern when dealing with stateful JS-based components.
The React lifecycle methods should update that cy
.Have you any experience with any libraries that can visualise graphs that might be better than cytoscape ?
;; Notice how I pass `elements` directly - don't miss re-frame and
;; stateful JS components. Put Reagent in between.
;; Just wrap this `graph` in another component and use `subscribe` there.
;; Pass the real value to this component.
(defn graph [#_{:keys [elements]}]
(let [cy (atom nil)
ref (react/createRef)]
(rg/create-class
{:reagent-render
(fn [_props]
[:div {:ref ref}])
:component-did-mount
(fn [_this]
(when-some [node (.-current ref)]
(reset! cy (cytoscape #js {:container node
:layout #js {:name "grid" :rows 2 :columns 2}}))))
:component-did-update
(fn [this [_ old-props]]
(let [{:keys [elements]} (rg/props this)]
(when (not= elements (:elements old-props))
;; Maybe you gotta remove the old ones first?
(.add @cy elements))))})))
I've never had to deal with Cytoscape so don't know whether what I had to deal with is better. In CLJS, I have only had to work with Vega. It was alright, nothing special. But the usage pattern is exactly like above.
Thanks for that, your code makes sense to me! Never realised I could put the rg/create-class within the let
Forgot to mention - never use .getElementById
. Except for the very first usage - when you render your whole app. React refs are the thing that should be used instead.
I'm just trying to visualise some code as its running as a graph that im updating as it runs
Maybe Vega would be enough there, no clue. But again - won't be different in its usage. It's just the pain of combining stateful JS components with Reagent. But you'll get comfortable with it soon enough, it's no big deal. Also, I definitely recommend going through Reagent examples. It has every single usage scenario that I've seen before covered - including this one.
Reagent examples meaning the intro page on the Reagent documentation?