diff --git a/src/cljx/grub/sync.cljx b/src/cljx/grub/sync.cljx index 0378745..35a182a 100644 --- a/src/cljx/grub/sync.cljx +++ b/src/cljx/grub/sync.cljx @@ -24,18 +24,22 @@ {:type :diff :diff diff})) -(defmulti handle-event (fn [event] (:type event))) +(defmulti handle-event (fn [event] + (:type event))) + +(defn apply-diff [states diff shadow] + (let [new-states (swap! states update-states diff) + new-state (state/get-latest new-states) + new-shadow (diff/patch-state shadow diff true)] + {:out-event (when-not (state/state= shadow new-state) + (diff-msg new-shadow new-state)) + :new-states new-states + :new-shadow new-shadow})) (defmethod handle-event :diff [{:keys [diff states shadow client?]}] (let [history-shadow (state/get-tagged @states (:shadow-tag diff))] (if history-shadow - (let [new-states (swap! states update-states diff) - new-state (state/get-latest new-states) - new-shadow (diff/patch-state history-shadow diff true)] - {:out-event (when-not (state/state= history-shadow new-state) - (diff-msg new-shadow new-state)) - :new-states new-states - :new-shadow new-shadow}) + (apply-diff states diff history-shadow) (if client? {:out-event full-sync-request :new-shadow shadow} diff --git a/src/test/grub/test/integration/synchronization.clj b/src/test/grub/test/integration/synchronization.clj index baa2c3b..a0c2dea 100644 --- a/src/test/grub/test/integration/synchronization.clj +++ b/src/test/grub/test/integration/synchronization.clj @@ -1,5 +1,6 @@ (ns grub.test.integration.synchronization (:require [grub.sync :as sync] + [grub.state :as state] [clojure.test :refer :all] [midje.sweet :refer :all] [clojure.core.async :as a :refer [!! chan go]])) @@ -8,58 +9,83 @@ (let [[v p] (a/alts!! [c (a/timeout 100)])] v)) -;; (fact "Client-only changes synced with server" -;; (let [client-shadow {:grubs {"1" {:text "2 apples" :completed true}} :recipes {}} -;; client-states (states-atom -;; {:grubs {"1" {:text "2 apples" :completed false}} :recipes {}} -;; {:grubs {"1" {:text "2 apples" :completed true}} :recipes {}}) -;; server-shadow {:grubs {"1" {:text "2 apples" :completed false}} :recipes {}} -;; server-states (states-atom server-shadow) -;; client-in (chan) -;; client-out (chan) -;; server-in (chan) -;; server-out (chan) -;; client-state-changes (chan 1) -;; msg {:type :new-state -;; :state {:grubs {"1" {:text "2 apples" :completed true}} :recipes {}}}] -;; (a/pipe client-out server-in) -;; (a/pipe server-out client-in) -;; (sync/make-client-agent client-in client-out client-states server-shadow) -;; (sync/make-server-agent server-in server-out server-states client-shadow) -;; (add-watch client-states :test (fn [_ _ _ new-states] (a/put! client-state-changes new-states))) -;; (>!! client-in msg) -;; ( {:grubs {"1" {:completed true, :text "2 apples"}} -;; :recipes {}} -;; (:state (last @server-states)) => {:grubs {"1" {:completed true, :text "2 apples"}} -;; :recipes {}})) +(defn client-server [client-states server-states] + (let [server-shadow (last @server-states) + client-shadow (last @client-states) + new-client-states (chan) + >client (chan) + new-server-states (chan) + >server (chan)] + (sync/make-client-agent >server >client new-client-states client-states server-shadow) + (sync/make-server-agent >client >server new-server-states server-states client-shadow) + {:new-client-states new-client-states + :new-server-states new-server-states})) + +(defn states-in-sync? [a b] + (state/state= (last a) (last b))) + +(defn last-state [states] + (-> states + (last) + (dissoc :tag))) + +(defn short-delay [] + (!! new-client-states client-change) + (short-delay) + (states-in-sync? @client @server) => true + (last-state @client) => {:grubs {"1" {:text "2 apples" :completed true}} + :recipes {}})) + +(fact "Other client changes synced with client" + (let [client (atom [{:tag 1 + :grubs {"1" {:text "2 apples" :completed false}} + :recipes {}}]) + server (atom [{:tag 44 :grubs {"1" {:text "2 apples" :completed false}} + :recipes {}}]) + {:keys [new-server-states]} (client-server client server) + server-change {:tag 2 + :grubs {"1" {:text "2 apples" :completed true}} + :recipes {}}] + (swap! server conj server-change) + (>!! new-server-states server-change) + (short-delay) + (states-in-sync? @client @server) => true + (last-state @client) => {:grubs {"1" {:text "2 apples" :completed true}} + :recipes {}})) + +(fact "Client changes and simultaneous server changes synced" + (let [client (atom [{:tag 1 + :grubs {"1" {:text "2 apples" :completed false}} + :recipes {}}]) + server (atom [{:tag 44 :grubs {"1" {:text "2 apples" :completed false}} + :recipes {}}]) + {:keys [new-client-states]} (client-server client server) + client-change {:tag 2 + :grubs {"1" {:text "2 apples" :completed true}} + :recipes {}} + server-change {:tag 45 + :grubs {"1" {:text "2 apples" :completed false} + "2" {:text "milk" :completed false}} + :recipes {}}] + (swap! client conj client-change) + (swap! server conj server-change) + (>!! new-client-states client-change) + (short-delay) + (states-in-sync? @client @server) => true + (last-state @client) => {:grubs {"1" {:text "2 apples" :completed true} + "2" {:text "milk" :completed false}} + :recipes {}})) -;; (fact "Client and server changes synced" -;; (let [client-shadow {:grubs {"1" {:text "2 apples" :completed false}} :recipes {}} -;; client-states (states-atom -;; {:grubs {"1" {:text "2 apples" :completed false}} :recipes {}} -;; {:grubs {"1" {:text "2 apples" :completed true}} :recipes {}}) -;; server-shadow {:grubs {"1" {:text "2 apples" :completed false}} :recipes {}} -;; server-states (states-atom -;; server-shadow -;; {:grubs {"1" {:text "4 apples" :completed false}} :recipes {}}) -;; client-in (chan) -;; client-out (chan) -;; server-in (chan) -;; server-out (chan) -;; msg {:type :new-state -;; :state {:grubs {"1" {:text "2 apples" :completed true}} :recipes {}}} -;; client-state-changes (chan 1)] -;; (a/pipe client-out server-in) -;; (a/pipe server-out client-in) -;; (sync/make-client-agent client-in client-out client-states server-shadow) -;; (sync/make-server-agent server-in server-out server-states client-shadow) -;; (add-watch client-states :test (fn [_ _ _ new-states] (a/put! client-state-changes new-states))) -;; (>!! client-in msg) -;; ( (hashed-states -;; {:grubs {"1" {:completed true, :text "4 apples"}}, :recipes {}}) -;; @server-states => (hashed-states -;; {:grubs {"1" {:completed false, :text "2 apples"}}, :recipes {}} -;; {:grubs {"1" {:completed false, :text "4 apples"}}, :recipes {}} -;; {:grubs {"1" {:completed true, :text "4 apples"}}, :recipes {}})))