简体   繁体   中英

Creating a clojurescript atom that runs on a heartbeat

(ns example.asyncq
  (:require
   [cljs.core.async :as async]
   goog.async.AnimationDelay)
  (:require-macros
   [cljs.core.async.macros :refer [go go-loop]]))

(defn loop-fx-on-atm-when-pred?-is-true [atm fx pred?]
  (let [c (async/chan)
        f (goog.async.AnimationDelay. #(async/put! c :loop))
        a (atom false)]
    (add-watch 
     atm
     :loop-fx-on-atm-when-pred?-is-true
     (fn [_ _ _ _]
       (when-not @a
         (async/put! c :initial-loop))))
    (go-loop []
      (async/<! c)
      (reset! a true)
      (swap! atm #(if (pred? %) (do (.start f) (fx %)) %))
      (reset! a false)
      (recur))))

(def the-final-countdown (atom 4))

(loop-fx-on-atm-when-pred?-is-true
 the-final-countdown
 #(do (js/console.log %) (dec %))
 pos?)

(swap! the-final-countdown inc)
;; prints "5" "4" "3" "2" "1", each on a seperate frame (hearbeat)

The purpose of loop-fx-on.... is to register fx to be called on atm (per heartbeat) whenever pred? @atm pred? @atm is true .

I would like the following properties:

  1. This code should only execute when pred? might return true . Otherwise, the state machine sits idle.
  2. This should not register more than once per frame. Modifications of the atom after the AnimationDelay has been called but before the next actual frame are effectively noops. That is, the atom is modified, but it does not cause f to be called again.
  3. From the point of view of the person using the atom, all they need do is modify the atom. They do not need to concern themselves with registering or unregistering the atom for AnimationFrame processing, it just works. The atom will continue to be processed every frame until pred? returns false.

My biggest gripe with the above code is (reset! a true) and (reset! a false) around the swap! . That is really ugly, and in a non single threaded environment would probably be a bug. Is there any way to improve this code so that I don't end up using a ?

My second concern is that 2 is not being met. Currently, if you modify the same atom twice between a frame, it will actually result in 2 calls to (f); or 2 callbacks on the next frame. I could use another atom to record whether I have actually registered for this frame or not, but this is already getting messy. Anything better?

Something like this might work for you:

(defn my-fn [a f pred]
  (let [pending? (atom false)
        f (fn []
            (reset! pending? false)
            (f @a))]
    (add-watch a (gensym)
      (fn [_k _a _old-val new-val]
        (when (and (not @pending?) (pred new-val))
          (reset! pending? true)
          (if (exists? js/requestAnimationFrame)
            (js/requestAnimationFrame f)
            (js/setTimeout f 16)))))))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM