Keep state history with state in one atom

This commit is contained in:
Nicholas Kariniemi 2014-10-15 22:08:09 +03:00
parent e5309fcd69
commit 78d5617356
5 changed files with 73 additions and 61 deletions

View file

@ -2,6 +2,7 @@
(:require [grub.websocket :as ws]
[grub.db :as db]
[grub.state :as state]
[grub.sync :as sync]
[ring.middleware.file :as file]
[ring.middleware.content-type :as content-type]
[ring.util.response :as resp]
@ -44,7 +45,7 @@
:db-conn nil
:port 3000
:stop-server nil
:state (atom nil)})
:states (atom nil)})
(def dev-system
{:index dev-index-page
@ -53,16 +54,16 @@
:db-conn nil
:port 3000
:stop-server nil
:state (atom nil)})
:states (atom nil)})
(defn handle-websocket [handler state]
(defn handle-websocket [handler states new-states]
(fn [{:keys [websocket?] :as request}]
(if websocket?
(httpkit/with-channel request ws-channel
(let [to-client (chan)
from-client (chan)]
(ws/add-connected-client! ws-channel to-client from-client)
(state/sync-new-client! to-client from-client state)))
(state/sync-new-client! to-client from-client new-states states)))
(handler request))))
(defn handle-root [handler index]
@ -79,32 +80,36 @@
:body ""}
(handler req))))
(defn make-handler [{:keys [index state]}]
(defn make-handler [{:keys [index states]} new-states]
(-> (fn [req] "Not found")
(file/wrap-file "public")
(content-type/wrap-content-type)
(handle-root index)
(handle-websocket state)
(handle-websocket states new-states)
(wrap-bounce-favicon)))
(defn start [{:keys [port db-name state] :as system}]
(defn start [{:keys [port db-name states] :as system}]
(let [{:keys [db conn]} (db/connect db-name)
_ (reset! state (db/get-current-state db))
stop-server (httpkit/run-server (make-handler system) {:port port})]
(add-watch state :db (fn [_ _ old new]
(db/update-db! db new)))
new-states (chan)
_ (reset! states (sync/new-state (db/get-current-state db)))
stop-server (httpkit/run-server (make-handler system new-states) {:port port})]
(add-watch states :db (fn [_ _ old new]
(when-not (= old new)
(let [new-state (sync/get-current-state new)]
(a/put! new-states new-state)
(db/update-db! db new-state)))))
(println "Started server on localhost:" port)
(assoc system
:db db
:db-conn conn
:stop-server stop-server
:state state)))
:states states)))
(defn stop [{:keys [db-conn stop-server state] :as system}]
(remove-watch state :db)
(defn stop [{:keys [db-conn stop-server states] :as system}]
(remove-watch states :db)
(stop-server)
(db/disconnect db-conn)
(reset! state nil)
(reset! states nil)
system)
(defn usage [options-summary]

View file

@ -1,5 +1,6 @@
(ns grub.core
(:require [grub.state :as state]
[grub.sync :as sync]
[grub.websocket :as websocket]
[grub.view.app :as view]
[cljs.core.async :as a :refer [<! >! chan]])
@ -12,21 +13,28 @@
:remote-states (chan)
:to-remote (chan)
:from-remote (chan)}
:states (atom nil)
:view-state nil})
(defn start [system]
(defn start [{:keys [states pending-msg] :as system}]
(reset! states sync/empty-state)
(let [new-states (chan)
render-states (chan)
>remote (chan)
events (chan)
state (view/render-app state/empty-state new-states)
ws (websocket/connect (:pending-msg system) >remote events)
agent-states (state/sync-client! >remote events new-states state)]
view-state (view/render-app state/empty-state render-states new-states)
ws (websocket/connect pending-msg >remote events)
agent-states (state/sync-client! >remote events new-states states)]
(add-watch states :render (fn [_ _ old new]
(when-not (= old new)
(a/put! render-states (sync/get-current-state new)))))
(assoc system
:ws ws
:channels {:new-states new-states
:>remote >remote
:events events}
:state state)))
:states states
:view-state view-state)))
(defn stop [{:keys [channels ws]} system]
(doseq [c (vals channels)] (a/close! c))

View file

@ -25,7 +25,7 @@
(dom/on-document-mousedown #(put! >events {:type :body-mousedown :event %}))
(dom/on-window-scroll #(put! >events {:type :body-scroll :event %}))))))
(defn render-app [initial-state >new-states]
(defn render-app [initial-state <new-states >new-states]
(let [state (atom initial-state)
>events (chan)
<events (a/pub >events :type)
@ -38,4 +38,8 @@
:add-grubs-ch add-grubs-ch}
:tx-listen (fn [{:keys [new-state tag]} _]
(when (= tag :local) (put! >new-states new-state)))})
(go (loop []
(let [new-state (<! <new-states)]
(reset! state new-state)
(recur))))
state))

View file

@ -10,19 +10,26 @@
(def empty-state sync/empty-state)
(defmulti handle-event (fn [event]
(:type event)))
(defn update-states [states diff]
(let [state (sync/get-current-state states)
new-state (diff/patch-state state diff)]
(sync/add-history-state states new-state)))
(defmethod handle-event :diff [{:keys [hash diff states shadow client? state] :as msg}]
(let [history-shadow (sync/get-history-state states hash)]
(defn diff-msg [shadow state]
(let [diff (diff/diff-states shadow state)
hash (hasch/uuid shadow)]
(message/diff-msg diff hash)))
(defmulti handle-event (fn [event] (:type event)))
(defmethod handle-event :diff [{:keys [hash diff states shadow client?]}]
(let [history-shadow (sync/get-history-state @states hash)]
(if history-shadow
(let [new-state (swap! state diff/patch-state diff)
new-states (sync/add-history-state states new-state)
new-shadow (diff/patch-state history-shadow diff)
new-diff (diff/diff-states new-shadow new-state)
new-hash (hasch/uuid new-shadow)]
(let [new-states (swap! states update-states diff)
new-state (sync/get-current-state new-states)
new-shadow (diff/patch-state history-shadow diff)]
{:out-event (when-not (sync/empty-diff? diff)
(message/diff-msg new-diff new-hash))
(diff-msg new-shadow new-state))
:new-states new-states
:new-shadow new-shadow})
(if client?
@ -33,61 +40,48 @@
:new-shadow state})))))
(defmethod handle-event :full-sync-request [{:keys [states]}]
(let [state (sync/get-current-state states)]
(let [state (sync/get-current-state @states)]
{:new-shadow state
:out-event (message/full-sync state)}))
(defmethod handle-event :full-sync [{:keys [full-state states state]}]
(reset! state full-state)
{:new-states (sync/new-state full-state)
:new-shadow full-state})
(defmethod handle-event :full-sync [{:keys [full-state states]}]
(reset! states (sync/new-state full-state))
{:new-shadow full-state})
(defmethod handle-event :default [msg]
#+cljs (logs "Unhandled message:" msg)
#+clj (println "Unhandled message:" msg)
{})
(defn diff-msg [shadow state]
(let [diff (diff/diff-states shadow state)
hash (hasch/uuid shadow)]
(message/diff-msg diff hash)))
(defn make-agent
([client? >remote events new-states state]
(make-agent client? >remote events new-states state sync/empty-state))
([client? >remote events new-states state initial-shadow]
(go (loop [shadow initial-shadow
states (sync/new-state @state)]
([client? >remote events new-states states]
(make-agent client? >remote events new-states states sync/empty-state))
([client? >remote events new-states states initial-shadow]
(go (loop [shadow initial-shadow]
(let [[v c] (a/alts! [new-states events] :priority true)]
(cond (nil? v) nil ;; drop out of loop
(= c new-states)
(do (when-not (= shadow v)
(swap! states sync/add-history-state v)
(>! >remote (diff-msg shadow v)))
(recur shadow (sync/add-history-state states v)))
(recur shadow))
(= c events)
(let [event (assoc v
:states states
:client? client?
:shadow shadow
:state state)
{:keys [new-states new-shadow out-event]} (handle-event event)]
:shadow shadow)
{:keys [new-shadow out-event]} (handle-event event)]
(when out-event (a/put! >remote out-event))
(recur (if new-shadow new-shadow shadow)
(if new-states new-states states)))))))))
(recur (if new-shadow new-shadow shadow)))))))))
(def make-server-agent (partial make-agent false))
(def make-client-agent (partial make-agent true))
#+clj
(defn sync-new-client! [>remote events state]
(let [client-id (java.util.UUID/randomUUID)
new-states (chan)]
(add-watch state client-id (fn [_ _ old new]
(when-not (= old new)
(a/put! new-states new))))
(make-server-agent >remote events new-states state)))
(defn sync-new-client! [>remote events new-states states]
(make-server-agent >remote events new-states states))
#+cljs
(defn sync-client! [>remote events new-states state]
(make-client-agent >remote events new-states state)
(defn sync-client! [>remote events new-states states]
(make-client-agent >remote events new-states states)
(a/put! >remote message/full-sync-request))

View file

@ -6,6 +6,7 @@
(def num-history-states 20)
(def empty-state {:grubs {} :recipes {}})
(def empty-states [{:grubs {} :recipes {}}])
(defn new-state [state]
[{:hash (hasch/uuid state)