Reset state from history
- fails if server changes are made while client is disconnected
This commit is contained in:
parent
763e6f2fc8
commit
a572b9e1eb
5 changed files with 85 additions and 34 deletions
|
@ -10,6 +10,24 @@
|
||||||
(def to-all (chan))
|
(def to-all (chan))
|
||||||
(def from-all (a/mult to-all))
|
(def from-all (a/mult to-all))
|
||||||
|
|
||||||
|
(def state-history (atom []))
|
||||||
|
|
||||||
|
(defn save-history-state [history new-state]
|
||||||
|
(when-not (= (last history) new-state)
|
||||||
|
(println "Adding state to history: " (hasch/uuid new-state))
|
||||||
|
(println "History size:" (inc (count history)))
|
||||||
|
(conj history new-state)))
|
||||||
|
|
||||||
|
(defn get-history-state [hash]
|
||||||
|
(println "Look for history state:" hash)
|
||||||
|
(println "History:")
|
||||||
|
(doseq [s @state-history]
|
||||||
|
(println (hasch/uuid s)))
|
||||||
|
(first (filter #(= (hasch/uuid %) hash) @state-history)))
|
||||||
|
|
||||||
|
(add-watch state :history (fn [_ _ _ new-state]
|
||||||
|
(swap! state-history save-history-state new-state)))
|
||||||
|
|
||||||
(defn initial-state [grubs recipes]
|
(defn initial-state [grubs recipes]
|
||||||
{:grubs (util/map-by-key :id grubs)
|
{:grubs (util/map-by-key :id grubs)
|
||||||
:recipes (util/map-by-key :id recipes)})
|
:recipes (util/map-by-key :id recipes)})
|
||||||
|
@ -17,18 +35,46 @@
|
||||||
(defn sync-new-client! [to from]
|
(defn sync-new-client! [to from]
|
||||||
(let [client-id (java.util.UUID/randomUUID)
|
(let [client-id (java.util.UUID/randomUUID)
|
||||||
server-shadow (atom cs/empty-state)]
|
server-shadow (atom cs/empty-state)]
|
||||||
(add-watch state client-id (fn [_ _ _ new-state]
|
(add-watch state client-id (fn [_ _ _ current-state]
|
||||||
(when-let [msg (cs/diff-states new-state @server-shadow)]
|
(when-let [msg (cs/diff-states @server-shadow current-state)]
|
||||||
(a/put! to msg)
|
(a/put! to msg)
|
||||||
(reset! server-shadow new-state))))
|
;; TODO: reset only if send succeeds?
|
||||||
|
(reset! server-shadow current-state))))
|
||||||
(a/go-loop []
|
(a/go-loop []
|
||||||
(if-let [{:keys [type diff hash shadow-hash] :as msg} (<! from)]
|
(if-let [{:keys [type diff hash shadow-hash] :as msg} (<! from)]
|
||||||
(do (condp = type
|
(do (condp = type
|
||||||
:diff (let [new-shadow (swap! server-shadow sync/patch-state diff)]
|
:diff
|
||||||
|
(if (= (hasch/uuid @server-shadow) shadow-hash)
|
||||||
|
;; we have what they thought we had
|
||||||
|
;; apply changes normally
|
||||||
|
(let [new-shadow (swap! server-shadow sync/patch-state diff)]
|
||||||
|
(println "Hash matched state, apply changes")
|
||||||
(if (= (hasch/uuid new-shadow) hash)
|
(if (= (hasch/uuid new-shadow) hash)
|
||||||
(let [new-state (swap! state sync/patch-state diff)]
|
(let [new-state (swap! state sync/patch-state diff)]
|
||||||
(>! @to-db diff))
|
(>! @to-db diff))
|
||||||
(do (println "Hash check failed --> complete sync")
|
(do (println "Applying diff failed --> full sync")
|
||||||
|
(let [sync-state @state]
|
||||||
|
(reset! server-shadow sync-state)
|
||||||
|
(a/put! to (cs/complete-sync-response sync-state))))))
|
||||||
|
;; we have something different than they thought
|
||||||
|
;; check history
|
||||||
|
(if-let [history-state (get-history-state shadow-hash)]
|
||||||
|
;; Found what they thought in history,
|
||||||
|
;; reset client state to this
|
||||||
|
;; and continue as normal
|
||||||
|
(do
|
||||||
|
(println "Hash check failed --> Reset from history")
|
||||||
|
(reset! server-shadow history-state)
|
||||||
|
(let [new-shadow (swap! server-shadow sync/patch-state diff)]
|
||||||
|
(if (= (hasch/uuid new-shadow) hash)
|
||||||
|
(let [new-state (swap! state sync/patch-state diff)]
|
||||||
|
(>! @to-db diff))
|
||||||
|
(do (println "Applying diff failed --> full sync")
|
||||||
|
(let [sync-state @state]
|
||||||
|
(reset! server-shadow sync-state)
|
||||||
|
(a/put! to (cs/complete-sync-response sync-state)))))))
|
||||||
|
;; No history found, do complete sync
|
||||||
|
(do (println "Hash check failed, not in history --> full sync")
|
||||||
(let [sync-state @state]
|
(let [sync-state @state]
|
||||||
(reset! server-shadow sync-state)
|
(reset! server-shadow sync-state)
|
||||||
(a/put! to (cs/complete-sync-response sync-state))))))
|
(a/put! to (cs/complete-sync-response sync-state))))))
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
[cljs.core.async :as a :refer [<! >! chan]])
|
[cljs.core.async :as a :refer [<! >! chan]])
|
||||||
(:require-macros [grub.macros :refer [log logs]]))
|
(:require-macros [grub.macros :refer [log logs]]))
|
||||||
|
|
||||||
(defn init-app []
|
(defn connect-to-server [reset?]
|
||||||
(view/render-app state/app-state)
|
|
||||||
(let [to-remote (chan)
|
(let [to-remote (chan)
|
||||||
from-remote (chan)]
|
from-remote (chan)]
|
||||||
(ws/connect-client! to-remote from-remote)
|
(ws/connect-client! to-remote from-remote)
|
||||||
(state/sync-state! from-remote to-remote)))
|
(state/sync-state! from-remote to-remote reset?)))
|
||||||
|
|
||||||
|
(defn init-app []
|
||||||
|
(view/render-app state/app-state)
|
||||||
|
(connect-to-server true))
|
||||||
|
|
||||||
(init-app)
|
(init-app)
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
|
|
||||||
(def app-state (atom cs/empty-state))
|
(def app-state (atom cs/empty-state))
|
||||||
|
|
||||||
(defn sync-state! [to from]
|
(defn sync-state! [to from reset?]
|
||||||
(let [client-shadow (atom cs/empty-state)]
|
(let [client-shadow (atom cs/empty-state)]
|
||||||
(add-watch app-state :app-state (fn [_ _ _ new]
|
(add-watch app-state :app-state (fn [_ _ _ current-state]
|
||||||
(when-let [msg (cs/diff-states new @client-shadow)]
|
(when-let [msg (cs/diff-states @client-shadow current-state)]
|
||||||
(a/put! from msg)
|
(a/put! from msg)
|
||||||
;; TODO: reset shadow only if send succeeds
|
;; TODO: reset only if send succeeds
|
||||||
(reset! client-shadow new))))
|
(reset! client-shadow current-state))))
|
||||||
(go-loop []
|
(go-loop []
|
||||||
(if-let [{:keys [type diff hash shadow-hash] :as msg} (<! to)]
|
(if-let [{:keys [type diff hash shadow-hash] :as msg} (<! to)]
|
||||||
(do (condp = type
|
(do (condp = type
|
||||||
|
@ -28,4 +28,4 @@
|
||||||
(logs "Invalid msg:" msg))
|
(logs "Invalid msg:" msg))
|
||||||
(recur))
|
(recur))
|
||||||
(remove-watch app-state :app-state)))
|
(remove-watch app-state :app-state)))
|
||||||
(a/put! from cs/complete-sync-request)))
|
(when reset? (a/put! from cs/complete-sync-request))))
|
||||||
|
|
|
@ -27,10 +27,13 @@
|
||||||
(let [msg (cljs.reader/read-string (.-message event))]
|
(let [msg (cljs.reader/read-string (.-message event))]
|
||||||
(a/put! from msg)))
|
(a/put! from msg)))
|
||||||
|
|
||||||
|
(def ws (atom nil))
|
||||||
|
|
||||||
(defn connect-client! [to from]
|
(defn connect-client! [to from]
|
||||||
(let [handler (goog.events.EventHandler.)
|
(let [handler (goog.events.EventHandler.)
|
||||||
websocket (goog.net.WebSocket.)
|
websocket (goog.net.WebSocket.)
|
||||||
listen (fn [type fun] (.listen handler websocket type fun false))]
|
listen (fn [type fun] (.listen handler websocket type fun false))]
|
||||||
|
(reset! ws websocket)
|
||||||
(listen goog.net.WebSocket.EventType.OPENED (partial on-connected websocket))
|
(listen goog.net.WebSocket.EventType.OPENED (partial on-connected websocket))
|
||||||
(listen goog.net.WebSocket.EventType.MESSAGE (partial on-message from))
|
(listen goog.net.WebSocket.EventType.MESSAGE (partial on-message from))
|
||||||
(listen goog.net.WebSocket.EventType.CLOSED #(log "Closed:" %))
|
(listen goog.net.WebSocket.EventType.CLOSED #(log "Closed:" %))
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
:hash hash
|
:hash hash
|
||||||
:shadow-hash shadow-hash})
|
:shadow-hash shadow-hash})
|
||||||
|
|
||||||
(defn diff-states [state shadow*]
|
(defn diff-states [shadow state]
|
||||||
(let [shadow shadow*]
|
|
||||||
(when (not= state shadow)
|
(when (not= state shadow)
|
||||||
(let [diff (sync/diff-states shadow state)
|
(let [diff (sync/diff-states shadow state)
|
||||||
hash (hasch/uuid state)
|
hash (hasch/uuid state)
|
||||||
|
@ -28,4 +27,4 @@
|
||||||
;(logs "Remote = " shadow)
|
;(logs "Remote = " shadow)
|
||||||
;(logs "Diff:" diff)
|
;(logs "Diff:" diff)
|
||||||
;(logs "Send" shadow-hash "->" hash)
|
;(logs "Send" shadow-hash "->" hash)
|
||||||
))))
|
)))
|
||||||
|
|
Loading…
Reference in a new issue