|
1 | 1 | (ns n01se.xkcd1110 |
2 | 2 | (:require [clj-json.core :as json] |
3 | | - [n01se.ws :as ws])) |
| 3 | + [n01se.ws :as ws] )) |
4 | 4 |
|
5 | 5 | (set! *warn-on-reflection* true) |
6 | 6 |
|
| 7 | +(def max-clients 20) |
| 8 | +(def max-msg-length 500) |
| 9 | +(def push-interval 75) |
| 10 | + |
| 11 | +(defn send-one [ws obj] |
| 12 | + (ws/send-message ws (json/generate-string obj))) |
| 13 | + |
7 | 14 | (defn round [num places] |
8 | 15 | (let [factor (Math/pow 10 places)] |
9 | 16 | (/ (Math/round (* num factor)) factor))) |
|
15 | 22 | speed 0.2 |
16 | 23 | delay 150 |
17 | 24 | halfpi (/ Math/PI 2) |
18 | | - ws (ws/ws-client uri :onmessage (fn [_]))] |
19 | | - (ws/sendjson ws {:nick (str "circlebot " i)}) |
| 25 | + ws (ws/ws-client uri :onmessage (fn [_ _]))] |
| 26 | + (send-one ws {:nick (str "circlebot " i)}) |
20 | 27 | (loop [theta (* 0.3 i)] |
21 | | - (ws/sendjson ws |
| 28 | + (send-one ws |
22 | 29 | {:dx (round (* (Math/cos (+ theta halfpi)) speed) 4) |
23 | 30 | :dy (round (* (Math/sin (+ theta halfpi)) speed) 4) |
24 | 31 | :x (Math/round (+ -823 (* (Math/cos theta) radius))) |
|
27 | 34 | (Thread/sleep delay) |
28 | 35 | (recur (+ theta (/ (* speed delay) 150)))))) |
29 | 36 |
|
| 37 | +;; server state: |
| 38 | +(defonce last-id (atom 0)) |
| 39 | +(defonce client-struct (ref {})) ;; ref of map of client obj to thing |
| 40 | +;; could be another ref in the struct: |
| 41 | +(defonce changes (ref {})) ;; ref of map of client obj to client-delta |
| 42 | +(defonce publisher (agent 0)) |
| 43 | + |
| 44 | +(defn get-world [] |
| 45 | + (dosync |
| 46 | + (into {} (for [{:keys [id data-ref]} (vals @client-struct)] |
| 47 | + [id @data-ref])))) |
| 48 | + |
| 49 | +(defn send-all [obj] |
| 50 | + (let [txt (json/generate-string obj)] |
| 51 | + (doseq [struct (vals @client-struct)] |
| 52 | + (try |
| 53 | + (ws/send-message ^WebSocket$Connection (:conn struct) txt) |
| 54 | + (catch java.io.IOException e |
| 55 | + ;; presumably this connection will be closed shortly... |
| 56 | + nil))))) |
| 57 | + |
| 58 | + |
| 59 | +(defn get-id [client] |
| 60 | + (get-in @client-struct [client :id])) |
| 61 | + |
| 62 | +(defn publish [i] |
| 63 | + (when (seq @changes) |
| 64 | + (let [old-changes |
| 65 | + (dosync |
| 66 | + (let [old-changes @changes] |
| 67 | + (alter changes empty) |
| 68 | + old-changes))] |
| 69 | + (send-all {:change |
| 70 | + (zipmap (map get-id (keys old-changes)) |
| 71 | + (vals old-changes))}))) |
| 72 | + ;; do it again: |
| 73 | + (Thread/sleep push-interval) |
| 74 | + (send-off *agent* publish) |
| 75 | + (inc i)) |
| 76 | + |
| 77 | +(defn remove-client [client] |
| 78 | + (let [id (get-id client)] |
| 79 | + (dosync |
| 80 | + (commute client-struct dissoc client) |
| 81 | + (commute changes dissoc client)) |
| 82 | + (println "Client count:" (count @client-struct)) |
| 83 | + (send-all {:delete id}))) |
| 84 | + |
| 85 | +(defn server-onopen [client conn] |
| 86 | + (let [id (swap! last-id inc) |
| 87 | + struct {:id id, :conn conn, :data-ref (ref {})} ] |
| 88 | + (println "New client ID" id conn) |
| 89 | + (dosync |
| 90 | + (commute client-struct assoc client struct)) |
| 91 | + (send-one conn |
| 92 | + {:id id, :all (get-world)}))) |
| 93 | + |
| 94 | +(defn server-onclose [client code msg] |
| 95 | + (println "Client ID" (get-id client) "disconnected") |
| 96 | + (remove-client client)) |
| 97 | + |
| 98 | +(defn server-onmessage [client data] |
| 99 | + (if (> (count data) max-msg-length) |
| 100 | + (do |
| 101 | + (println "Client ID" (get-id client) "sent oversize message" |
| 102 | + (count data) "chars") |
| 103 | + (ws/close (get-in @client-struct [client :conn]))) |
| 104 | + (let [id (get-id client) |
| 105 | + msg (json/parse-string data true)] |
| 106 | + (when-not (:whomp msg) |
| 107 | + (dosync |
| 108 | + (commute changes update-in [client] merge msg) |
| 109 | + (let [data-ref (get-in @client-struct [client :data-ref])] |
| 110 | + (alter data-ref merge msg))))))) |
| 111 | + |
| 112 | +(defn carefully [f] |
| 113 | + (fn [& args] |
| 114 | + (try |
| 115 | + (apply f args) |
| 116 | + (catch Exception e |
| 117 | + (.printStackTrace e) |
| 118 | + (throw e))))) |
| 119 | + |
30 | 120 | (defn -main [& [mode arg1 arg2]] |
31 | | - (condp = mode |
| 121 | + (case mode |
32 | 122 | "server" |
33 | 123 | (let [port (if arg1 (Integer/parseInt arg1) 8090) |
34 | 124 | server (ws/ws-server :port port |
35 | | - :onopen (fn [this conn] (println this conn)))] |
| 125 | + :onopen (carefully #'server-onopen) |
| 126 | + :onclose (carefully #'server-onclose) |
| 127 | + :onmessage #'server-onmessage)] |
36 | 128 | (prn "server" :port port) |
37 | 129 | (.start server) |
| 130 | + (send-off publisher publish) |
38 | 131 | server) |
39 | 132 |
|
40 | 133 | "client" |
|
0 commit comments