083e2e7

Branches > facts > 083e2e7 > files / src / net / bowmap

Use a ref for client state

Grahp, 2026-08-20

Commit: 083e2e72d5aec9c4f2e40f4387f9365685e914d4
Parent: 108a097

3 changed files:

src/net/bowmap/network/client.clj modified (view file)

@@ -1,32 +1,23 @@ (ns net.bowmap.network.client   (:require    [net.bowmap.network.packets.clientbound :refer [encode-packet!]]+   [net.bowmap.network.packets.serverbound :refer [decode-packet!]]    [net.bowmap.network.state :as state]-   [net.bowmap.network.streams :as streams]-   [net.bowmap.server.entity :as entity]-   [net.bowmap.server.server :as server])+   [net.bowmap.network.streams :as streams])   (:import    [java.net Socket])) -(defn send-clientbound-packet [client packet]-  #_(println "sending packet out: " packet)-  (streams/write-frame!! (encode-packet! packet (req! client ::state/connection-state))-                         (Socket/.getOutputStream (req! client ::socket)))-  client)--(defn leave! [client-agent]-  (try-    (dosync-      (when-let [uuid (::entity/uuid @client-agent)]-        (commute server/players dissoc uuid)))-    (finally-      (Socket/.close (req! @client-agent ::socket)))))- (defn client [socket]-  (agent {::socket socket-          ::state/connection-state ::state/handshaking}-         :error-handler (fn [a ex]-                          (try-                            (Throwable/.printStackTrace ex *err*)-                            (finally-                              (leave! a))))))+  (ref {::state/connection-state ::state/handshaking+        ::socket socket}))++(defn read-packet! [client-ref]+  (let [{:keys! [::socket ::state/connection-state]} @client-ref+        frame (streams/read-frame!! (Socket/.getInputStream socket))]+    (decode-packet! frame connection-state)))++(defn send-packet! [client-ref packet]+  (let [{:keys! [::socket ::state/connection-state]} @client-ref]+    #_(println "sending packet out: " packet)+    (streams/write-frame!! (encode-packet! packet connection-state) (Socket/.getOutputStream socket))+    client))

src/net/bowmap/network/handle.clj modified (view file)

@@ -1,187 +1,42 @@ (ns net.bowmap.network.handle   (:require-   [net.bowmap.network.chunk :as chunk]-   [net.bowmap.network.client :as client]+   [net.bowmap.network.client :refer [send-packet!]]    [net.bowmap.network.packet :as pk]    [net.bowmap.network.packets.clientbound :as clientbound]-   [net.bowmap.network.packets.registry :as registry]-   [net.bowmap.network.packets.serverbound :as serverbound :refer [decode-packet!]]-   [net.bowmap.network.registry :as reg]+   [net.bowmap.network.packets.serverbound :as serverbound]    [net.bowmap.network.state :as state]-   [net.bowmap.server.entity :as entity]-   [net.bowmap.server.gamemode :as gamemode]    [net.bowmap.server.server :as server])) -; Handles incoming packets from clients--(defmulti handler+(defmulti handle-packet   (fn [client packet]     (::pk/id packet))) -(defmethod handler :default [client pk]-  #_(prn "Unhandled packet:" pk)-  client)+(defmethod handle-packet :default [client _]+  {:client client}) -(defmethod handler ::serverbound/handshaking [client pk]-  (assoc client-         ::state/connection-state (req! pk :intent)-         :protocol-id (req! pk :protocol)))+(defmethod handle-packet ::serverbound/handshaking [client pk]+  (let [{:keys! [intent protocol]} pk]+    {:client (assoc client+                    ::state/connection-state intent+                    :protocol-id protocol)}))  ; TODO move to a fact and the game logic handler :)-(defmethod handler ::serverbound/status-request [client _]-  (client/send-clientbound-packet client-        {::pk/id ::clientbound/status-response-         ;; Should be un-bundled yeah.-         :response {:version {:name "26.2"-                              :protocol 776}-                    :players {:online (count @server/players)-                              :max server/max-player-count}-                    ; Is this a text component? Could make that interface-                    :description {:text "Bowmap!"}-                    :enforcesSecureChat false}}))+(defmethod handle-packet ::serverbound/status-request [client _]+  {:client client+   :packets [{::pk/id ::clientbound/status-response+              ;; Should be un-bundled yeah.+              :response {:version {:name "26.2"+                                   :protocol 776}+                         :players {:online (count @server/players)+                                   :max server/max-player-count}+                         ; Is this a text component? Could make that interface+                         :description {:text "Bowmap!"}+                         :enforcesSecureChat false}}]}) -(defmethod handler ::serverbound/ping-request [client pk]-  (client/send-clientbound-packet-    client-    {::pk/id ::clientbound/pong-response-     :timestamp (:timestamp pk)}))--(defmethod handler ::serverbound/login-start [client pk]-  (let [{:keys! [uuid username]} pk]-    (client/send-clientbound-packet-      (assoc client ::entity/uuid uuid)-      {::pk/id ::clientbound/login-success-       :profile {:uuid uuid-                 :username username-                 :properties []}-       :session-id (random-uuid)})))--(defmethod handler ::serverbound/login-acknowledged [client _]-  (client/send-clientbound-packet-    (assoc client ::state/connection-state ::state/config)-    {::pk/id ::clientbound/known-packs-     ;; TODO also probably good to unbundle this-     :known-packs [{:namespace "minecraft"-                    :id "core"-                    :version "26.2"}]}))--(defmethod handler ::serverbound/known-packs [client _]-  (doseq [pk (registry/registry-packets)]-    (client/send-clientbound-packet client pk))--  ; TODO ofc move these to the registry :p-  (client/send-clientbound-packet client {::pk/id ::clientbound/update-tags-                                          :tagged-registries [{:registry "minecraft:block"-                                                               :tags [{:tag-name "bowmap:infiniburn"-                                                                       :entries []}]}]})-  (client/send-clientbound-packet client {::pk/id ::clientbound/update-tags-                                          :tagged-registries [{:registry "minecraft:damage_type"-                                                               :tags [{:tag-name "minecraft:is_fire"-                                                                       :entries []}-                                                                      {:tag-name "minecraft:is_explosion"-                                                                       :entries []}-                                                                      {:tag-name "minecraft:bypasses_shield"-                                                                       :entries []}]}]})-  (client/send-clientbound-packet client {::pk/id ::clientbound/update-tags-                                          :tagged-registries [{:registry "minecraft:banner_pattern"-                                                               :tags [{:tag-name "minecraft:pattern_item/bordure_indented" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/creeper" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/field_masoned" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/flow" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/flower" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/globe" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/guster" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/mojang" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/piglin" :entries []}-                                                                      {:tag-name "minecraft:pattern_item/skull" :entries []}]}]})---  (client/send-clientbound-packet client {::pk/id ::clientbound/finish-config}))--(defn- build-chunk-packets []-  (let [pk {::pk/id ::clientbound/chunk-data-and-update-light-            :chunk-data-            (let [stone {:block-count (* 16 16 16)-                         :fluid-count 0-                         :block-states {:bpe 0-                                        :palatte 1}-                         :biomes {:bpe 0-                                  :palatte 0}}-                  air {:block-count 0-                       :fluid-count 0-                       :block-states {:bpe 0-                                      :palatte 0}-                       :biomes {:bpe 0-                                :palatte 0}}]-              {:data (cons stone (repeat (- chunk/world-height-chunk-sections 1) air))})-            :light-data {}}]-    (for [x (range -16 17)-          z (range -16 17)]-      (assoc pk-             :chunk-x x-             :chunk-z z))))--(defmethod handler ::serverbound/finish-config [client _]-  (let [entity-id (entity/next-entity-id!)-        uuid (req! client ::entity/uuid)-        client (assoc client-                      ::state/connection-state ::state/play-                      ::entity/id entity-id)-        player {::entity/id entity-id-                ::entity/uuid uuid}]-    (client/send-clientbound-packet client-                                    {::pk/id ::clientbound/login-                                     :entity-id entity-id-                                     :hardcore? false-                                     ; TODO use the registry id, show that connection somehow because this is a reference to that id-                                     :dimension-names [reg/world-dimension]-                                     :max-players server/max-player-count-                                     :view-distance 32-                                     :simulation-distance 32-                                     :reduced-debug-info? false-                                     :enable-respawn-screen? false-                                     :do-limited-crafting? false-                                     :dimension-type-id reg/world-dimension-id-                                     :dimension-name reg/world-dimension-                                     :hashed-seed 203894-                                     :gamemode ::gamemode/adventure-                                     :debug-world? false-                                     :superflat-world? true-                                     :has-death-location? false-                                     :portal-cooldown 10-                                     :sea-level 60-                                     :online-mode? false-                                     :enforce-secure-chat? true})-+(defn handle!! [client-ref packet]+  (let [{:keys [client packets]} (handle-packet @client-ref packet)]     (dosync-      (if (@server/players uuid)-        (throw (Exception. "Player tried to join with an existing UUID"))-        (commute server/players assoc uuid player)))--    (client/send-clientbound-packet client-                                    {::pk/id ::clientbound/game-event-                                     :event :wait-for-chunks})--    ; TODO can't enqueue keepalive because I have no access to the queue!--    (doseq [pk (build-chunk-packets)]-      (client/send-clientbound-packet client pk))-    (client/send-clientbound-packet client-                                    {::pk/id ::clientbound/synchronize-player-position-                                     :teleport-id (rand-int Integer/MAX_VALUE)-                                     :x 0-                                     :y 16-                                     :z 0-                                     :velocity-x 0-                                     :velocity-y 0-                                     :velocity-z 0-                                     :yaw 0-                                     :pitch 0-                                     :flags #{}})))--; TODO keepalive--(defn process-serverbound-frame [client frame]-  (let [packet (decode-packet! frame (req! client ::state/connection-state))]-    ; TODO make handler return packets and send them out here-    (handler client packet)))+      (ref-set client-ref client))+    (doseq [pk packets]+      (send-packet! client-ref pk))+    client))

src/net/bowmap/network/server.clj modified (view file)

@@ -1,9 +1,8 @@ (ns net.bowmap.network.server   (:require    [clojure.core.async :refer [io-thread]]-   [net.bowmap.network.client :as client :refer [leave!]]-   [net.bowmap.network.handle :as handle]-   [net.bowmap.network.streams :as streams])+   [net.bowmap.network.client :as client]+   [net.bowmap.network.handle :as handle])   (:import    [java.io EOFException]    [java.net ServerSocket Socket SocketException]))@@ -12,17 +11,15 @@   (let [server (ServerSocket. port)]     (loop []       (let [socket (ServerSocket/.accept server)-            input-stream (Socket/.getInputStream socket)-            client-agent (client/client socket)]+            client-ref (client/client socket)]         (io-thread           (try             (loop []               ; TODO this is another way the client can leave!-              (let [frame (streams/read-frame!! input-stream)]-                (send client-agent handle/process-serverbound-frame frame))+              (handle/handle!! client-ref (client/read-packet! client-ref))               (recur))             (catch EOFException _)             (catch SocketException _)             (finally-              (leave! client-agent)))))+              (Socket/.close socket)))))       (recur))))