|
4 | 4 | [clojure.set :as set] |
5 | 5 | [day8.re-frame.forward-events-fx])) |
6 | 6 |
|
7 | | -(def default-id :async/flow) |
8 | 7 |
|
9 | 8 | (defn seen-all-of? |
10 | 9 | [required-events seen-events] |
|
24 | 23 | (filterv (fn [task] ((:when task) (:events task) now-seen-events))))) |
25 | 24 |
|
26 | 25 |
|
| 26 | +(def map-when->fn {:seen? seen-all-of? |
| 27 | + :seen-both? seen-all-of? |
| 28 | + :seen-all-of? seen-all-of? |
| 29 | + :seen-any-of? seen-any-of?}) |
| 30 | + |
| 31 | +(defn when->fn |
| 32 | + [when-kw] |
| 33 | + (if-let [when-fn (map-when->fn when-kw)] |
| 34 | + when-fn |
| 35 | + (re-frame/console :error "async-flow: got bad value for :when - " when-kw))) |
| 36 | + |
27 | 37 | (defn massage-rules |
28 | 38 | "Massage the supplied rules as follows: |
29 | 39 | - replace `:when` keyword value with a function implementing the predicate |
30 | 40 | - ensure that only `:dispatch` or `:dispatch-n` is provided |
31 | | - - add a unique :id, if one not already present |
32 | | - - add halt event when :halt? present" |
33 | | - [flow-id rules] |
34 | | - (let [halt-event [flow-id :halt-flow] |
35 | | - when->fn {:seen? seen-all-of? |
36 | | - :seen-both? seen-all-of? |
37 | | - :seen-all-of? seen-all-of? |
38 | | - :seen-any-of? seen-any-of?} |
39 | | - add-halt (fn [tasks halt?] |
40 | | - ; when rule represents stop, add `:halt-flow` as last event |
41 | | - (if halt? (concat tasks [halt-event]) tasks))] |
42 | | - (->> rules |
43 | | - (map-indexed (fn [index {:keys [id when events dispatch dispatch-n halt?]}] |
44 | | - (let [when-as-fn (when->fn when) |
45 | | - _ (assert (cond [(some? dispatch) (some? dispatch-n)] |
46 | | - [false false] true ; either or both can be nil |
47 | | - [true false] true ; only dispatch provided |
48 | | - [false true] true ; only dispatch-n provided |
49 | | - false) "async-flow: rule can only specify one of :dispatch :dispatch-n") |
50 | | - _ (assert (some? when-as-fn) (str "async-flow: found bad value for :when: " when)) |
51 | | - tasks (-> (cond |
52 | | - dispatch (list dispatch) |
53 | | - dispatch-n dispatch-n |
54 | | - :else nil) |
55 | | - (add-halt halt?))] |
56 | | - {:id (or id index) |
57 | | - :when when-as-fn |
58 | | - :events (if (coll? events) (set events) #{events}) |
59 | | - :dispatch-n tasks})))))) |
60 | | - |
61 | | - |
62 | | -;; -- Create Event Handler |
| 41 | + - add a unique :id, if one not already present" |
| 42 | + [rules] |
| 43 | + (->> rules |
| 44 | + (map-indexed (fn [index {:as rule :keys [id when events dispatch dispatch-n halt?]}] |
| 45 | + {:id (or id index) |
| 46 | + :halt? (or halt? false) |
| 47 | + :when (when->fn when) |
| 48 | + :events (if (coll? events) (set events) (hash-set events)) |
| 49 | + :dispatch-n (cond |
| 50 | + dispatch-n (if dispatch |
| 51 | + (re-frame/console :error "async-flow: rule can only specify one of :dispatch and :dispatch-n. Got both: " rule) |
| 52 | + dispatch-n) |
| 53 | + dispatch (list dispatch) |
| 54 | + :else '())})))) |
| 55 | + |
| 56 | + |
| 57 | +;; -- Event Handler |
63 | 58 |
|
64 | 59 | (defn make-flow-event-handler |
65 | | - "given a flow definitiion, returns an event handler which implements this definition" |
| 60 | + "Given a flow definitiion, returns an event handler which implements this definition" |
66 | 61 | [{:keys [id db-path rules first-dispatch]}] |
67 | | - (let [id (or id default-id) |
| 62 | + (let [ |
68 | 63 | ;; Subject to db-path, state is either stored in app-db or in a local atom |
69 | 64 | ;; Two pieces of state are maintained: |
70 | 65 | ;; - the set of seen events |
|
81 | 76 | (fn [db] (get-in db db-path)) |
82 | 77 | (fn [_] @local-store)) |
83 | 78 |
|
84 | | - rules (massage-rules id rules)] ;; all of the events refered to in the rules |
| 79 | + rules (massage-rules rules)] ;; all of the events refered to in the rules |
85 | 80 |
|
86 | 81 | ;; Return an event handler which will manage the flow. |
87 | 82 | ;; This event handler will receive 3 kinds of events: |
88 | 83 | ;; (dispatch [:id :setup]) |
89 | 84 | ;; (dispatch [:id :halt-flow]) |
90 | 85 | ;; (dispatch [:id [:forwarded :event :vector]]) |
91 | 86 | ;; |
92 | | - ;; This event handler returns a map of effects. |
| 87 | + ;; This event handler returns a map of effects - it expects to be registered using |
| 88 | + ;; reg-event-fx |
93 | 89 | ;; |
94 | 90 | (fn async-flow-event-hander |
95 | 91 | [{:keys [db]} event-v] |
|
113 | 109 | :forward-events {:unregister id} |
114 | 110 | :deregister-event-handler id} |
115 | 111 |
|
116 | | - ;; Here we are managig the flow. |
117 | | - ;; A new event has been forwarded to this handler. What does it mean? |
118 | | - ;; 1. does this new event mean we need to dispatch another? |
| 112 | + ;; Here we are managing the flow. |
| 113 | + ;; A new event has been forwarded, so work out what should happen: |
| 114 | + ;; 1. does this new event mean we should dispatch another? |
119 | 115 | ;; 2. remember this event has happened |
120 | 116 | (let [[_ [forwarded-event-id & args]] event-v |
121 | 117 | {:keys [seen-events rules-fired]} (get-state db) |
122 | 118 | new-seen-events (conj seen-events forwarded-event-id) |
123 | 119 | ready-rules (startable-rules rules new-seen-events rules-fired) |
| 120 | + add-halt? (some :halt? ready-rules) |
124 | 121 | ready-rules-ids (->> ready-rules (map :id) set) |
125 | | - new-rules-fired (set/union rules-fired ready-rules-ids)] |
| 122 | + new-rules-fired (set/union rules-fired ready-rules-ids) |
| 123 | + new-dispatches (cond-> (mapcat :dispatch-n ready-rules) |
| 124 | + add-halt? vec |
| 125 | + add-halt? (conj [id :halt-flow]))] |
126 | 126 | (merge |
127 | 127 | {:db (set-state db new-seen-events new-rules-fired)} |
128 | | - (when (seq ready-rules) {:dispatch-n (mapcat :dispatch-n ready-rules)}))))))) |
| 128 | + (when (seq new-dispatches) {:dispatch-n new-dispatches}))))))) |
129 | 129 |
|
130 | 130 |
|
131 | | -;; -- Register effects handler with re-frame |
| 131 | +(defn- ensure-has-id |
| 132 | + "Ensure `flow` has an id. |
| 133 | + Return a vector of [id flow]" |
| 134 | + [flow] |
| 135 | + (if-let [id (:id flow)] |
| 136 | + [id flow] |
| 137 | + (let [new-id (keyword (str "async-flow/" (gensym "id-")))] |
| 138 | + [new-id (assoc flow :id new-id)]))) |
132 | 139 |
|
133 | | -(defn flow->handler |
134 | | - [{:as flow :keys [id] :or {id default-id}}] |
135 | | - (re-frame/reg-event-fx |
136 | | - id ;; add debug middleware if dp-path set ??? XXX |
137 | | - (make-flow-event-handler flow)) |
138 | | - (re-frame/console :log "starting async-flow:" id) |
139 | | - (re-frame/dispatch [id :setup])) |
140 | 140 |
|
| 141 | +;; -- Effect handler |
| 142 | + |
| 143 | + |
| 144 | +(defn flow->handler |
| 145 | + "Action the given flow effect" |
| 146 | + [flow] |
| 147 | + (let [[id flow'] (ensure-has-id flow)] |
| 148 | + (re-frame/reg-event-fx id (make-flow-event-handler flow')) ;; register event handler |
| 149 | + (re-frame/dispatch [id :setup]))) ;; kicks things off |
141 | 150 |
|
142 | 151 | (re-frame/reg-fx |
143 | 152 | :async-flow |
|
0 commit comments