0b339d9
Branches > main > 0b339d9 > files / src / net / bowmap
Remove global reference types to clients and connections and handle client state locally
Commit: 0b339d9bee779aa3e693bda9d66e2afcbbd1a7bb
Parent: 5534b1b
5 changed files:
src/net/bowmap/network/client.clj view file
@@ -1,11 +1,20 @@ (ns net.bowmap.network.client (:require+ [clojure.core.async :refer [chan]] [clojure.spec.alpha :as s] [net.bowmap.network.connection :as connection]+ [net.bowmap.network.packets.clientbound :refer [encode-packet!]] [net.bowmap.network.state :as state])) (s/def ::client (s/keys :opt [::state/connection-state ::connection/connection])) (defn client [socket]- (ref {::state/connection-state ::state/handshaking- ::connection/connection (connection/connection socket)}))+ {::state/connection-state ::state/handshaking+ ::connection/connection (connection/connection socket)+ ; Not the best place but I can move later+ ::clientbound-packets (chan 1024)})++(defn send-packet!! [client packet]+ (let [{:keys! [::connection/connection ::state/connection-state]} client+ frame (encode-packet! packet connection-state)]+ (connection/send-frame!! frame connection)))src/net/bowmap/network/connection.clj view file
@@ -11,14 +11,12 @@ ; TODO I really don't like any of this lol (defn connection [socket]- (agent {::socket socket- ::input-stream (Socket/.getInputStream socket)- ::output-stream (Socket/.getOutputStream socket)}))+ {::socket socket+ ::input-stream (Socket/.getInputStream socket)+ ::output-stream (Socket/.getOutputStream socket)}) -(defn send-frame-action! [connection frame]- (let [{:keys! [::output-stream]} connection]- (streams/write-frame!! frame output-stream)- connection))+(defn read-frame!! [connection]+ (streams/read-frame!! (req! connection ::input-stream))) -(defn close! [connection]- (Socket/.close (req! connection ::socket)))+(defn send-frame!! [frame connection]+ (streams/write-frame!! frame (req! connection ::output-stream)))src/net/bowmap/network/handle.clj view file
@@ -1,5 +1,6 @@ (ns net.bowmap.network.handle (:require+ [clojure.core.async :refer [put!]] [net.bowmap.network.chunk :as chunk] [net.bowmap.network.client :as client] [net.bowmap.network.connection :as connection]@@ -167,15 +168,14 @@ :keepalive? true})) ; TODO just make this a pure function and put the handling logic elsewhere to make the separation clearer-(defn handle!! [client-ref packet]- (dosync- ; May want sync-facts vs. async-facts?- ; The facts themselves shouldn't care but the handler might- (let [{:keys [client packets facts keepalive?] :or {client @client-ref}} (handle-packet @client-ref packet)]- (ref-set client-ref client)- (let [{:keys! [::connection/connection ::state/connection-state]} @client-ref]- (doseq [pk packets]- (send connection connection/send-frame-action! (clientbound/encode-packet! pk connection-state)))- ; TODO re-re-add keepalive- (doseq [fact facts]- (handle-responses! (logic/handle-fact fact)))))))+(defn handle!! [client packet]+ ; May want sync-facts vs. async-facts?+ ; The facts themselves shouldn't care but the handler might+ (let [{:keys [client packets facts keepalive?] :or {client client}} (handle-packet client packet)+ {:keys! [::client/clientbound-packets ::state/connection-state]} client]+ (doseq [pk packets]+ (put! clientbound-packets pk))+ ; TODO re-re-add keepalive+ (doseq [fact facts]+ (handle-responses! (logic/handle-fact fact)))+ client))src/net/bowmap/network/server.clj view file
@@ -1,18 +1,44 @@ (ns net.bowmap.network.server (:require- [clojure.core.async :refer [io-thread]]- [net.bowmap.network.client :as client]+ [clojure.core.async :refer [>!! alt!! chan close! io-thread]]+ [net.bowmap.network.client :as client :refer [send-packet!!]] [net.bowmap.network.connection :as connection] [net.bowmap.network.handle :as handle] [net.bowmap.network.packets.serverbound :refer [decode-packet!]]- [net.bowmap.network.state :as state]- [net.bowmap.network.streams :as streams])+ [net.bowmap.network.state :as state]) (:import- [java.io EOFException]- [java.net ServerSocket Socket SocketException]))+ [java.net ServerSocket])) (defonce server (atom nil)) +; TODO handle errors and disconnect player+; Send player disconnected fact, and update the player list from that+(defn handle-client [client]+ (io-thread+ (let [serverbound-frames (chan 64)+ clientbound-packets (req! client ::client/clientbound-packets)]+ (io-thread+ (try+ (loop []+ (>!! serverbound-frames (connection/read-frame!! (::connection/connection client)))+ (recur))+ (finally+ (close! serverbound-frames))))+ (loop [client client]+ (recur+ (alt!!+ serverbound-frames ([frame]+ (if (some? frame)+ (handle/handle!! client (decode-packet! frame (::state/connection-state client)))+ ; TODO should you disconnect the client when this happens?+ (throw (Exception. "AAAAH"))))+ clientbound-packets ([pk]+ (if (some? pk)+ ; TODO Can't sending packets change the client state?+ (do (send-packet!! client pk)+ client)+ (throw (Exception. "Disconnect, eventually"))))))))))+ (defn start-server! [port] (when-let [server @server] (ServerSocket/.close server))@@ -20,32 +46,6 @@ (reset! server server-socket) (loop [] (let [socket (ServerSocket/.accept server-socket)- input-stream (Socket/.getInputStream socket)- ; HERE IS THE NEW AGE!- ; A client is a piece of data, it has state info- ; Get this ref out of here, we don't like it.- ; Connections do I/O, and they need no state.- ; They're just for frames- ; They also don't need synchronized- ; There does need to be one point in the program where serverbound packets are handled 1 at a time- ; And a new client state will be returned from that handling, as well as other things to happen- client-ref (client/client socket)]- (io-thread- (try- ; TODO also read packets from the clientbound channel!- (loop []- (let [frame (streams/read-frame!! input-stream)]- (dosync- (->> (decode-packet! frame (::state/connection-state @client-ref))- ; So I want sending a clientbound packet to be kind of a private detail- ; But it could happen asynchronously at any time...- ; So the client may need to keep its connection object, which I don't love.- ; Perhaps a map of player uuids to connections/to a queue you could send packets out onto would be best?- (handle/handle!! client-ref))))- (recur))- (catch EOFException _)- (catch SocketException _)- (finally- ; TODO Send player disconnected fact, and update the player list from that- (send (@client-ref ::connection/connection) connection/close!)))))+ client (client/client socket)]+ (handle-client client)) (recur))))src/net/bowmap/stormbow/responses.clj view file
@@ -1,5 +1,6 @@ (ns net.bowmap.stormbow.responses (:require+ [clojure.core.async :refer [put!]] [net.bowmap.network.client :as client] [net.bowmap.network.connection :as connection] [net.bowmap.network.packet :as pk]@@ -21,11 +22,11 @@ ; The client value may well have changed since this method was called (defmethod handle-response! ::response/send-server-list [response] (let [{:keys! [::client/client server-list-response]} response- {:keys! [::state/connection-state ::connection/connection]} client+ {:keys! [::state/connection-state ::client/clientbound-packets]} client packet {::pk/id ::clientbound/status-response- :response server-list-response}- frame (encode-packet! packet connection-state)]- (send connection connection/send-frame-action! frame)))+ :response server-list-response}]+ ; Probably just want to return these packets+ (put! clientbound-packets packet))) (defn handle-responses! [responses] (run! handle-response! responses))