615c075
Branches > main > 615c075 > files
Potentially working networking rewrite idk
Commit: 615c0754ee264e3aa08d9b4632a37c92f512d892
Parent: a216cd1
4 changed files:
src/net/bowmap/core.clj view file
@@ -1,13 +1,13 @@ (ns net.bowmap.core (:require- [net.bowmap.minecraft-server :as mc]))+ [net.bowmap.network.minecraft-server :as mc])) (defn -main [& _args]- (let [shutdown (mc/start-server! {:port 25565})]+ (let [shutdown (mc/start-minecraft-server!)] (println "Server Started!") @shutdown)) (comment (binding [*out* ((requiring-resolve 'clojure.java.io/writer) System/out)]- (def shutdown (mc/start-server! {:port 25565})))+ (def shutdown (mc/start-minecraft-server!))) (deliver shutdown :shutdown))src/net/bowmap/message_application.clj view file
@@ -11,20 +11,19 @@ [net.bowmap.server.gamemode :as gamemode])) (s/def ::messages (s/coll-of ::msg/message :type vector?))-(s/def ::process-id qualified-keyword?)-(s/def ::process-timeout pos-int?)-(s/def ::process (s/keys :opt [::process-id ::process-timeout]))-(s/def ::processes (s/coll-of ::process :type vector?))-(s/def ::response (s/keys :opt [::client/client ::messages ::processes])) ; And eventually game events as well :)+(s/def ::response (s/keys :opt [::client/client ::messages])) ; TODO ; I do think having these processes/timeouts/whatever be data with a deadline and just run the things by deadline order is better ; Right now they're scheduled and then you don't even know where they are and have no way to access them or associate them with ; a client...-(defmulti apply-process- (fn [process client]- (::process-id process))) +; Can also shell out to the server and just send a fact in that'll get handled, asynchronously I guess?+; Because this only deals in messages and new client state returned right now.++; TODO rename :p+; Just return effects instead?+; Handle them at the client level (defmulti apply-message "'Applies' a message to a client state. Returns the new client state, as well as the effects that should occur from this message (see the spec)."@@ -146,9 +145,9 @@ ::processes [{::process-id ::keep-alive ::process-timeout 20000}]}) -(defmethod apply-process ::keep-alive [process client]- {::client/client client- ::processes [process]- ::messages- [{::msg/id ::clientbound/keep-alive- :id (long (* (rand) Long/MAX_VALUE))}]})+#_(defmethod apply-process ::keep-alive [process client]+ {::client/client client+ ::processes [process]+ ::messages+ [{::msg/id ::clientbound/keep-alive+ :id (long (* (rand) Long/MAX_VALUE))}]})src/net/bowmap/minecraft_server.clj view file
@@ -1,89 +0,0 @@-(ns net.bowmap.minecraft-server- (:require- [clojure.core.async :refer [<!! >!! alt!! chan close! io-thread put!- timeout]]- [net.bowmap.message-application :as a]- [net.bowmap.network.client :as client]- [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]))--; The logical server, holds a set of clients connected--(defn run-effects! [messages-out process-chan result]- (let [{::a/keys! [processes messages] ::client/keys! [client]} 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 (serverbound/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)))--; TODO I don't really like all these channels...-(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 (clientbound/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})--(defn closable-promise- "Returns a promise that closes 'ch' when it is resolved."- [ch]- (let [p (promise)]- (io-thread- @p- (close! ch))- p))--; TODO run all of this through a var so it's reloadable, and clearly so-; TODO this is networking, and so should be in net.bowmap.server.network-(defn start-server!- "Starts the Minecraft server at the specified port.- Returns a promise that will deliver when the server closes.- Delivering the promise will also close the server."- [{:keys! [port]}]- (let [clients (server/start-tcp-server! {:port port})- shutdown (closable-promise clients)]- ; Loop over the instance?- (io-thread- (loop []- (if-some [{::server/keys! [packets-in packets-out]} (<!! clients)]- (do (connect! (client) packets-in packets-out)- (recur))- (deliver shutdown :shutdown))))- shutdown))src/net/bowmap/network/minecraft_server.clj view file
@@ -0,0 +1,63 @@+(ns net.bowmap.network.minecraft-server + (:require+ [clojure.core.async :refer [<!! >!! alt!! chan close! io-thread put!]]+ [dev.grahp.error :refer [raise]]+ [net.bowmap.message-application :as a]+ [net.bowmap.network.client :as client]+ [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))+ {:keys [::client/client ::a/messages]} (a/apply-message message client)]+ (doseq [message messages]+ (put! outgoing-messages message))+ client))++(defn handle-packets!! [client outgoing-messages]+ (let [{::server/keys! [packets-in packets-out]} client]+ (alt!!+ packets-in ([packet]+ (if (some? packet)+ (handle-incoming-packet client packet outgoing-messages)+ (raise "client incoming packet channel closed!")))+ outgoing-messages ([message]+ (if (some? message)+ (do (>!! packets-out (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 (or (handle-packets!! client outgoing-messages) client))))+ nil))++(defn closable-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 (closable-promise clients)]+ (io-thread+ (loop []+ (if-some [client (<!! clients)]+ (do (connect! client)+ (recur))+ (deliver shutdown :shutdown))))+ shutdown))