169f063
Branches > client-handling-rewrite > 169f063 > files
Docstrings comments and move to bytebuf efficiency
Commit: 169f06356d14f5b66773410c090800c5d17d2ad6
Parent: e8d0abe
7 changed files:
src/dev/grahp/bytebuf.clj view file
@@ -1,16 +1,19 @@ (ns dev.grahp.bytebuf (:import- [io.netty.buffer ByteBuf Unpooled]+ [io.netty.buffer ByteBuf] [java.io Writer])) +(defn byte-buf-bytes [buf]+ (let [arr (byte-array (ByteBuf/.readableBytes buf))]+ (ByteBuf/.getBytes buf (ByteBuf/.readerIndex buf) arr)+ (vec arr)))+ (defn print-bb [buf] (into [] (comp (map #(bit-and % 0xFF)) (interpose ", "))- (subvec (vec (.array buf))- (.readerIndex buf)- (.writerIndex buf))))+ (byte-buf-bytes buf))) (defmethod print-method ByteBuf [bb w] (Writer/.write w (str "#" (Class/.getSimpleName (type bb)) " "))src/dev/grahp/error.clj view file
@@ -20,6 +20,14 @@ `(throw (~msg ~m ~cause)) `(throw (ex-info ~msg ~m ~cause))))) +(defmacro adorn+ "Catches any throwables 'body' throws and throws an exception caused by the+ throwable with an additional message and data map."+ [body msg m]+ `(try ~body+ (catch Throwable t#+ (raise ~msg ~m t#))))+ (defmacro assert! "Throws an exception with an optional map and message when 'x' is falsey and *assert* is truthy, otherwise, returns 'x'"src/net/bowmap/packet/client.clj view file
@@ -3,12 +3,12 @@ (:require [clojure.alpha.spec :as s] [dev.grahp.build :refer [build]]- [dev.grahp.error :refer [assert! raise]]+ [dev.grahp.error :refer [adorn assert!]] [net.bowmap.client.state :as state] [net.bowmap.packet :as pk] [net.bowmap.packet.write :as write]) (:import- [io.netty.buffer ByteBuf Unpooled]))+ [io.netty.buffer ByteBuf CompositeByteBuf Unpooled])) (defn build-packet-ids [registry] (build [[state packet-ids] registry@@ -248,29 +248,33 @@ ::game-event [:event [:enum :byte {3 :set-gamemode 13 :wait-for-chunks}] :value [:or :float 0]]})) -(defn numeric-id [state id]- (packet-ids {::state/state state ::pk/id id}))+(defn- numeric-id [pk state]+ (assert! (packet-ids {::state/state state ::pk/id (::pk/id pk)})+ {:pk pk :state state}+ "No such packet with that id.")) -(defn packet-encoder [pk]- (packets (::pk/id pk)))+(defn- packet-encoder [pk]+ (assert! (packets (::pk/id pk))+ {:pk pk}+ "No such encoder for that packet.")) -;; Maybe this should just be a function of the packet :p-(defn- encode-packet*+(defn- length-prefixed+ "Returns a composite bytebuf prefixed by the varint length of 'buf'."+ [buf]+ (-> (Unpooled/compositeBuffer 2)+ (CompositeByteBuf/.addComponent true (write/write-varint! (Unpooled/buffer 5) (ByteBuf/.writerIndex buf)))+ (CompositeByteBuf/.addComponent true buf)))++(defn- encoded-packet+ "Returns an encoded packet." [pk state]- {:pre [(pk/packet? pk)]}- (let [id (assert! (numeric-id state (::pk/id pk)) {:pk pk :state state} "No such packet with that id.")- encoder (assert! (packet-encoder pk) {:pk pk :state state :id id} "No such encoder for that packet!")- data-buf (-> (Unpooled/buffer)- (write/write-varint! id)- (encoder pk))]- ;; This buffer only contains the data + a varint, which can never be larger than 5 bytes- ;; TODO apparently there's a 'CompositeByteBuf' for this very purpose :p- (-> (Unpooled/buffer (+ (ByteBuf/.writerIndex data-buf) 5))- (write/write-varint! (ByteBuf/.writerIndex data-buf))- (ByteBuf/.writeBytes data-buf))))+ (let [encode-body (packet-encoder pk)]+ (length-prefixed (-> (Unpooled/buffer)+ (write/write-varint! (numeric-id pk state))+ (encode-body pk))))) (defn encode-packet- "Encodes packet 'pk' into a (new) ByteBuf of the packet bytes.+ "Encodes packet 'pk' into a ByteBuf of the packet bytes. Like decoding, encoding a packet also requires the connection state, as packet ids are not unique across connection state, so there isn't a direct mapping of packet ids to numeric ids.@@ -278,6 +282,5 @@ same across connection states, and so can be sent without needing to switch on the connection state." [pk state]- (try (encode-packet* pk state)- (catch Throwable e- (raise "Error while encoding packet:" {:pk pk :state state} e))))+ (adorn (encoded-packet pk state)+ "Error while encoding packet" {:pk pk :state state}))src/net/bowmap/packet/read.clj view file
@@ -1,4 +1,7 @@ (ns net.bowmap.packet.read+ "Functions for reading data from serialized packet ByteBufs.+ When given bad data, read functions will throw.+ See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types" (:require [clojure.alpha.spec :as s] [dev.grahp.error :refer [ensure-not! raise]]@@ -55,7 +58,9 @@ acc)) {} codec-fields)) -(defn- mapize-vector-or-something [x]+(defn- mapize-vector-or-something+ "Returns a map of keys to vals, where vectors will map their indecies to their values."+ [x] {:pre [(or (map? x) (vector? x))]} (if (vector? x) (zipmap (range) x)@@ -82,6 +87,8 @@ (raise (str "No such type! " (name t)) {:type t})))) (def readers+ "Map of type names to read functions.+ See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types" {:byte ByteBuf/.readByte :ubyte (comp unchecked-byte ByteBuf/.readByte) :short ByteBuf/.readShortsrc/net/bowmap/packet/server.clj view file
@@ -142,16 +142,14 @@ ::player-loaded [] ::player-input [:flags [:bit-mask :ubyte [:forward :backward :left :right :jump :sneak :sprint]]]})) -#_"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'.- After calling, the buf will contain one less packet.- Can throw if:- 1. No such packet with the packet id and state exists- 2. 'buf' is not properly framed, and does not contain a full packet- 3. The contents of 'buf' are not a valid packet"- (defn decode-packet- ;; Pre-framed+ "Decodes/deserializes one packet (::net.bowmap.packet) from framed ByteBuf 'buf'.+ The initial length field of the packet should be stripped from the buffer.+ Can throw if:+ 1. No such packet with the numeric id and state exists+ 2. No schema exists for that packet id+ 3. 'buf' is not properly framed+ 4. The contents of 'buf' are not a valid packet" [buf state] (let [numeric-id (read/read-varint! buf) packet-id (packet-ids {::state/state state ::pk/numeric-id numeric-id})src/net/bowmap/packet/types.clj view file
@@ -2,6 +2,8 @@ (:require [clojure.alpha.spec :as s])) +; Tables used by packet schemas+; See read-table! and write-table! (s/def ::table (s/* (s/cat :field keyword? :doc (s/? string?)src/net/bowmap/packet/write.clj view file
@@ -1,11 +1,12 @@ (ns net.bowmap.packet.write "Functions to write Minecraft protocol types.+ When given bad data, write functions will throw. See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types" (:require [clojure.alpha.spec :as s] [clojure.data.json :as json] [clojure.set :as set]- [dev.grahp.error :refer [raise]]+ [dev.grahp.error :refer [adorn raise]] [net.bowmap.packet.types :as t]) (:import [io.netty.buffer ByteBuf]@@ -43,13 +44,11 @@ (ByteBuf/.writeLong (UUID/.getMostSignificantBits uuid)) (ByteBuf/.writeLong (UUID/.getLeastSignificantBits uuid)))) -;; TODO this is where optionality and nil checking should be, and the packet stuff should use this.+; This is where you could check for optionality I think. If the fields aren't [:optional ...] (defn write-table! [buf codec-fields m] (reduce (fn [acc {:keys [field codec]}]- (try- (codec acc (get m field))- (catch Throwable e- (raise "Error while encoding packet field" {:field field :value (get m field)} e))))+ (adorn (codec acc (get m field))+ "Error while encoding packet field" {:field field :value (get m field)})) buf codec-fields)) (defn write-boolean! [buf x]@@ -184,7 +183,7 @@ (raise (str "No such type! " (name t)) {:type t})))) (def writers- "Map of type names to functions.+ "Map of type names to write functions. See https://minecraft.wiki/w/Java_Edition_protocol/Packets#Data_types" {:byte ByteBuf/.writeByte :ubyte ByteBuf/.writeByte