minecraft_server.clj
Branches > main > 46a654f > 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)