4808874
Branches > main > 4808874 > files
Make client/connection state epochal
Commit: 4808874f0fd78e38c5340fd7381d51b9e514ce82
Parent: 72082f2
3 changed files:
src/net/bowmap/server/game/handle.clj 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}]}) -(defmethod handle-packet ::serverbound/finish-config [state connection-uuid _]+(defmethod handle-packet ::serverbound/known-packs [_ _ {::p/keys! [known-packs]}]+ (if (= server-known-packs known-packs)+ {:packets (conj registry/registry-packets {::p/id ::clientbound/finish-config})}+ (throw (ex-info "Client does not support the core datapack."+ {:known-packs known-packs+ :server-known-packs server-known-packs}))))++(defmethod handle-packet ::serverbound/finish-config+ [server {::player/keys! [uuid] :as session} _] (let [entity-id (entity/next-entity-id)]- [(rel/transact state- [:update ::server/players- {::server/playing? true- ::entity/id entity-id}- [= ::connection/connection-uuid connection-uuid]])- {::p/id ::clientbound/login- ::entity/id entity-id- :dimensions [registry/world-dimension]- ::p/max-player-count server/max-player-count- :render-distance 32- :simulation-distance 32- :current-dimension-registry-id (registry/registry-id ::mc/dimension_type registry/world-dimension)- :current-dimension registry/world-dimension- :first-8-bytes-of-the-sha-256-hash-of-the-world's-seed 0- ::gamemode/gamemode ::gamemode/adventure- :superflat-world? true- :portal-cooldown-ticks 0- :sea-level 60- :enforce-secure-chat? true}- {::p/id ::clientbound/game-event- ::p/game-event-id ::p/wait-for-chunk-event}]))+ {:server (rel/transact server+ [:update ::server/players+ {::server/playing? true+ ::entity/id entity-id}+ [= ::player/uuid uuid]])+ :session (assoc session ::phase/phase ::phase/play)+ :packets [{::p/id ::clientbound/login+ ::entity/id entity-id+ :dimensions [registry/world-dimension]+ ::p/max-player-count server/max-player-count+ :render-distance 32+ :simulation-distance 32+ :current-dimension-registry-id (registry/registry-id ::mc/dimension_type registry/world-dimension)+ :current-dimension registry/world-dimension+ :first-8-bytes-of-the-sha-256-hash-of-the-world's-seed 0+ ::gamemode/gamemode ::gamemode/adventure+ :superflat-world? true+ :portal-cooldown-ticks 0+ :sea-level 60+ :enforce-secure-chat? true}+ {::p/id ::clientbound/game-event+ ::p/game-event-id ::p/wait-for-chunk-event}]}))src/net/bowmap/server/network/connection.clj 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 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]