write.clj

Branches > main > e20a0a8 > 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#)))})