instance.clj
Branches > refactor/use-spec-1 > 2eb109b > files / src / net / bowmap / instance.clj
; Could be called minecraft-server?
(ns net.bowmap.instance
(:require
[clojure.core.async :refer [<!! >!! alt!! chan io-thread put! timeout]]
[net.bowmap.message-application :as a]
[net.bowmap.network.client :as client]
[net.bowmap.network.message :as msg]
[net.bowmap.network.message.client :as cl]
[net.bowmap.network.message.server :as srv]
[net.bowmap.network.server :as server]
[net.bowmap.network.state :as state]))
; The logical server, holds a set of clients connected
; TODO Global state D:
(defonce instance (atom {::clients {}
::message-out-channels {}}))
(defonce instance2 (atom {::conections {}}))
(defn send-message! [client msg]
; TODO is put! okay?
(put! ((::message-out-channels @instance) (::client/id client)) msg))
(defn keep-alive! [client]
(io-thread
(loop []
(<!! (timeout 20000))
(send-message! client {::msg/id ::cl/keep-alive
:id (long (* (rand) Long/MAX_VALUE))})
(recur))))
(defn read-and-handle-packet
"Reads/deserializes one packet into a message, and handles it.
Because packet reading is dependent on the state of the client, and handling a packet can update the state of the client,
Reading and handling must be synchronized!"
[pk client]
(when-some [msg (srv/packet->message pk (::state/connection-state client))]
(let [{:keys [::client/client ::a/keep-alive? ::a/messages]} (a/apply-message msg client)]
(when keep-alive?
(keep-alive! client))
(doseq [msg messages]
(send-message! client msg))
client)))
(defn handle-packets-and-messages [client packets-in messages-out packets-out]
(alt!!
messages-out ([msg]
(when msg
(assert (>!! packets-out (cl/message->packet msg (::state/connection-state client))))
client))
packets-in ([pk]
(when pk
(read-and-handle-packet pk client)))))
(defn connect! [client packets-in packets-out]
(let [messages-out (chan 1024)]
; TODO error handling and disconnect
(io-thread
(loop [client client]
(when-some [client (handle-packets-and-messages client packets-in messages-out packets-out)]
; Otherwise, stop?
(swap! instance update ::clients assoc (::client/id client) client)
(recur client))))
(swap! instance update ::clients assoc (::client/id client) client)
(swap! instance update ::message-out-channels assoc (::client/id client) messages-out)
client))
(defn client
"Returns a new client value"
[]
; TODO this state should be server-wide, I think.
; ??? What is he on about ^
{::state/connection-state ::state/handshaking
::client/id (random-uuid)})
(defn handle-client [packets-in packets-out]
(let [client (client)]
(connect! client packets-in packets-out)))
; TODO rename...
(defn start-server! [{:keys [port]}]
(server/start-server! {:port port
:handler #'handle-client})
nil)