minecraft_server.clj

Branches > main > 8643329 > files / src / net / bowmap / network / minecraft_server.clj

(ns net.bowmap.network.minecraft-server 
  (:require
   [clojure.core.async :refer [<!! >!! alt!! chan close! io-thread put!
                               timeout]]
   [dev.grahp.error :refer [raise]]
   [net.bowmap.message-application :as a]
   [net.bowmap.network.client :as client]
   [net.bowmap.network.protocol.message :as msg]
   [net.bowmap.network.protocol.message.clientbound :as clientbound]
   [net.bowmap.network.protocol.message.serverbound :as serverbound]
   [net.bowmap.network.protocol.state :as state]
   [net.bowmap.network.server :as server]))

(defn handle-incoming-packet [client packet outgoing-messages]
  (let [message (serverbound/packet->message packet (::state/connection-state client))
        {::client/keys! [client] ::a/keys [messages keep-alive?]} (a/handle-message message client)]
    (doseq [message messages]
      (put! outgoing-messages message))
    (when keep-alive?
      (io-thread
        (loop []
          (when (>!! outgoing-messages {::msg/id ::clientbound/keep-alive
                                        :id (long (* (rand) Long/MAX_VALUE))})
            (<!! (timeout 20000))
            (recur)))))
    client))

(defn handle-packets!! [client outgoing-messages]
  (let [{::server/keys! [incoming-frames outgoing-frames]} client]
    (alt!!
      incoming-frames
      ([frame]
       (if (some? frame)
         (handle-incoming-packet client frame outgoing-messages)
         (raise "client incoming frame channel closed!")))
      outgoing-messages
      ([message]
       (if (some? message)
         (do (>!! outgoing-frames (clientbound/message->packet message (::state/connection-state client)))
             client)
         (raise "client outgoing message channel closed!"))))))

(defn connect! [client]
  (let [outgoing-messages (chan 1024)]
    (io-thread
      (loop [client client]
        (recur (handle-packets!! client outgoing-messages))))
    nil))

(defn closing-promise
  "Returns a promise that closes 'ch' when it is resolved."
  [ch]
  (let [p (promise)]
    (io-thread
      @p
      (close! ch))
    p))

(defn start-minecraft-server!
  "Starts the Minecraft server.
   Returns a promise that will deliver when the server closes.
   Delivering the promise will also close the server."
  []
  (let [clients (server/start-tcp-server! {:port 25565})
        shutdown (closing-promise clients)]
    (io-thread
      (loop []
        (if-some [client (<!! clients)]
          (do (connect! client)
              (recur))
          (deliver shutdown :shutdown))))
    shutdown))