forked from trevex/golem
/
room_manager.go
279 lines (254 loc) · 8.44 KB
/
room_manager.go
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/*
Copyright 2013 Niklas Voss
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package golem
const (
roomManagerCreateEvent = "create"
roomManagerRemoveEvent = "remove"
CloseConnectionOnLastRoomLeft = 1
)
// Room request information holding name of the room and the connection, which requested.
type roomReq struct {
// Name of the lobby the request goes to.
name string
// Reference to the connection, which requested.
conn *Connection
}
// Room messages contain information about to which room it is being send and the data being send.
type roomMsg struct {
// Name of the room the message goes to.
to string
// Data being send to specified room.
msg *message
}
// Wrapper for normal lobbies to add a member counter.
type managedRoom struct {
// Reference to room.
room *Room
// Member-count to allow removing of empty lobbies.
count uint
}
// Structure containing all necessary informations and options of
// connection for the room manager instance
type connectionInfo struct {
rooms map[string]bool
options uint32
}
type connectionInfoReq struct {
conn *Connection
options uint32
overwrite bool
}
// Constructor for connection info struct
func newConnectionInfo() *connectionInfo {
return &connectionInfo{
rooms: make(map[string]bool),
options: 0,
}
}
// Handles any count of lobbies by keys. Currently only strings are supported as keys (room names).
// As soon as generics are supported any key should be able to be used. The methods are used similar to
// single rooms but preceded by the key.
type RoomManager struct {
// Map of connections mapped to lobbies joined; necessary for leave all/clean up functionality.
members map[*Connection]*connectionInfo
// Map of all managed lobbies with their names as keys.
rooms map[string]*managedRoom
// Channel of join requests.
join chan *roomReq
// Channel of leave requests.
leave chan *roomReq
// Channel of leave all requests, essentially cleaning up every trace of the specified connection.
leaveAll chan *Connection
// Channel of room destruction requests
destroy chan string
// Channel of connection option requests
options chan *connectionInfoReq
// Channel of messages associated with this room manager
send chan *roomMsg
// Stop signal channel
stop chan bool
// Room creation and removal callbacks
callbackRoomCreation func(string)
callbackRoomRemoval func(string)
}
// NewRoomManager initialises a new instance and returns the a pointer to it.
func NewRoomManager() *RoomManager {
// Create instance.
rm := RoomManager{
members: make(map[*Connection]*connectionInfo),
rooms: make(map[string]*managedRoom),
join: make(chan *roomReq),
leave: make(chan *roomReq),
leaveAll: make(chan *Connection),
destroy: make(chan string),
options: make(chan *connectionInfoReq),
send: make(chan *roomMsg, roomSendChannelSize),
stop: make(chan bool),
callbackRoomCreation: func(string) {},
callbackRoomRemoval: func(string) {},
}
// Start message loop in new routine.
go rm.run()
// Return reference to this room manager.
return &rm
}
// Helper function to leave a room by name. If specified room has
// no members after leaving, it will be cleaned up.
func (rm *RoomManager) leaveRoomByName(name string, conn *Connection) {
if m, ok := rm.rooms[name]; ok { // Continue if getting the room was ok.
if c, ok := rm.members[conn]; ok { // Continue if connection has map of joined lobbies.
if _, ok := c.rooms[name]; ok { // Continue if connection actually joined specified room.
m.room.leave <- conn
m.count--
delete(c.rooms, name)
if len(c.rooms) == 0 && (c.options&CloseConnectionOnLastRoomLeft) == CloseConnectionOnLastRoomLeft {
delete(rm.members, conn)
conn.Close()
}
if m.count == 0 { // Get rid of room if it is empty
m.room.Stop()
delete(rm.rooms, name)
go rm.callbackRoomRemoval(name)
}
}
}
}
}
// Run should always be executed in a new goroutine, because it contains the
// message loop.
func (rm *RoomManager) run() {
for {
select {
// Join
case req := <-rm.join:
m, ok := rm.rooms[req.name]
if !ok { // If room was not found for join request, create it!
m = &managedRoom{
room: NewRoom(),
count: 1, // start with count 1 for first user
}
rm.rooms[req.name] = m
go rm.callbackRoomCreation(req.name)
} else { // If room exists increase count and join.
m.count++
}
m.room.join <- req.conn
c, ok := rm.members[req.conn]
if !ok { // If room association map for connection does not exist, create it!
c = newConnectionInfo()
rm.members[req.conn] = c
}
c.rooms[req.name] = true // Flag this room on members room map.
// Leave
case req := <-rm.leave:
rm.leaveRoomByName(req.name, req.conn)
// Leave all
case conn := <-rm.leaveAll:
if c, ok := rm.members[conn]; ok {
for name := range c.rooms { // Iterate over all lobbies this connection joined and leave them.
rm.leaveRoomByName(name, conn)
}
delete(rm.members, conn) // Remove map of joined lobbies
}
case name := <-rm.destroy:
if m, ok := rm.rooms[name]; ok {
// This should result inthe room being stopped/destroyed when the last
// connection is dropped
for conn := range m.room.members {
rm.leaveRoomByName(name, conn)
}
}
case req := <-rm.options:
c, ok := rm.members[req.conn]
if !ok { // If room association map for connection does not exist, create it!
c = newConnectionInfo()
rm.members[req.conn] = c
}
if req.overwrite {
c.options = req.options
} else {
c.options = req.options | c.options
}
// Send
case rMsg := <-rm.send:
if m, ok := rm.rooms[rMsg.to]; ok { // If room exists, get it and send data to it.
m.room.send <- rMsg.msg
}
// Stop
case <-rm.stop:
for k, m := range rm.rooms { // Stop all lobbies!
m.room.Stop()
delete(rm.rooms, k)
}
return
}
}
}
func (rm *RoomManager) SetConnectionOptions(conn *Connection, options uint32, overwrite bool) {
rm.options <- &connectionInfoReq{
conn: conn,
options: options,
overwrite: overwrite,
}
}
// Join adds the connection to the specified room.
func (rm *RoomManager) Join(name string, conn *Connection) {
rm.join <- &roomReq{
name: name,
conn: conn,
}
}
// Leave removes the connection from the specified room.
func (rm *RoomManager) Leave(name string, conn *Connection) {
rm.leave <- &roomReq{
name: name,
conn: conn,
}
}
// LeaveAll removes the connection from all joined rooms of this manager.
// This is an important step and should be called OnClose for all connections, that could have joined
// a room of the manager, to keep the member reference count of the manager accurate.
func (rm *RoomManager) LeaveAll(conn *Connection) {
rm.leaveAll <- conn
}
// Emit a message, that can be fetched using the golem client library. The provided
// data interface will be automatically marshalled according to the active protocol.
func (rm *RoomManager) Emit(to string, event string, data interface{}) {
rm.send <- &roomMsg{
to: to,
msg: &message{
event: event,
data: data,
},
}
}
// Stop the message loop and shutsdown the manager. It is safe to delete the instance afterwards.
func (rm *RoomManager) Stop() {
rm.stop <- true
}
// Remove connections from a particular room and delete the room
func (rm *RoomManager) Destroy(name string) {
rm.destroy <- name
}
// The room manager can emit several events. At the moment there are two events:
// "create" - triggered if a room was created and
// "remove" - triggered when a room was removed because of insufficient users
// For both the callback needs to be of the type func(string) where the argument
func (rm *RoomManager) On(eventName string, callback interface{}) {
switch eventName {
case roomManagerCreateEvent:
rm.callbackRoomCreation = callback.(func(string))
case roomManagerRemoveEvent:
rm.callbackRoomRemoval = callback.(func(string))
}
}