clerk

Alex 2024-12-13T10:10:46.812439Z

I'm trying to visualize and animate some of the Advent of Code solutions with Clerk. I'm using a slider control with ::clerk/sync and an atom that contains the pre-rendered frames and the current frame number to show. It looks like the following (see the attached clip). Pre-rendering is actually a performance optimization to avoid waiting for recompute. Question: how to make it even more streamlined, by avoiding to wait for all the queued recomputes to happen? 🙂

mkvlr 2024-12-13T10:49:44.658699Z

can you try to alter-var-root! clerk/recompute! to do nothing while a recomputation is in progress and see if that’s what you want?

mkvlr 2024-12-13T10:54:05.110079Z

something along

(in-ns 'nextjournal.clerk)

(def !recomputing? (atom false))

(defn recompute!
  "Recomputes the currently visible doc, without parsing it."
  []
  (if @!recomputing?
    (println "Skipping recompute...")
    (when-not (eval/cljs? @webserver/!doc)
      (reset! !recomputing? true)
      (binding [*ns* (:ns @webserver/!doc)]
        (let [{:keys [result time-ms]} (eval/time-ms (eval/eval-analyzed-doc @webserver/!doc))]
          (println (str "Clerk recomputed '" @!last-file "' in " time-ms "ms."))
          (webserver/update-doc! result)
          (reset! !recomputing? false))))))

Alex 2024-12-13T14:27:06.317249Z

nice hack, thanks. it does help for the toy example, but there is other (unrelated) problem with the actual 130×130 map and over 6,000 frames — it is painfully slow, the web client needs to reconnect often, and there is this warning on the server: Fri Dec 13 15:22:54 CET 2024 [server-loop] WARN - Max payload length 4m, get: 103069089 I guess that has to do with the approach where I put both the current frame number and all pre-rendered frames in a single atom that is synced between server and the client. is there a way to pass the rendered frames once and only sync the frame number, according to the slider position?

mkvlr 2024-12-13T14:37:00.031319Z

yes, you should be able to make a sync atom with just the frame number

mkvlr 2024-12-13T14:37:07.321769Z

and then just use that downstream

mkvlr 2024-12-13T14:38:23.405519Z

so the slider just modifies the frame number, and you keep the (nth rendered-frames frame-number) out of the client and keep it on the JVM

mkvlr 2024-12-13T14:39:02.050439Z

or you keep the slider purely in the browser

Alex 2024-12-13T14:39:03.474759Z

how do I refer to the rendered frames from the render-fn?

mkvlr 2024-12-13T14:39:25.270059Z

you don’t

mkvlr 2024-12-13T14:39:30.476229Z

you render in clojure

mkvlr 2024-12-13T14:39:53.206079Z

(clerk/html [:pre (nth rendered-frames @frame-number)])

Alex 2024-12-13T14:40:26.594839Z

that is too slow due to sync/recompute. the whole point of doing in the render-fn is to avoid that roundtrip

mkvlr 2024-12-13T14:40:52.477279Z

are you sure?

mkvlr 2024-12-13T14:41:06.552539Z

what’s taking to so long?

Alex 2024-12-13T14:41:10.942379Z

well, I can try again

mkvlr 2024-12-13T14:47:19.557839Z

(def frames-viewer
  {:transform-fn (comp (clerk/update-val symbol)
                       clerk/mark-presented)
   :render-fn '(fn [sym]
                 (let [atom* @(resolve sym)
                       {:keys [max-value frame-number]} @atom*]
                   [:input {:type :range
                            :value frame-number
                            :min 0
                            :max max-value
                            :on-change #(swap! atom*
                                               assoc
                                               :frame-number
                                               (int (.. % -target -value)))}]))})

^::clerk/sync
(defonce frames*
  (atom {:frame-number 0
         :max-value (-> rendered-frames count dec)}))
#_(reset! frames*
          {:frame-number 0
           :max-value (-> rendered-frames count dec)})

(-> (clerk/with-viewer frames-viewer
      `frames*)
    (assoc :nextjournal/width :full))

(def rendered-frames
  (mapv render-board guard-path-frames))

^{::clerk/width :full}
(clerk/html [:pre (get rendered-frames (:frame-number @frames*))])

mkvlr 2024-12-13T14:47:25.907459Z

this isn’t so bad but also not great

mkvlr 2024-12-13T14:53:06.132429Z

(def frames-viewer
  {:transform-fn clerk/mark-presented
   :render-fn '(fn [frames]
                 (reagent.core/with-let [frame* (reagent.core/atom 0)]
                   [:div 
                    [:input {:type :range
                             :value @frame*
                             :min 0
                             :max (dec (count frames))
                             :on-change #(reset! frame*
                                                 (int (.. % -target -value)))}]
                    [:pre (get frames @frame*)]]))})

(clerk/with-viewer frames-viewer
  (mapv render-board guard-path-frames))

mkvlr 2024-12-13T14:53:13.968119Z

this keeps it completely in the client and then it’s fast

Alex 2024-12-13T14:53:53.599909Z

that's what I'm looking for! ❤️

Alex 2024-12-13T15:05:19.544939Z

@mkvlr perfect, that's exactly what I needed 🙂

mkvlr 2024-12-13T15:09:08.686459Z

great to hear!

Alex 2024-12-13T15:30:31.968399Z

just one final thought, if my frame is not text but an instance of BufferedImage instead — how do I use it from within render-fn?

mkvlr 2024-12-13T15:40:48.845719Z

clerk’s image viewer should render that

mkvlr 2024-12-13T15:42:22.510939Z

there’s currently some known issues around the performance of presenting BufferedImages

Alex 2024-12-13T15:45:29.011089Z

> clerk’s image viewer should render that I'm hopeful. how do I use it from within hiccup of the render-fn?

mkvlr 2024-12-13T15:50:04.142329Z

(def image-frames-viewer
  {:render-fn '(fn [frames]
                 (prn :frames frames)
                 (reagent.core/with-let [frame* (reagent.core/atom 0)]
                   [:div 
                    [:input {:type :range
                             :value @frame*
                             :min 0
                             :max (dec (count frames))
                             :on-change #(reset! frame*
                                                 (int (.. % -target -value)))}]
                    [:div.flex.flex-col.items-center.not-prose
                     [:img {:src #?(:clj  (nextjournal.clerk.render/url-for (:nextjournal/value (get frames @frame*)))
                                    :cljs blob-or-url)}]]]))})

(clerk/with-viewer image-frames-viewer
  (mapv #(clerk/image (format "/Users/mk/Desktop/%d.png" %))
        (range 1 5)))

mkvlr 2024-12-13T15:57:10.935939Z

no need for clerk/image if you already have your BufferedImage in a vector

Alex 2024-12-13T15:57:44.877609Z

it seems I was close. it doesn't seem to fully work though

Alex 2024-12-13T15:58:59.636389Z

the tags comes out like this: <img src="/_blob/?value={:path [1 0], :nextjournal/value {:blob-id &quot;5dsq3A2Zfhjovuf5Cw4iyrGkKJN5UP&quot;, :path [1 0]}, :nextjournal/width :wide, :nextjournal/viewer {:name nextjournal.clerk.viewer/image-viewer, :render-fn #nextjournal.clerk.viewer.ViewerFn{:form (fn [blob-or-url] [:div.flex.flex-col.items-center.not-prose [:img {:src (nextjournal.clerk.render/url-for blob-or-url)}]]), :f #object[Function]}, :hash &quot;5drT9JGE6qJ8mafafimFkbc2LneM6b&quot;}, :nextjournal/content-type &quot;image/png&quot;}">

mkvlr 2024-12-13T16:04:30.164729Z

@alex.shulgin I think you’re missing a (:nextjournal/value ,,,)

mkvlr 2024-12-13T16:04:44.562789Z

see my snippet above

Alex 2024-12-13T16:05:10.633899Z

🤦🏻 I think I made a map out of it instead of using it as a look up. let me try again

Alex 2024-12-13T16:05:58.283099Z

yep, it works — awesome! 😍

🙌 1
mkvlr 2024-12-13T16:13:58.696139Z

not sure this will keep working when publishing a static html doc though, I’d be surprised if it does

Alex 2024-12-13T16:14:55.569109Z

no, but it doesn't have to — I'm pretty satisfied with the result this way. thanks a ton for helping out!

mkvlr 2024-12-13T16:15:45.772459Z

my pleasure

mkvlr 2024-12-13T16:15:59.229559Z

would be interested in a small video of the final result if it’s not too much trouble

Alex 2024-12-13T16:16:08.409309Z

sure

Alex 2024-12-13T16:20:02.411989Z

wait, it actually does work in the static build! that's a huge win as well

1
mkvlr 2024-12-13T16:20:44.831759Z

wow that’s cool!

Alex 2024-12-13T16:22:10.122679Z

it is is still not as smooth as I would like it to be, but I think I could use canvas and try rendering in the browser

mkvlr 2024-12-13T16:23:52.834749Z

feels pretty smooth here

mkvlr 2024-12-13T16:24:03.493489Z

but canvas might be a better fit, yes

Alex 2024-12-13T16:24:57.412019Z

mkvlr 2024-12-13T16:26:51.253549Z

👍🏻 1