git.fiddlerwoaroof.com
Raw Blame History
;; Copyright (c) Alan Dipert and Micha Niskin. All rights reserved.
;; The use and distribution terms for this software are covered by the
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be bound by
;; the terms of this license.
;; You must not remove this notice, or any other, from this software.

(ns javelin.core
  (:require-macros [javelin.core])
  (:require [tailrecursion.priority-map :refer [priority-map]]))

;; helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(declare Cell cell? cell input? lens?)

(def ^:private ^:dynamic *tx* nil)
(def ^:private last-rank (atom 0))

(defn- bf-seq
  "Like tree-seq but traversal is breadth-first instead of depth-first."
  [branch? children root]
  (letfn [(walk [queue]
            (when-let [node (peek queue)]
              (->> (when (branch? node) (children node))
                (into (pop queue)) walk (cons node) lazy-seq)))]
    (walk (conj cljs.core.PersistentQueue.EMPTY root))))

(defn- propagate* [pri-map]
  (when-let [next (first (peek pri-map))]
    (let [popq  (pop pri-map)
          old   (.-prev next)
          new   (if-let [f (.-thunk next)] (f) (.-state next))
          diff? (not= new old)]
      (when diff? (set! (.-prev next) new) (-notify-watches next old new))
      (recur (if-not diff? popq (reduce #(assoc %1 %2 (.-rank %2)) popq (.-sinks next)))))))

(defn  deref*     [x]   (if (cell? x) @x x))
(defn- next-rank  [ ]   (swap! last-rank inc))
(defn- cell->pm   [c]   (priority-map c (.-rank c)))
(defn- add-sync!  [c]   (swap! *tx* assoc c (.-rank c)))
(defn- safe-nth   [c i] (try (nth c i) (catch js/Error _)))
(defn- propagate! [c]   (if *tx* (doto c add-sync!) (doto c (-> cell->pm propagate*))))

;; javelin ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn destroy-cell! [this & [keep-watches?]]
  (let [srcs (.-sources this)]
    (set! (.-sources this) [])
    (set! (.-update this) nil)
    (set! (.-thunk this) nil)
    (when-not keep-watches? (set! (.-watches this) {}))
    (doseq [src srcs]
      (when (cell? src)
        (set! (.-sinks src) (disj (.-sinks src) this))))))

(defn set-formula! [this & [f sources]]
  (destroy-cell! this true)
  (when f
    (set! (.-sources this) (conj (vec sources) f))
    (doseq [source (.-sources this)]
      (when (cell? source)
        (set! (.-sinks source) (conj (.-sinks source) this))
        (if (> (.-rank source) (.-rank this))
          (doseq [dep (bf-seq identity #(.-sinks %) source)]
            (set! (.-rank dep) (next-rank))))))
    (let [compute #(apply (deref* (peek %)) (map deref* (pop %)))]
      (set! (.-thunk this) #(set! (.-state this) (compute (.-sources this))))))
  (propagate! this))

(deftype Cell [meta state rank prev sources sinks thunk watches update]
  cljs.core/IPrintWithWriter
  (-pr-writer [this w _] (write-all w "#<Cell: " (pr-str state) ">"))

  cljs.core/IWithMeta
  (-with-meta  [this meta]  (Cell. meta state rank prev sources sinks thunk watches update))

  cljs.core/IMeta
  (-meta [this] meta)

  cljs.core/IDeref
  (-deref [this] (.-state this))

  cljs.core/IReset
  (-reset! [this x]
    (cond (lens? this)  ((.-update this) x)
          (input? this) (do (set! (.-state this) x) (propagate! this))
          :else         (throw (js/Error. "can't swap! or reset! formula cell")))
    (.-state this))

  cljs.core/ISwap
  (-swap! [this f]        (reset! this (f (.-state this))))
  (-swap! [this f a]      (reset! this (f (.-state this) a)))
  (-swap! [this f a b]    (reset! this (f (.-state this) a b)))
  (-swap! [this f a b xs] (reset! this (apply f (.-state this) a b xs)))

  cljs.core/IWatchable
  (-notify-watches [this o n] (doseq [[key f] watches] (f key this o n)))
  (-add-watch      [this k f] (set! (.-watches this) (assoc watches k f)))
  (-remove-watch   [this k]   (set! (.-watches this) (dissoc watches k))))

(defn cell?     [c]   (when (= (type c) Cell) c))
(defn formula?  [c]   (when (and (cell? c) (.-thunk c)) c))
(defn lens?     [c]   (when (and (cell? c) (.-update c)) c))
(defn input?    [c]   (when (and (cell? c) (not (formula? c))) c))
(defn set-cell! [c x] (set! (.-state c) x) (set-formula! c))
(defn formula   [f]   (fn [& sources] (set-formula! (cell ::none) f sources)))
(defn lens      [c f] (let [c ((formula identity) c)] (set! (.-update c) f) c))
(defn cell
  ([x] (set-formula! (Cell. nil x (next-rank) x [] #{} nil {} nil)))
  ([x & {:keys [meta]}] (set-formula! (Cell. meta x (next-rank) x [] #{} nil {} nil))))

(def ^:deprecated lift formula)

;; javelin util ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn dosync* [thunk]
  (let [bind #(binding [*tx* (atom (priority-map))] (%))
        prop #(let [tx @*tx*] (binding [*tx* nil] (propagate* tx)))]
    (if *tx* (thunk) (bind #(do (thunk) (prop))))))

(defn alts! [& cells]
  (let [olds    (atom (repeat (count cells) ::none))
        tag-neq #(vector (not= %1 %2) %2)
        diff    #(->> %2 (map tag-neq %1) (filter first) (map second) distinct)
        proc    #(let [news (diff (deref olds) %&)] (reset! olds %&) news)]
    (apply (formula proc) cells)))

(defn cell-map [f c]
  (let [cseq ((formula seq) c)]
    (map #((formula (comp f safe-nth)) cseq %) (range 0 (count @cseq)))))

(defn cell-doseq* [items-seq f]
  (let [pool-size (cell 0)
        cur-count ((formula count) items-seq)
        ith-item  #((formula safe-nth) items-seq %)]
    ((formula (fn [pool-size cur-count f ith-item reset-pool-size!]
                (when (< pool-size cur-count)
                  (doseq [i (range pool-size cur-count)] (f (ith-item i)))
                  (reset-pool-size! cur-count))))
     pool-size cur-count f ith-item (partial reset! pool-size))))