创建一个在心跳上运行的clojurescript原子

时间:2015-01-02 22:52:28

标签: clojure clojurescript core.async

(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)

loop-fx-on....的目的是在fxatm时,pred? @atm(每次检测)注册true

我想要以下属性:

  1. 此代码只应在pred?可能返回true时执行。否则,状态机闲置。
  2. 每帧不应多次注册。在调用AnimationDelay之后但在下一个实际帧之前的原子的修改实际上是noops。也就是说,原子被修改了,但它不会再次调用f
  3. 从使用原子的人的角度来看,他们所需要做的就是修改原子。他们不需要关心为AnimationFrame处理注册或取消注册原子,它只是起作用。原子将继续每帧处理,直到pred?返回false。
  4. 我对上述代码的最大抱​​怨是(reset! a true)(reset! a false) swap!左右。这真的很难看,在非单线程环境中可能是一个bug。有没有办法改进此代码,以便我不会最终使用a

    我的第二个担忧是2没有得到满足。目前,如果你在一帧之间修改同一个原子两次,它实际上会导致2次调用(f);或下一帧的2个回调。我可以使用另一个原子来记录我是否实际注册过这个帧,但这已经变得混乱了。还有什么更好的吗?

1 个答案:

答案 0 :(得分:2)

这样的事可能适合你:

(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)))))))