client.clj

Branches > main > e6445b6 > files / src / net / bowmap / packets / client.clj

(ns net.bowmap.packets.client
  "Clientbound packets"
  (:require
   [clojure.alpha.spec :as s]
   [dev.grahp.error :refer [assert!]]
   [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?]])
  (:import
   [io.netty.buffer ByteBuf Unpooled]))

;; A lot of work to do in this file lol

;; 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)))
        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)))

(defn build-packet [pk-name schema]
  (let [fn-name (symbol (str "encode-" (name pk-name)))
        ;; TODO types can be []
        [fields types] (pairs schema)
        ;; ?????
        codecs (map type->codec types)
        _ (assert codecs)
        name-codec-pairs (for [codec codecs]
                           [(gensym) codec])
        codec-names (map first name-codec-pairs)
        bindings (apply concat name-codec-pairs)
        pairs? (map vector fields codec-names)
        buf (gensym "buf__")
        pk (gensym "pk__")]
    `(let [~@bindings]
       (fn ~fn-name [~buf ~pk]
         ~@(for [[field codec] pairs?]
             `(~codec ~buf (~field ~pk)))
         ~buf))))

(defmacro build-packets [registry]
  (reduce (fn [acc [packet-id schema]]
            (assoc acc packet-id (build-packet packet-id schema)))
          {} (update-vals registry #(remove string? %))))

;; 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)}
  (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 %)))}
  (build-packets
   {::status-response [:response "Response to ::server/status-request" :json]
    ::pong-response [:timestamp "The same timestamp as ::server/ping-request" :long]}))

;; TODO can be nil
(defn packet-encoder [pk]
  (packets (::pk/id pk)))

(comment
  (packets ::status-response))

(defn encode-packet! [buf pk state]
  {:pre [(packet? pk)]}
  (let [id (assert! (numeric-id state (::pk/id pk)))
        encoder (assert! (packet-encoder pk))
        data-buf (-> (Unpooled/buffer)
                     (write-varint! id)
                     (encoder pk))]
    (write-varint! buf (ByteBuf/.writerIndex data-buf))
    (ByteBuf/.writeBytes buf data-buf)))

(comment
  (.array (encode-packet! (Unpooled/buffer)
                          {::pk/id ::pong-response
                           :timestamp 42}
                          ::state/status)))