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}
|