aboutsummaryrefslogtreecommitdiff
path: root/src/hub.moon
blob: 48fcec31bf0052eb8a21951cc51bc84b037fd226 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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}