minecraft_server.clj

Branches > main > 60172ff > files / src / net / bowmap / minecraft_server.clj

(ns net.bowmap.minecraft-server
  (:require
   [clojure.core.async :refer [<!! >!! alt!! chan io-thread put! timeout]]
   [dev.grahp.error :refer [guard]]
   [net.bowmap.message-application :as a]
   [net.bowmap.network.client :as client]
   [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 run-effects! [messages-out process-chan result]
  (let [{:keys [::client/client ::a/processes ::a/messages]} result]
    (doseq [process processes]
      (io-thread
        (<!! (timeout (::a/process-timeout process)))
        (>!! process-chan process)))
    (doseq [msg messages]
      (put! messages-out msg))
    client))

(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 process-chan]
  (when-some [msg (srv/packet->message pk (::state/connection-state client))]
    (run-effects! messages-out process-chan (a/apply-message msg client))))

(defn handle-process! [process client messages-out process-chan]
  (run-effects! messages-out process-chan (a/apply-process process client)))

(defn handle-packets-and-messages! [client packets-in messages-out packets-out process-chan]
  (alt!!
    process-chan ([process]
                  (handle-process! process client messages-out process-chan))
    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 process-chan)))))

(defn connect! [client packets-in packets-out]
  (let [messages-out (chan 1024)
        process-chan (chan 64)]
    ; TODO error handling and disconnect
    (io-thread
      (loop [client client]
        (when-some [client (handle-packets-and-messages! client packets-in messages-out packets-out process-chan)]
          ; 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]}]
  (let [{::server/keys [server client-chan]} (server/start-tcp-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)