55d36f2
Branches > main > 55d36f2 > files
Add and improve readers and writers
Commit: 55d36f2e706038f6b2754a6bd63f79bb6763f255
Parent: eb5d4dd
2 changed files:
src/net/bowmap/server/protocol/readers.clj view file
@@ -9,12 +9,49 @@ [net.bowmap.server.protocol.types :refer [max-varint-size]]) (:import [io.netty.buffer ByteBuf]- [java.nio.charset Charset]))+ [java.nio.charset Charset]+ [java.util UUID])) (s/def ::reader (s/fspec :args (s/cat :frame ::frame) :ret any?)) +(defn- read-field+ "Reads a field into map 'm' from 'buf' via 'reader'.+ Throws a useful error message when that reading fails."+ [m field reader buf]+ (try+ (assoc-some m field (reader buf))+ (catch Throwable t+ (throw (ex-info "Error while reading table field" {:field field+ :reader reader}+ t)))))++(defn table<+ "Takes a sequence of alternating keys and readers, e.g.+ [:key reader< :key2 reader<].++ Returns a reader that builds a map where, for each key/reader pair,+ the read value from the buffer and is mapped with the corresponding key.+ Keys are omitted when the value is nil."+ [table]+ (let [t (partition 2 table)]+ (fn [buf]+ (reduce (fn [acc [field reader]]+ (read-field acc field reader buf))+ {} t))))++(defn ->reader+ "Attempts to coerces 'x' into a reader. Throws if it cannot.+ Vectors with pairs of [:key reader] are turned into tables readers.+ Nil returns an identity reader."+ [x]+ (cond+ (vector? x) (table< x)+ (nil? x) identity+ (fn? x) x+ :else (throw (IllegalArgumentException. (str "Cannot coerce into reader: " (pr-str x))))))+ (def byte< ByteBuf/.readByte) (def ubyte< ByteBuf/.readUnsignedByte) (def short< ByteBuf/.readShort)@@ -35,29 +72,16 @@ result' (recur (inc num-read) result')))))) -(defn string< [buf]+(defn string<+ [buf] (let [len (varint< buf)] (if (neg? len) (throw (Exception. "String length cannot be negative")) (ByteBuf/.readCharSequence buf len (Charset/forName "UTF-8"))))) +(defn uuid< [buf]+ (UUID. (long< buf) (long< buf)))+ (defn enum< [reader m] (fn [buf] (m (reader buf))))--(defn table< [table]- (let [t (partition 2 table)]- (fn [buf]- (reduce (fn [acc [field reader]]- (assoc-some acc field (reader buf)))- {} t))))--(defn ->reader- "Attempts to coerces 'x' into a reader. Throws if it cannot.- Vectors with pairs of [:key reader] are turned into tables readers.- Nil is coerced to the empty table reader."- [x]- (cond- (or (vector? x) (nil? x)) (table< x)- (fn? x) x- :else (throw (IllegalArgumentException. (str "Cannot coerce into reader: " (pr-str x))))))src/net/bowmap/server/protocol/writers.clj view file
@@ -8,55 +8,37 @@ [net.bowmap.server.protocol.schema :as p]) (:import [io.netty.buffer ByteBuf]- [java.nio.charset Charset]))+ [java.nio.charset StandardCharsets]+ [java.util UUID])) (s/def ::writer (s/fspec :args (s/cat :frame ::frame :value any?) :fn #(= (-> % :args :frame) (:ret %)))) -(def byte> ByteBuf/.writeByte)-(def ubyte> ByteBuf/.writeByte)-(def short> ByteBuf/.writeShort)-(def ushort> ByteBuf/.writeShort)-(def int> ByteBuf/.writeInt)-(def long> ByteBuf/.writeLong)-(def float> ByteBuf/.writeFloat)-(def double> ByteBuf/.writeDouble)+(defn- write-field+ "Writes a field from map 'm' into 'buf' via 'writer'.+ Throws a useful error message when that writing fails."+ [writer buf m field]+ (try+ (writer buf (get m field))+ (catch Throwable t+ (throw (ex-info "Error while writing table field" {:field field+ field (get m field)+ :table m}+ t))))) -(defn varint> [buf n]- (loop [buf buf- n n]- (let [b (bit-and n 0x7F)- nxt (unsigned-bit-shift-right n 7)]- (if (zero? nxt)- (ByteBuf/.writeByte buf b)- (recur (ByteBuf/.writeByte buf (bit-or b 0x80))- nxt)))))+(defn table>+ "Takes a sequence of alternating keys and writers, e.g.+ [:key writer> :key2 writer>]. -(defn string> [buf s]- {:pre [(string? s)]}- (doto buf- (varint> (count s))- (ByteBuf/.writeCharSequence s (Charset/forName "UTF-8"))))--(defn json> [buf s]- (string> buf (json/write-str s)))--(defn status-response> [buf {::p/keys! [version-name protocol]- ::p/keys [motd max-player-count online-player-count]}]- (json> buf- (cond-> {:version {:name version-name- :protocol protocol}}- motd (assoc :description motd)- (and max-player-count online-player-count) (assoc :players {:max max-player-count- :online online-player-count}))))--(defn table> [table]+ Returns a writer that takes a map and, for each key/writer pair,+ writes the value associated (or nil) with the key using the corresponding writer."+ [table] (let [t (partition 2 table)]- (fn [buf m]+ (fn table-writer [buf m] (reduce (fn [acc [field writer]]- (writer acc (get m field)))+ (write-field writer acc m field)) buf t)))) (defn ->writer@@ -68,3 +50,109 @@ (vector? x) (table> x) (fn? x) x :else (throw (IllegalArgumentException. (str "Cannot coerce into writer: " (pr-str x))))))++(def byte> ByteBuf/.writeByte)+(def ubyte> ByteBuf/.writeByte)+(def short> ByteBuf/.writeShort)+(def ushort> ByteBuf/.writeShort)+(def int> ByteBuf/.writeInt)+(def long> ByteBuf/.writeLong)+(def float> ByteBuf/.writeFloat)+(def double> ByteBuf/.writeDouble)++(defn boolean>+ "Writes the byte 1 if 'x' is truthy, else the byte 0."+ [buf x]+ (if x+ (byte> buf 1)+ (byte> buf 0)))++(defn varint> [buf n]+ (loop [buf buf