0b339d9

Branches > main > 0b339d9 > files / src / net / bowmap

Remove global reference types to clients and connections and handle client state locally

Grahp, 2026-08-21

Commit: 0b339d9bee779aa3e693bda9d66e2afcbbd1a7bb
Parent: 5534b1b

5 changed files:

src/net/bowmap/network/client.clj modified (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 modified (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 modified (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 modified (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 modified (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))