aboutsummaryrefslogtreecommitdiff
path: root/src/hub.moon
diff options
context:
space:
mode:
Diffstat (limited to 'src/hub.moon')
-rw-r--r--src/hub.moon184
1 files changed, 184 insertions, 0 deletions
diff --git a/src/hub.moon b/src/hub.moon
new file mode 100644
index 0000000..48fcec3
--- /dev/null
+++ b/src/hub.moon
@@ -0,0 +1,184 @@
+-- Hub-and-spoke networking hub
+-- Manages client connections and routes messages between them
+
+net = require "net"
+log = require "log"
+world = require "world"
+
+-- Register message types that clients sends to the hub
+-- Client -> Hub: RequestEntities has no payload (empty table is fine)
+net.register_message("RequestEntities", {})
+
+-- Envelope used for all logical messages routed through the hub
+net.register_message("routed_message", {
+ required: {
+ message_type: "string"
+ from: "string"
+ }
+ optional: {
+ data: "table"
+ }
+})
+
+net.register_message("Join", {
+ required: {
+ name: "string"
+ }
+})
+
+-- Hub -> Client: RespondEntities sends the current entities list (array)
+net.register_message("RespondEntities", {
+ optional: {
+ entities: "table"
+ }
+})
+
+-- Hub -> Client: CreateEntity announces a new entity for a given peerid
+net.register_message("CreateEntity", {
+ required: {
+ peerid: "string"
+ }
+})
+
+class Hub
+ new: =>
+ @peer = nil
+ @clients = {} -- client_id -> connection
+ @client_names = {} -- client_id -> name (optional)
+ @initialized = false
+ @routes = {}
+ @on_connect_callbacks = {}
+ @on_disconnect_callbacks = {}
+
+ initialize: =>
+ if @initialized
+ return
+ --@attempts = 0
+ --create_peer = () ->
+ --@peer = net.Peer!
+ --fail_peer_creation = () ->
+ --@attempts += 1
+ --while @attempts < 4
+ --xpcall(create_peer, fail_peer_creation)
+ --log.info("Creating peer attempt " .. @attempts .. " / 4", {"net","server"})
+ --if @peer == nil
+ --error("Failed to create peer 4 times...")
+ @peer = net.Peer!
+ log.info("Hub peer created: #{@peer.id}", {"server", "net"})
+ -- Listen for incoming connections
+ @peer\on "connection", (peer, message) ->
+ client_id = message.data.dest -- the other side of the connection?
+ log.info("Client connected: " .. tostring(peer) .. ":" .. tostring(message), {"server", "net"})
+ conn = message.data
+ @clients[client_id] = conn
+ -- Set up connection handlers
+ -- When the connection receives data, the callback is invoked with
+ -- (connection, message_array), where message_array = { msgname, msgdata }.
+ -- We only care about the logical client id and the logical message array,
+ -- so forward just those to handle_message. This keeps the JS bridge
+ -- signature Hub.handle_message(from_client, msg) where msg is the
+ -- array { msg_type, msg_data }.
+ conn\on "data", (conn, message) ->
+ log.info("On data got arguments:" .. tostring(conn) .. "," .. tostring(message), {"server","net"})
+ @handle_message(client_id, message)
+
+ conn\on "close", () ->
+ @handle_disconnect(client_id)
+
+ -- Notify connection callbacks
+ for callback in *@on_connect_callbacks
+ callback(client_id)
+
+ @initialized = true
+ require("server.init")
+
+ handle_message: (from_client, message) =>
+ -- message is the array {msg_type, msg_data} that was sent over the wire
+ log.info("Hub received message: #{message} from #{from_client}", {"net", "server"})
+ msg_type = message[1]
+ msg_data = message[2]
+
+ -- Build routed_message-style envelope for validation
+ routed = {
+ message_type: msg_type
+ from: from_client
+ data: msg_data
+ }
+
+ -- Validate envelope
+ ok, err = pcall(net.validate, "routed_message", routed)
+ if not ok
+ log.error("Invalid routed_message from " .. tostring(from_client) .. ": " .. tostring(routed), {"net", "server"})
+ return
+
+ -- Validate payload for the specific message type
+ ok, err = pcall(net.validate, msg_type, msg_data)
+ if not ok
+ log.error("Invalid " .. tostring(msg_type) .. " payload from " .. tostring(from_client) .. ": " .. tostring(err), {"net", "server"})
+ return
+
+ if msg_type == "Join"
+ log.info("Hub handling Join from " .. tostring(from_client) .. " with data=" .. tostring(msg_data), {"net", "server", "debug"})
+ world.domain = "server"
+ if @routes[msg_type]
+ -- routes[msg_type] is a flat map id -> callback(from_client, data)
+ for _, callback in pairs(@routes[msg_type])
+ callback(from_client, msg_data)
+ else
+ log.warn("No routes for " .. tostring(msg_type), {"net","server"})
+ world.domain = "client"
+
+ listen: (message_type, id, callback) =>
+ @routes[message_type] = @routes[message_type] or {}
+ id = id or "default"
+ @routes[message_type][id] = callback
+ log.info("Hub listening for #{message_type}", {"net", "server"})
+ id
+
+ defen: (message_type, id) =>
+ @routes[message_type][id] = nil
+
+ send: (client_id, message_type, data) =>
+ conn = @clients[client_id]
+ if not conn
+ log.warn("Cannot send to unknown client: #{client_id}", {"server", "net"})
+ return
+ log.info("Hub sending #{message_type} to #{client_id}", {"net", "server"})
+ conn\send({message_type, data})
+
+ broadcast: (message_type, data) =>
+ nclients = 0
+ for _, _ in pairs(@clients)
+ nclients += 1
+ log.info("Hub broadcasting #{message_type} to " .. tostring(nclients) .. " clients", {"net", "server"})
+ for clientid, conn in pairs(@clients)
+ conn\send({message_type, data})
+
+ handle_disconnect: (client_id) =>
+ log.info("Client disconnected: #{client_id}", {"server", "net"})
+ @clients[client_id] = nil
+ @client_names[client_id] = nil
+
+ -- Notify disconnect callbacks
+ for callback in *@on_disconnect_callbacks
+ callback(client_id)
+
+ on_connect: (callback) =>
+ table.insert(@on_connect_callbacks, callback)
+
+ on_disconnect: (callback) =>
+ table.insert(@on_disconnect_callbacks, callback)
+
+ get_clients: =>
+ clients = {}
+ for id, conn in pairs @clients
+ table.insert(clients, {
+ id: id
+ name: @client_names[id]
+ })
+ clients
+
+ pump: =>
+ net.pump!
+
+{:Hub}