ab39dc4

Branches > client-handling-rewrite > ab39dc4 > files / src / net / bowmap

Update packet code

Grahp, 2026-02-27

Commit: ab39dc45bcd9e0bacd7d506c11e40446e5b2ccc3
Parent: e528244

5 changed files:

src/net/bowmap/codecs/read.clj modified (view file)

@@ -37,6 +37,20 @@     (ensure-not! neg? len)     (ByteBuf/.readCharSequence buf len (Charset/forName "UTF-8")))) +(defn read-enum! [codec m buf]+  (m (codec buf)))++(defn read-array [codec len buf])++(declare readers reader)++(defn reader+  "Returns a form that will evaluate to a function that takes a buffer and reads the respective type from it."+  ([t]+   (or (readers t) (raise "No such type!" {:type t})))+  ([t & args]+   (apply (reader t) args)))+ (def readers   {:byte   `ByteBuf/.readByte    :ubyte  `(comp unchecked-byte ByteBuf/.readByte)@@ -45,18 +59,6 @@    :int    `ByteBuf/.readInt    :long   `ByteBuf/.readLong    :varint `read-varint!-   :string `read-string!})--(defn reader [t]-  (or (readers t) (raise "No such type!" {:type t})))--(defn read-enum! [codec m buf]-  (m (codec buf)))--(defn read-array [codec len buf])--; Perhaps have :optional and :required, so all non :optional or :prefixed-optional fields are implicitly :required-; Otherwise nils can easily creep into these functions and cause random NPEs-(def composite-readers-  {:enum (fn [t enum-fn]-           `(fn [buf#] (read-enum! ~(reader t) ~enum-fn buf#)))})+   :string `read-string!+   :enum (fn [t m]+           `(fn [buf#] (read-enum! ~(reader t) ~m buf#)))})

src/net/bowmap/codecs/write.clj modified (view file)

@@ -1,6 +1,7 @@ (ns net.bowmap.codecs.write   (:require-   [clojure.data.json :as json])+   [clojure.data.json :as json]+   [dev.grahp.error :refer [raise]])   (:import    [io.netty.buffer ByteBuf]    [java.nio.charset Charset]))@@ -23,6 +24,17 @@ (defn write-json! [buf s]   (write-string! buf (json/write-str s))) +(declare writer writers)++;; I think this should have :required/:optional/:prefixed-optional,+;; and have it that non-optional fields are implicity :required and thus nil checked+(defn writer+  "Returns a form that will evaluate to a function that takes a buffer and writes the respective type to it."+  ([t]+   (or (writers t) (raise "No such type!" {:type t})))+  ([t & args]+   (apply (writer t) args)))+ (def writers   {:byte   `ByteBuf/.writeByte    :ubyte  `ByteBuf/.writeByte@@ -33,6 +45,3 @@    :varint `write-varint!    :string `write-string!    :json   `write-json!})--(def composite-writers-  {})

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

@@ -6,7 +6,8 @@    [dev.grahp.pairs :refer [pairs]]    [net.bowmap.client.state :as state]    [net.bowmap.codecs.write :as write :refer [write-varint!]]-   [net.bowmap.packet :as pk :refer [packet?]])+   [net.bowmap.packet :as pk :refer [packet?]]+   [net.bowmap.packets.registry :as reg])   (:import    [io.netty.buffer ByteBuf Unpooled])) @@ -14,29 +15,22 @@  ;; TODO this also looks identical to the serverbound packet ids... (defmacro build-packet-ids [registry]-  (into {} (mapcat (fn [[state packet-ids]]-                     (into {} (map-indexed (fn [i pk] [{::state/state state ::pk/id pk} i]))-                           packet-ids)))+  (into {}+        (mapcat (fn [[state packet-ids]]+                  (into {} (map-indexed (fn [i pk] [{::state/state state ::pk/id pk} i]))+                        packet-ids)))         registry)) -;; May return nil-;; Should assert or something idk-;; TODO this function looks just like the one in serverbound packets (defn type->codec [t]   (if (vector? t)-    (let [[codec & args] t-          writer (write/composite-writers codec)]-      (when writer-        (apply writer args)))-    (write/writers t)))+    (apply write/writer t)+    (write/writer t)))  (defn build-packet [pk-name schema]-  (let [fn-name (symbol (str "encode-" (name pk-name)))-        ;; TODO types can be []-        [fields types] (pairs schema)-        ;; ?????+  (let [fn-name (symbol (str "encode-" (name pk-name) "-packet"))+        fields (map :field schema)+        types (map :type schema)         codecs (map type->codec types)-        _ (assert codecs)         name-codec-pairs (for [codec codecs]                            [(gensym) codec])         codec-names (map first name-codec-pairs)@@ -50,37 +44,36 @@              `(~codec ~buf (~field ~pk)))          ~buf)))) +;; TODO why does this not use the conform-registry? (defmacro build-packets [registry]   (reduce (fn [acc [packet-id schema]]             (assoc acc packet-id (build-packet packet-id schema)))-          {} (update-vals registry #(remove string? %))))+          {} (reg/conform-registry registry))) -;; TODO document why packet ids don't map to numeric ids directly-(def packet-ids-  ^{:spec (s/map-of (s/schema [::state/state ::pk/id]) ::pk/numeric-id)}+(def ^{:spec (s/map-of (s/schema [::state/state ::pk/id]) ::pk/numeric-id)}+  packet-ids+  "Map of [::state/state ::pk/id] to numeric-id.+   Packet ids (::pk/id) could map directly to numeric-ids and be stateless,+    but some packets have duplicates across connection states that you'd want to handle in the same way.+   If it turns out that I don't end up handling packets in this way, then packet ids could map directly to numeric ids."   (build-packet-ids    {::state/status     [::status-response      ::pong-response]})) -;; Can return nil (defn numeric-id [state id]   (packet-ids {::state/state state ::pk/id id})) -(def packets-  ^{:spec (s/map-of ::pk/id (s/fspec :args (s/cat #(instance? ByteBuf %) ::pk/packet)-                                     :ret #(instance? ByteBuf %)))}+(def ^{:spec (s/map-of ::pk/id (s/fspec :args (s/cat :buf #(instance? ByteBuf %) :pk ::pk/packet)+                                        :ret #(instance? ByteBuf %)))}+  packets   (build-packets-   {::status-response [:response "Response to ::server/status-request" :json]-    ::pong-response [:timestamp "The same timestamp as ::server/ping-request" :long]}))+   {::status-response [:response "Response to ::sp/status-request" :json]+    ::pong-response [:timestamp "The same timestamp as ::sp/ping-request" :long]})) -;; TODO can be nil (defn packet-encoder [pk]   (packets (::pk/id pk))) -(comment-  (packets ::status-response))- (defn encode-packet! [pk state]   {:pre [(packet? pk)]}   (let [id (assert! (numeric-id state (::pk/id pk)))

Show all 109 lines

src/net/bowmap/packets/registry.clj modified (view file)

@@ -4,7 +4,6 @@    [dev.grahp.error :refer [raise]]    [net.bowmap.packet :as pk])) -;; TODO could have :type actually check that it's a valid type! (s/def ::packet-registry   (s/map-of ::pk/id             (s/* (s/cat :field keyword?

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

@@ -1,7 +1,7 @@ (ns net.bowmap.packets.server   (:require    [clojure.alpha.spec :as s]-   [dev.grahp.error :refer [assert! raise]]+   [dev.grahp.error :refer [raise]]    [net.bowmap.client.state :as state]    [net.bowmap.codecs.read :as read]    [net.bowmap.packet :as pk]@@ -13,13 +13,12 @@ (defn type->codec   "Takes a type from net.bowmap.codecs.read and returns a form that will evaluate to a function to decode that type from a ByteBuffer"   [t]-  (assert! (if (vector? t)-             (let [[codec & args] t]-               (apply (read/composite-readers codec) args))-             (read/readers t))))+  (if (vector? t)+    (apply read/reader t)+    (read/reader t)))  (defn build-packet [packet-id schema]-  (let [fn-name (symbol (str "decode-" (name packet-id)))+  (let [fn-name (symbol (str "decode-" (name packet-id) "-packet"))         buf (gensym "buf__")          fields (map :field schema)@@ -39,9 +38,9 @@             (assoc acc packet-id (build-packet packet-id schema)))           {} (reg/conform-registry registry))) -(def packets-  ^{:spec (s/map-of ::pk/id (s/fspec :args (s/cat #(instance? % ByteBuf))-                                     :ret ::pk/packet))}+(def ^{:spec (s/map-of ::pk/id (s/fspec :args (s/cat :buf #(instance? % ByteBuf))+                                        :ret ::pk/packet))}+  packets   (build-packets     {::handshaking [:protocol :varint                     :address :string@@ -50,6 +49,7 @@      ::status-request []      ::ping-request [:timestamp :long]})) +;; TODO this is unreadable lol (defmacro build-packet-ids [registry]   (into {}         (mapcat (fn [[state packet-ids]]@@ -58,9 +58,8 @@                         packet-ids)))         registry)) -(def packet-ids-  ^{:spec (s/map-of (s/schema [::state/state ::pk/numeric-id])-                    ::pk/id)}+(def ^{:spec (s/map-of (s/schema [::state/state ::pk/numeric-id]) ::pk/id)}+  packet-ids   (build-packet-ids    {::state/handshaking     [::handshaking]@@ -68,17 +67,6 @@     [::status-request      ::ping-request]})) -(defn packet-id [state numeric-id]-  (packet-ids {::state/state state-               ::pk/numeric-id numeric-id}))--;; TODO unit test all these :p-(defn packet-  "Takes a packet state and numeric id an returns a packet function (in 'packets'),-   or nil if there is no such packet."-  [state numeric-id]-  (packets (packet-id state numeric-id)))- (defn decode-packet!   "Decodes and returns one packet from ByteBuf 'buf', which should contain at least one readable packet.    The initial length field of the packet should not be stripped from 'buf'.@@ -92,7 +80,8 @@   ;; None of those packets have been implemented yet, though.   (let [_unused-length (read/read-varint! buf)         numeric-id (read/read-varint! buf)-        dedec (packet state numeric-id)]+        packet-id (packet-ids {::state/state state ::pk/numeric-id numeric-id})+        dedec (packets packet-id)]     (if dedec       (dedec buf)       (raise "Failed to decode packet, no such packet with that id." {:id numeric-id :state state}))))