创建一个在心跳上运行的 clojurescript 原子
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)
loop-fx-on....
的目的是注册 fx
在 atm
上调用(每次心跳)只要 pred? @atm
是 true
。
我想要以下属性:
- 此代码应仅在
pred?
可能 return true
时执行。否则,状态机处于空闲状态。
- 每帧不应注册多次。在调用 AnimationDelay 之后但在下一个实际帧之前对原子的修改实际上是 noops。即原子被修改,但不会导致
f
再次被调用
- 从使用原子的人的角度来看,他们需要做的就是修改原子。他们不需要关心为 AnimationFrame 处理注册或取消注册原子,它只是工作。原子将继续每帧处理,直到
pred?
returns false.
我对上述代码最大的不满是 (reset! a true)
和 swap!
周围的 (reset! a false)
。这真的很丑陋,在非单线程环境中可能是一个错误。有什么方法可以改进这段代码,这样我就不会最终使用 a
?
我的第二个担心是没有满足 2。目前,如果你在一个帧之间修改同一个原子两次,它实际上会导致对 (f) 的 2 次调用;或下一帧的 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)))))))
(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....
的目的是注册 fx
在 atm
上调用(每次心跳)只要 pred? @atm
是 true
。
我想要以下属性:
- 此代码应仅在
pred?
可能 returntrue
时执行。否则,状态机处于空闲状态。 - 每帧不应注册多次。在调用 AnimationDelay 之后但在下一个实际帧之前对原子的修改实际上是 noops。即原子被修改,但不会导致
f
再次被调用 - 从使用原子的人的角度来看,他们需要做的就是修改原子。他们不需要关心为 AnimationFrame 处理注册或取消注册原子,它只是工作。原子将继续每帧处理,直到
pred?
returns false.
我对上述代码最大的不满是 (reset! a true)
和 swap!
周围的 (reset! a false)
。这真的很丑陋,在非单线程环境中可能是一个错误。有什么方法可以改进这段代码,这样我就不会最终使用 a
?
我的第二个担心是没有满足 2。目前,如果你在一个帧之间修改同一个原子两次,它实际上会导致对 (f) 的 2 次调用;或下一帧的 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)))))))