7014976
Branches > refactor/use-spec-1 > 7014976 > files
Updates
Commit: 7014976933fe9b1f558dc202b97f0be1f4582ace
Parent: 0880c86
9 changed files:
Notes.md view file
@@ -0,0 +1,3 @@+Not tickets yet :p++- clean up core main logic until I'm happy with itsrc/dev/grahp/error.clj view file
@@ -40,6 +40,8 @@ ~m)) x))) +#_(defmacro when! [])+ ;; TODO these have the arguments in a different order than assert! ;; And keywords are assumed to be specs even if you want to ensure a key is present in a map... (defmacro ensure!src/net/bowmap/core.clj view file
@@ -9,40 +9,51 @@ [net.bowmap.network.message.server :as server] [net.bowmap.server :as server-process])) +;; TODO server should just provide messages in/out++(defn read-and-handle-packet+ "Reads/deserializes one packet into a message, and handles it.+ Because packet reading is dependent on the state of the client, and handling a packet can update the state of the client,+ Reading and handling must be synchronized!"+ [pk client]+ (when-some [msg (server/packet->message pk (::state/connection-state client))]+ (handle/handle-packet!! client msg)))++ ; TODO bad positional arguments, use a map? ; TODO only send in things to the server, the rest is stateful-(defn handle-packets-and-messages [client packets-in messages-in messages-out packets-out]+(defn handle-packets-and-messages [client packets-in messages-out packets-out] (alt!! messages-out ([msg]- ; Make one of these keepalive- ; How does keepalive being though?- (assert (>!! packets-out (client/message->packet msg (::state/state client))))- client)+ (when msg+ ; Make one of these keepalive+ ; How does keepalive begin though?+ (assert (>!! packets-out (client/message->packet msg (::state/connection-state client))))+ client)) ; These could be agents around clients with vectors as well instead of immutable maps holding channels ; Put this onto messages-in after translating? ; Check messages-out and send to uhh yeah ;; TODO do you need to send messages out before reading them?- packets-in ([buf]- (when buf- (when-some [msg (server/packet->message buf (::state/state client))]- (handle/handle-packet!! client msg))))))+ packets-in ([pk]+ (when pk+ (read-and-handle-packet pk client))))) ; Do I need to close out?+; So clients should be managed by the server and they're not really data are they (defn client-handler [packets-in packets-out]- (let [messages-out (chan)- messages-in (chan)- client {::state/state ::state/handshaking- ::c/messages-out messages-out}]+ (let [messages-out (chan) ; Use like 1024 so it can block but only when bad things are happening bad things are afoot+ client-state {::state/connection-state ::state/handshaking+ ; TODO this is used but get rid of it/make it generic and pass it around not a key of player+ ::c/messages-out messages-out}] ; Client is not data- (try (loop [client client]- (when-some [client (handle-packets-and-messages client packets-in messages-in messages-out packets-out)]- (recur client)))- (catch Throwable e- ; Disconnect/cleanup?- (println "In client handler: " e)))))+ (loop [client client-state]+ ; TODO this can just... stop the loop+ (when-some [client (handle-packets-and-messages client packets-in messages-out packets-out)]+ (recur client))))) (defn start-server [] (server-process/start-server! {:handler #'client-handler+ :logger-fn prn :port 25565})) (defn -main [& _args]src/net/bowmap/network/message/client.clj view file
@@ -21,7 +21,7 @@ (defn build-packet-ids [registry] (build [[state packet-ids] registry [i id] (map-indexed vector packet-ids)]- [{::state/state state+ [{::state/connection-state state ::msg/id id} i])) @@ -30,7 +30,7 @@ (assoc acc packet-id (table-> table))) {} registry)) -(def ^{:spec (s/map-of (s/schema [::state/state ::msg/id]) ::pk/id)}+(def ^{:spec (s/map-of (s/schema [::state/connection-state ::msg/id]) ::pk/id)} packet-ids ; TODO docstrings shouldn't contain these yeag... ; Update@@ -260,7 +260,7 @@ :value (or-> float-> 0)]})) (defn- numeric-id [pk state]- (assert! (packet-ids {::state/state state ::msg/id (::msg/id pk)})+ (assert! (packet-ids {::state/connection-state state ::msg/id (::msg/id pk)}) {:pk pk :state state} "No such packet with that id.")) @@ -285,8 +285,6 @@ (varint-> (numeric-id pk state)) (encode-body pk))))) -; TODO also update, see sp/decode-packet-; message->packet (defn message->packet "Encodes packet 'pk' into a ByteBuf of the packet bytes. Like decoding, encoding a packet also requires the connection state, assrc/net/bowmap/network/message/server.clj view file
@@ -27,12 +27,12 @@ (defn build-message-id-map [registry] (build [[state packet-ids] registry [i id] (map-indexed vector packet-ids)]- [{::state/state state+ [{::state/connection-state state ::pk/id i} id])) ; TODO document why ids are enumerated like this?-(def ^{:spec (s/map-of (s/schema [::state/state ::pk/id]) ::msg/id)}+(def ^{:spec (s/map-of (s/schema [::state/connection-state ::pk/id]) ::msg/id)} packet-ids (build-message-id-map {::state/handshaking@@ -162,8 +162,8 @@ :pitch ->float :flags ->byte]})) -; TODO is this pure/no external effects besides the buf? (defn packet->message+ ; TODO more references in docstrings "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:@@ -173,7 +173,7 @@ 4. The contents of 'buf' are not a valid packet" [packet-buf state] (-> (let [numeric-id (->varint packet-buf)- packet-id (packet-ids {::state/state state ::pk/id numeric-id})+ packet-id (packet-ids {::state/connection-state state ::pk/id numeric-id}) reader (packets packet-id)] (if reader (adorn (reader packet-buf)src/net/bowmap/network/registry.clj view file
@@ -88,7 +88,7 @@ [id]) ;; TODO this shouldn't be here :p-(def registry-packets+(def registry-messages (for [[id m] (partition 2 registries)] {::msg/id ::client/registry-data :registry-id idsrc/net/bowmap/network/state.clj view file
@@ -2,6 +2,7 @@ (:require [dev.grahp.spec :as S])) -(S/def ::state+; TODO rename to connection state I think+(S/def ::connection-state "Network connection state, see https://minecraft.wiki/w/Java_Edition_protocol/Packets" #{::handshaking ::status ::login ::config ::play})src/net/bowmap/packet_handling.clj view file
@@ -15,6 +15,7 @@ ; And it's stateful! ; Performs a side effect and returns the new client?+; TODO nil? (defmulti handle-packet (fn [msg client] (::msg/id msg)))@@ -22,6 +23,7 @@ ; Returns the client and performs side effects... ; Should be run single-threaded? ; Hmm...+; TODO nil? (defn handle-packet!! [client msg] (handle-packet msg client)) @@ -38,7 +40,7 @@ client) (defmethod handle-packet ::server/handshaking [msg client]- (assoc client ::state/state (:intent msg)))+ (assoc client ::state/connection-state (:intent msg))) (defmethod handle-packet ::server/status-request [_ client] (send-message! client@@ -74,10 +76,10 @@ :known-packs [{:namespace "minecraft" :id "core" :version "1.12.11"}]})- (assoc client ::state/state ::state/config))+ (assoc client ::state/connection-state ::state/config)) (defmethod handle-packet ::server/known-packs [_ client]- (doseq [msg registry/registry-packets]+ (doseq [msg registry/registry-messages] (send-message! client msg)) (send-message! client {::msg/id ::client/finish-config})@@ -120,4 +122,4 @@ :id (long (* (rand) Long/MAX_VALUE))}) (recur))) - (assoc client ::state/state ::state/play))+ (assoc client ::state/connection-state ::state/play))src/net/bowmap/server.clj view file
@@ -1,8 +1,11 @@ (ns net.bowmap.server (:require [aleph.tcp :as tcp]+ [clojure.alpha.spec :as spec] [clojure.core.async :refer [chan io-thread]]+ [dev.grahp.spec :as S] [manifold.stream :as s]+ [net.bowmap.network.packet :as pk] [net.bowmap.network.packet.read :as read :refer [->varint]]) (:import [io.netty.bootstrap ServerBootstrap]@@ -27,18 +30,27 @@ (recur res')) res)))))) -(defn- handle-client [handler stream info]- (let [in (chan 1 packet-framer-xf)- out (chan)]- (s/connect stream in)- (s/connect out stream)- (io-thread (handler in out))))+(defn logging-handler [handler logger-fn]+ (fn [in out]+ (try+ (handler in out)+ (catch Throwable t+ (logger-fn t)))))++(defn- handle-client [handler logger-fn stream info]+ (let [packets-in (chan 1 packet-framer-xf)+ packets-out (chan)+ handler (logging-handler handler logger-fn)]+ (s/connect stream packets-in)+ (s/connect packets-out stream)+ (io-thread (handler packets-in packets-out)))) ; Could return a channel of clients?+; Make sure this is just kept an interface, it really only needs to know about packets-in, packets-out, and then maybe how to close/disconnect? (defn start-server! "Starts a server at the port, calling 'handler' with an 'in' and 'out' channel of packets."- [& {:keys [handler port]}]+ [& {:keys [handler port logger-fn]}] (tcp/start-server (fn [stream info]- (handle-client handler stream info))+ (handle-client handler logger-fn stream info)) {:port port :bootstrap-transform #(ServerBootstrap/.childOption % ChannelOption/TCP_NODELAY true)}))