write.clj
Branches > namespace-reorganization > 92885b7 > files / src / net / bowmap / codecs / write.clj
(ns net.bowmap.codecs.write
(:require
[clojure.alpha.spec :as s]
[clojure.data.json :as json]
[dev.grahp.error :refer [raise]]
[net.bowmap.codecs.types :as types]
[net.bowmap.packets.registry :as reg])
(:import
[io.netty.buffer ByteBuf Unpooled]
[java.nio.charset Charset]
[java.util UUID]))
(defn write-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 write-string! [buf s]
(doto buf
(write-varint! (count s))
(ByteBuf/.writeCharSequence s (Charset/forName "UTF-8"))))
(defn write-json! [buf s]
(write-string! buf (json/write-str s)))
(defn write-list! [buf t xs]
(doseq [x xs]
(t buf x)))
(defn write-uuid! [buf uuid]
(ByteBuf/.writeLong buf (UUID/.getMostSignificantBits uuid))
(ByteBuf/.writeLong buf (UUID/.getLeastSignificantBits uuid)))
;; TODO this is where optionality and nil checking should be, and the packet stuff should use this.
(defn write-table! [buf t? m]
(doseq [{:keys [field type]} t?]
(type buf (get m field))))
(declare 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]
(if (vector? t)
(let [[t & args] t]
(apply (writer t) args))
(or (writers t)
(raise (str "No such type! " (name t)) {:type t}))))
(defn write-boolean! [buf x]
(if x
(ByteBuf/.writeByte buf 1)
(ByteBuf/.writeByte buf 0)))
(defn write-context-optional! [buf codec x]
(if x
(codec buf x)
buf))
(def writers
{:byte `ByteBuf/.writeByte
:ubyte `ByteBuf/.writeByte
:short `ByteBuf/.writeShort
:ushort `ByteBuf/.writeShort
:int `ByteBuf/.writeInt
:long `ByteBuf/.writeLong
:varint `write-varint!
:string `write-string!
:json `write-json!
:uuid `write-uuid!
;; TODO These are kinda named backwards...
:context-optional (fn [t]
`(fn ~'write-context-optional [buf# x#]
(write-context-optional! buf# ~(writer t) x#)))
:optional (fn [t]
`(fn ~'write-optional [buf# x#]
(write-boolean! buf# x#)
(write-context-optional! buf# ~(writer t) x#)))
:table (fn [t]
;; TODO needs to be moved yeah.
(let [t (->> (s/conform ::reg/table t)
(mapv #(update % :type writer)))]
`(fn ~'write-table [buf# m#] (write-table! buf# ~t m#))))
:list (fn [t]
`(fn ~'write-list [buf# xs#] (write-list! buf# ~(writer t) xs#)))})