2015-03-04 13:00:36 +00:00
|
|
|
(ns re-frame.router
|
|
|
|
(:refer-clojure :exclude [flush])
|
|
|
|
(:require-macros [cljs.core.async.macros :refer [go-loop go]])
|
|
|
|
(:require [reagent.core :refer [flush]]
|
|
|
|
[re-frame.handlers :refer [handle]]
|
2015-05-02 00:52:11 +00:00
|
|
|
[re-frame.utils :refer [warn error]]
|
2015-09-27 08:39:35 +00:00
|
|
|
[cljs.core.async :refer [chan put! <! timeout close!]]
|
|
|
|
[goog.async.nextTick]))
|
2015-03-04 13:00:36 +00:00
|
|
|
|
|
|
|
;; -- The Event Conveyor Belt --------------------------------------------------------------------
|
|
|
|
;;
|
|
|
|
;; Moves events from "dispatch" to the router loop.
|
|
|
|
;; Using core.async means we can have the aysnc handling of events.
|
|
|
|
;;
|
|
|
|
(def ^:private event-chan (chan)) ;; TODO: set buffer size?
|
|
|
|
|
2015-05-02 00:52:11 +00:00
|
|
|
(defn purge-chan
|
|
|
|
"read all pending events from the channel and drop them on the floor"
|
|
|
|
[]
|
|
|
|
#_(loop [] ;; TODO commented out until poll! is a part of the core.asyc API
|
|
|
|
(when (go (poll! event-chan)) ;; progress: https://github.com/clojure/core.async/commit/d8047c0b0ec13788c1092f579f03733ee635c493
|
|
|
|
(recur))))
|
2015-03-04 13:00:36 +00:00
|
|
|
|
|
|
|
;; -- router loop ---------------------------------------------------------------------------------
|
|
|
|
;;
|
2015-04-18 11:15:53 +00:00
|
|
|
;; In a perpetual loop, read events from "event-chan", and call the right handler.
|
2015-03-04 13:00:36 +00:00
|
|
|
;;
|
|
|
|
;; Because handlers occupy the CPU, before each event is handled, hand
|
2015-09-27 08:39:35 +00:00
|
|
|
;; back control to the browser, via a (<! (yield)) call.
|
2015-03-04 13:00:36 +00:00
|
|
|
;;
|
2015-04-18 11:15:53 +00:00
|
|
|
;; In some cases, we need to pause for an entire animationFrame, to ensure that
|
2015-03-04 13:00:36 +00:00
|
|
|
;; the DOM is fully flushed, before then calling a handler known to hog the CPU
|
|
|
|
;; for an extended period. In such a case, the event should be laballed with metadata
|
|
|
|
;; Example usage (notice the ":flush-dom" metadata):
|
|
|
|
;; (dispatch ^:flush-dom [:event-id other params])
|
|
|
|
;;
|
2015-05-02 00:52:11 +00:00
|
|
|
|
2015-09-27 08:39:35 +00:00
|
|
|
(defn yield
|
|
|
|
"Yields control to the browser. Faster than (timeout 0).
|
|
|
|
See http://dev.clojure.org/jira/browse/ASYNC-137"
|
|
|
|
[]
|
|
|
|
(let [ch (chan)]
|
|
|
|
(goog.async.nextTick #(close! ch))
|
|
|
|
ch))
|
|
|
|
|
|
|
|
|
2015-05-02 00:52:11 +00:00
|
|
|
(defn router-loop
|
|
|
|
[]
|
|
|
|
(go-loop []
|
|
|
|
(let [event-v (<! event-chan) ;; wait for an event
|
|
|
|
_ (if (:flush-dom (meta event-v)) ;; check the event for metadata
|
|
|
|
(do (flush) (<! (timeout 20))) ;; wait just over one annimation frame (16ms), to rensure all pending GUI work is flushed to the DOM.
|
2015-09-27 08:39:35 +00:00
|
|
|
(<! (yield)))] ;; just in case we are handling one dispatch after an other, give the browser back control to do its stuff
|
2015-05-02 00:52:11 +00:00
|
|
|
(try
|
|
|
|
(handle event-v)
|
|
|
|
|
2015-05-08 05:49:48 +00:00
|
|
|
;; If the handler throws:
|
|
|
|
;; - allow the exception to bubble up because the app, in production,
|
2015-05-02 00:52:11 +00:00
|
|
|
;; may have hooked window.onerror and perform special processing.
|
2015-05-08 05:49:48 +00:00
|
|
|
;; - But an exception which bubbles up will break the enclosing go-loop.
|
2015-05-02 00:52:11 +00:00
|
|
|
;; So we'll need to start another one.
|
2015-05-08 05:49:48 +00:00
|
|
|
;; - purge any pending events, because they are probably related to the
|
|
|
|
;; event which just fell in a screaming heap. Not sane to handle further
|
|
|
|
;; events if the prior event failed.
|
2015-05-02 00:52:11 +00:00
|
|
|
(catch js/Object e
|
|
|
|
(do
|
|
|
|
;; try to recover from this (probably uncaught) error as best we can
|
|
|
|
(purge-chan) ;; get rid of any pending events
|
|
|
|
(router-loop) ;; Exception throw will cause termination of go-loop. So, start another.
|
|
|
|
|
|
|
|
(throw e))))) ;; re-throw so the rest of the app's infrastructure (window.onerror?) gets told
|
2015-03-04 13:00:36 +00:00
|
|
|
(recur)))
|
|
|
|
|
2015-05-02 00:52:11 +00:00
|
|
|
;; start event processing
|
|
|
|
(router-loop)
|
|
|
|
|
2015-03-04 13:00:36 +00:00
|
|
|
|
|
|
|
;; -- dispatch ------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
(defn dispatch
|
|
|
|
"Send an event to be processed by the registered handler.
|
|
|
|
|
|
|
|
Usage example:
|
|
|
|
(dispatch [:delete-item 42])
|
|
|
|
"
|
|
|
|
[event-v]
|
|
|
|
(if (nil? event-v)
|
2015-05-02 00:52:11 +00:00
|
|
|
(error "re-frame: \"dispatch\" is ignoring a nil event.") ;; nil would close the channel
|
2015-03-04 13:00:36 +00:00
|
|
|
(put! event-chan event-v))
|
2015-04-07 23:12:47 +00:00
|
|
|
nil) ;; Ensure nil return. See https://github.com/Day8/re-frame/wiki/Beware-Returning-False
|
2015-03-04 13:00:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
(defn dispatch-sync
|
2015-03-06 01:44:22 +00:00
|
|
|
"Send an event to be processed by the registered handler, but avoid the async-inducing
|
|
|
|
use of core.async/chan.
|
|
|
|
|
|
|
|
Usage example:
|
|
|
|
(dispatch-sync [:delete-item 42])"
|
2015-03-04 13:00:36 +00:00
|
|
|
[event-v]
|
2015-03-06 01:44:22 +00:00
|
|
|
(handle event-v)
|
2015-04-07 23:12:47 +00:00
|
|
|
nil) ;; Ensure nil return. See https://github.com/Day8/re-frame/wiki/Beware-Returning-False
|
2015-03-04 13:00:36 +00:00
|
|
|
|
|
|
|
|