instance.clj

Branches > client-handling-rewrite > 27afad4 > 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

(defn keep-alive! [messages-out]
  (io-thread
    (loop []
      (<!! (timeout 20000))
      (put! messages-out {::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 messages-out]
  (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! messages-out))
      (doseq [msg messages]
        (put! messages-out 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 messages-out)))))

(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?
          (recur client))))
    nil))

(defn client
  "Returns a new client value"
  []
  {::state/connection-state ::state/handshaking})

; TODO make reloadable, if port is already running, just swap it out?
(defn start-server! [{:keys [port]}]
  ; Start the client handling loop here
  (let [{::server/keys [server client-chan]} (server/start-server! {:port port})]
    (io-thread
      ; Loop over the instance?
      (loop []
        (when-some [{::server/keys [packets-in packets-out]} (<!! client-chan)]
          (connect! (client) packets-in packets-out)
          (recur)))))
  nil)