Создание атома clojurescript, который работает с частотой сердцебиения

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

Я хотел бы следующие свойства:

  1. Этот код должен выполняться только тогда, когда pred? может вернуть true. В противном случае конечный автомат простаивает.
  2. Это не должно регистрироваться более одного раза за кадр. Модификации атома после вызова AnimationDelay, но до следующего фактического кадра фактически являются noops. То есть атом модифицируется, но это не приводит к повторному вызову f.
  3. С точки зрения человека, использующего атом, все, что ему нужно сделать, это изменить атом. Им не нужно заботиться о регистрации или отмене регистрации атома для обработки AnimationFrame, он просто работает. Атом будет продолжать обрабатываться в каждом кадре, пока pred? не вернет false.

Моя самая большая претензия к приведенному выше коду — это (reset! a true) и (reset! a false) вокруг swap!. Это действительно уродливо, и в неоднопоточной среде, вероятно, будет ошибкой. Есть ли способ улучшить этот код, чтобы я не использовал a ?

Моя вторая проблема заключается в том, что 2 не выполняется. В настоящее время, если вы дважды измените один и тот же атом между кадрами, это фактически приведет к 2 вызовам (f); или 2 обратных вызова на следующем кадре. Я мог бы использовать другой атом, чтобы записать, действительно ли я зарегистрировался для этого фрейма или нет, но это уже становится беспорядочным. Что-нибудь лучше?


person Stephen Cagle    schedule 02.01.2015    source источник


Ответы (1)


Что-то вроде этого может сработать для вас:

(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)))))))
person Tim Pote    schedule 03.01.2015