diff --git a/src/clj/grub/state.clj b/src/clj/grub/state.clj index 5621299..d210363 100644 --- a/src/clj/grub/state.clj +++ b/src/clj/grub/state.clj @@ -10,6 +10,24 @@ (def to-all (chan)) (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] {:grubs (util/map-by-key :id grubs) :recipes (util/map-by-key :id recipes)}) @@ -17,21 +35,49 @@ (defn sync-new-client! [to from] (let [client-id (java.util.UUID/randomUUID) server-shadow (atom cs/empty-state)] - (add-watch state client-id (fn [_ _ _ new-state] - (when-let [msg (cs/diff-states new-state @server-shadow)] + (add-watch state client-id (fn [_ _ _ current-state] + (when-let [msg (cs/diff-states @server-shadow current-state)] (a/put! to msg) - (reset! server-shadow new-state)))) + ;; TODO: reset only if send succeeds? + (reset! server-shadow current-state)))) (a/go-loop [] (if-let [{:keys [type diff hash shadow-hash] :as msg} (! @to-db diff)) - (do (println "Hash check failed --> complete sync") - (let [sync-state @state] - (reset! server-shadow sync-state) - (a/put! to (cs/complete-sync-response sync-state)))))) + :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) + (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)))))) + ;; 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] + (reset! server-shadow sync-state) + (a/put! to (cs/complete-sync-response sync-state)))))) :complete (let [new-state (reset! server-shadow @state)] (a/put! to (cs/complete-sync-response new-state))) (println "Invalid msg:" msg)) diff --git a/src/cljs/grub/core.cljs b/src/cljs/grub/core.cljs index 1064bc4..329485d 100644 --- a/src/cljs/grub/core.cljs +++ b/src/cljs/grub/core.cljs @@ -5,11 +5,14 @@ [cljs.core.async :as a :refer [! chan]]) (:require-macros [grub.macros :refer [log logs]])) -(defn init-app [] - (view/render-app state/app-state) +(defn connect-to-server [reset?] (let [to-remote (chan) from-remote (chan)] (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) diff --git a/src/cljs/grub/state.cljs b/src/cljs/grub/state.cljs index 083c2b4..85b2825 100644 --- a/src/cljs/grub/state.cljs +++ b/src/cljs/grub/state.cljs @@ -8,13 +8,13 @@ (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)] - (add-watch app-state :app-state (fn [_ _ _ new] - (when-let [msg (cs/diff-states new @client-shadow)] + (add-watch app-state :app-state (fn [_ _ _ current-state] + (when-let [msg (cs/diff-states @client-shadow current-state)] (a/put! from msg) - ;; TODO: reset shadow only if send succeeds - (reset! client-shadow new)))) + ;; TODO: reset only if send succeeds + (reset! client-shadow current-state)))) (go-loop [] (if-let [{:keys [type diff hash shadow-hash] :as msg} (" hash) - )))) +(defn diff-states [shadow state] + (when (not= state shadow) + (let [diff (sync/diff-states shadow state) + hash (hasch/uuid state) + shadow-hash (hasch/uuid shadow) + msg (diff-msg diff hash shadow-hash)] + msg + ;(logs "Sync because:") + ;(logs "Local = " state) + ;(logs "Remote = " shadow) + ;(logs "Diff:" diff) + ;(logs "Send" shadow-hash "->" hash) + )))