4808874

Bowmap · bowmap.git · Tickets

Branches > main > 4808874 > files

Make client/connection state epochal

Grahp, 2026-10-10

Commit: 4808874f0fd78e38c5340fd7381d51b9e514ce82
Parent: 72082f2

3 changed files:

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

@@ -4,84 +4,92 @@    [net.bowmap.server.game.player :as player]    [net.bowmap.server.game.server :as server]    [net.bowmap.server.game.text :as text]-   [net.bowmap.server.network.connection :as connection]    [net.bowmap.server.packet.clientbound :as clientbound]    [net.bowmap.server.packet.entity :as entity]    [net.bowmap.server.packet.gamemode :as gamemode]+   [net.bowmap.server.packet.phase :as phase]    [net.bowmap.server.packet.registry :as registry]    [net.bowmap.server.packet.schema :as p]    [net.bowmap.server.packet.schema.minecraft :as mc]    [net.bowmap.server.packet.serverbound :as serverbound])) +(def initial-session {::phase/phase ::phase/handshaking})+ (defmulti handle-packet-  "Handles a packet from a player given their connection uuid. Takes the game-   state, and returns a vector of nil or a state value, followed by 0 or more-   response packets."-  (fn [state connection-uuid packet]+  (fn [server session packet]     (::p/id packet)))  (defmethod handle-packet :default [_ _ _]) -(defmethod handle-packet ::serverbound/status-request [state _ _]-  [nil-   {::p/id ::clientbound/status-response-    ::p/version-name "26.3"-    ::p/version-id 777-    ::p/motd {::text/content "Bowmap!"-              ::text/color "#9f9f9f"}-    ::p/online-player-count (count (state ::server/players))-    ::p/max-player-count server/max-player-count}])+(defmethod handle-packet ::serverbound/handshaking [_ session packet]+  {:session (assoc session ::phase/phase (req! packet ::phase/phase))})++(defmethod handle-packet ::serverbound/status-request [server _ _]+  {:packets [{::p/id ::clientbound/status-response+              ::p/version-name "26.3"+              ::p/version-id 777+              ::p/motd {::text/content "Bowmap!"+                        ::text/color "#9f9f9f"}+              ::p/online-player-count (count (rel/q server [[:from ::server/players]+                                                            [:where ::server/playing?]]))+              ::p/max-player-count server/max-player-count}]})  (defmethod handle-packet ::serverbound/ping-request [_ _ {::p/keys! [timestamp]}]-  [nil-   {::p/id ::clientbound/pong-response-    ::p/timestamp timestamp}])+  {:packets [{::p/id ::clientbound/pong-response+              ::p/timestamp timestamp}]}) -(defmethod handle-packet ::serverbound/login-start [state connection-uuid {::p/keys! [uuid username]}]-  [(rel/transact state-                 [:insert ::server/players-                  {::player/uuid uuid-                   ::player/username username-                   ::connection/connection-uuid connection-uuid}])-   {::p/id ::clientbound/login-success-    ::p/game-profile {::p/uuid uuid-                      ::p/username username-                      ::p/game-profile-properties []}-    ::p/session-uuid connection-uuid}])+(defmethod handle-packet ::serverbound/login-start+  [server session {::p/keys! [uuid username]}]+  (if (contains? (rel/index server server/players-by-uuid) uuid)+    (throw (ex-info "Player tried to join with an existing UUID."+                    {:uuid uuid}))+    {:server (rel/transact server+                           [:insert ::server/players+                            {::player/uuid uuid+                             ::player/username username}])+     :session (assoc session ::player/uuid uuid)+     :packets [{::p/id ::clientbound/login-success+                ::p/game-profile {::p/uuid uuid+                                  ::p/username username+                                  ::p/game-profile-properties []}+                ::p/session-uuid (random-uuid)}]})) -(defmethod handle-packet ::serverbound/login-acknowledged [_ _ _]-  [nil-   {::p/id ::clientbound/known-packs-    ::p/known-packs [registry/core-data-pack]}])+(def server-known-packs [registry/core-data-pack]) -(defmethod handle-packet ::serverbound/known-packs [state _ {::p/keys! [known-packs]}]-  (if (= [registry/core-data-pack] known-packs)-    (cons state-          (conj registry/registry-packets-                {::p/id ::clientbound/finish-config}))-    (throw (ex-info "Client does not support the core datapack"-                    {:known-packs known-packs}))))+(defmethod handle-packet ::serverbound/login-acknowledged [_ session _]+  {:session (assoc session ::phase/phase ::phase/config)+   :packets [{::p/id ::clientbound/known-packs+              ::p/known-packs server-known-packs}]}) 

Show all 154 lines

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

@@ -1,9 +1,6 @@ (ns net.bowmap.server.network.connection   (:require    [clojure.core.async :refer [io-thread]]-   [clojure.spec.alpha :as s]-   [clojure.stacktrace :refer [print-stack-trace]]-   [clojure.tools.logging :as log]    [net.bowmap.server.network.streams :as streams])   (:import    [java.io IOException]@@ -12,12 +9,13 @@  (defprotocol Connection   (send-frame! [conn frame]-    "Sends 'frame' to the connection asynchronously. Returns nil immediately.")+    "Sends 'frame' to the connection asynchronously. Returns immediately,+     or throws if the connection is closed.")   (receive-frame!! [conn]     "Blocks until a frame can be read from 'conn'.-     Returns nil after frames can no longer be read from the connection."))--(s/def ::connection #(satisfies? Connection %))+     Throws after frames can no longer be read from the connection.")+  (close! [conn]+    "Closes the connection, causing sends and receives to fail."))  (defn connection   "Creates a connection from a given java.net.Socket."@@ -36,11 +34,17 @@         (finally           (Socket/.close socket))))     (reify Connection+      (close! [_]+        (Socket/.close socket))       (send-frame! [_ frame]-        (.put send-queue frame))+        (if (Socket/.isClosed socket)+          (throw (ex-info "Tried to send a frame to a closed connection."+                          {:frame frame}))+          (.put send-queue frame)))       (receive-frame!! [_]-        (try (streams/read-frame!! input-stream)-             (catch IOException _-               (Socket/.close socket))-             (catch Exception e-               (log/warn (with-out-str (print-stack-trace e)))))))))+        (if (Socket/.isClosed socket)+          (throw (Exception. "Connection closed."))+          (try (streams/read-frame!! input-stream)+               (catch Exception e+                 (Socket/.close socket)+                 (throw e))))))))

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

@@ -1,43 +1,63 @@ (ns net.bowmap.server.runtime.connections   (:require    [clojure.core.async :refer [io-thread]]+   [clojure.tools.logging :as log]    [net.bowmap.server.game.handle :as handle]    [net.bowmap.server.game.server :as state]    [net.bowmap.server.network.connection :as connection]    [net.bowmap.server.network.server :as server]-   [net.bowmap.server.packet.phase :as phase]-   [net.bowmap.server.packet.schema :as p]-   [net.bowmap.server.packet.serialize :as serialize]-   [net.bowmap.server.packet.serverbound :as serverbound]))+   [net.bowmap.server.packet.serialize :as serialize])+  (:import+   [java.io IOException])) -(defn apply-packet-  "Returns the value a client should take on when handling 'packet'."-  [client packet]-  (let [{::p/keys! [id]} packet]-    (case id-      ::serverbound/handshaking (assoc client ::phase/phase (req! packet ::phase/phase))-      ::serverbound/login-acknowledged (assoc client ::phase/phase ::phase/config)-      ::serverbound/finish-config (assoc client ::phase/phase ::phase/play)-      client)))+(defn handle-frame [server session frame]+  (handle/handle-packet server session+                        (serialize/decode-frame session frame)))++(defn cas-when!+  "Sets the state of atom to be (get m k) when k is in m.+   Returns the old value when the key is not present, the new value when the+   swap succeeded, or nil when the swap failed."+  [a old k m]+  (if (not (contains? m k))+    old+    (let [v (get m k)]+      (when (compare-and-set! a old v)+        v))))++(defn commit!+  "Calls 'f' with the server state, retrying until the :server key it returns,+   if any, is committed. Returns the result of 'f'."+  [f]+  (loop []+    (let [server @state/state+          result (f server)]+      (if (cas-when! state/state server :server result)+        result+        (recur)))))++(defn connection-loop [connection]+  (let [session-atom (atom handle/initial-session)]+    (loop []+      (let [frame (connection/receive-frame!! connection)+            session @session-atom+            result (commit! #(handle-frame % session frame))]+        (if-let [session' (cas-when! session-atom session :session result)]+          (doseq [packet (:packets result)]+            (connection/send-frame! connection (serialize/encode-packet session' packet)))+          (throw (ex-info "Session state changed while receiving packet."+                          {:old-session session+                           :new-session (:session result)}))))+      (recur))))  (defn handle-connection! [connection]-  (let [client-atom (atom {::phase/phase ::phase/handshaking})-        connection-uuid (random-uuid)-        send-packet! (fn [packet]-                       (let [frame (serialize/encode-packet @client-atom packet)]-                         (connection/send-frame! connection frame)))]-    (loop []-      (when-some [frame (connection/receive-frame!! connection)]-        (let [client @client-atom-              packet (serialize/decode-frame client frame)-              client' (apply-packet client packet)]-          (assert (compare-and-set! client-atom client client'))-          (loop [state @state/state]-            (let [[state' & packets] (handle/handle-packet state connection-uuid packet)]-              (if (or (nil? state') (compare-and-set! state/state state state'))-                (run! send-packet! packets)-                (recur @state/state)))))-        (recur)))))+  (try+    (connection-loop connection)+    (catch IOException _)+    (catch Exception e+      (log/warn e))+    (finally+      (connection/close! connection))))  (defn handle-connections!   [server]