github.com/metaworking/channeld@v0.7.3/pkg/channeld/channel.go (about) 1 package channeld 2 3 import ( 4 "container/list" 5 "errors" 6 "fmt" 7 "net" 8 "sync" 9 "sync/atomic" 10 "time" 11 12 "github.com/metaworking/channeld/pkg/channeldpb" 13 "github.com/metaworking/channeld/pkg/common" 14 "github.com/puzpuzpuz/xsync/v2" 15 "go.uber.org/zap" 16 "google.golang.org/protobuf/reflect/protoreflect" 17 "google.golang.org/protobuf/reflect/protoregistry" 18 ) 19 20 type ChannelState uint8 21 22 const ( 23 ChannelState_INIT ChannelState = 0 24 ChannelState_OPEN ChannelState = 1 25 ChannelState_HANDOVER ChannelState = 2 26 ) 27 28 // ChannelTime is the relative time since the channel created. 29 type ChannelTime int64 // time.Duration 30 31 func (t ChannelTime) AddMs(ms uint32) ChannelTime { 32 return t + ChannelTime(time.Duration(ms)*time.Millisecond) 33 } 34 35 func (t ChannelTime) OffsetMs(ms int32) ChannelTime { 36 return t + ChannelTime(time.Duration(ms)*time.Millisecond) 37 } 38 39 type channelMessage struct { 40 ctx MessageContext 41 handler MessageHandlerFunc 42 } 43 44 // Use this interface instead of Connection for protecting the connection from unsafe writing in the channel goroutine. 45 type ConnectionInChannel interface { 46 Id() ConnectionId 47 GetConnectionType() channeldpb.ConnectionType 48 OnAuthenticated(pit string) 49 HasAuthorityOver(ch *Channel) bool 50 Close() 51 IsClosing() bool 52 Send(ctx MessageContext) 53 // Returns the subscription instance if successfully subscribed, and true if subscription message should be sent. 54 SubscribeToChannel(ch *Channel, options *channeldpb.ChannelSubscriptionOptions) (*ChannelSubscription, bool) 55 UnsubscribeFromChannel(ch *Channel) (*channeldpb.ChannelSubscriptionOptions, error) 56 sendSubscribed(ctx MessageContext, ch *Channel, connToSub ConnectionInChannel, stubId uint32, subOptions *channeldpb.ChannelSubscriptionOptions) 57 sendUnsubscribed(ctx MessageContext, ch *Channel, connToUnsub *Connection, stubId uint32) 58 HasInterestIn(spatialChId common.ChannelId) bool 59 Logger() *Logger 60 RemoteAddr() net.Addr 61 } 62 63 type Channel struct { 64 id common.ChannelId 65 channelType channeldpb.ChannelType 66 state ChannelState 67 // DO NOT use this field directly, use GetOwner() and SetOwner() instead. 68 ownerConnection ConnectionInChannel 69 ownerLock sync.RWMutex 70 subscribedConnections map[ConnectionInChannel]*ChannelSubscription 71 // Lock for sub/unsub outside the channel. Read lock: tickConnections, tickData(fan-out), Broadcast, GetAllConnections. 72 subLock sync.RWMutex 73 // Read-only property, e.g. name 74 metadata string 75 data *ChannelData 76 // The ID of the client connection that causes the latest ChannelDataUpdate 77 latestDataUpdateConnId ConnectionId 78 spatialNotifier common.SpatialInfoChangedNotifier 79 entityController EntityGroupController 80 inMsgQueue chan channelMessage 81 fanOutQueue *list.List 82 // Time since channel created 83 startTime time.Time 84 tickInterval time.Duration 85 tickFrames int 86 enableClientBroadcast bool 87 logger *Logger 88 removing int32 89 } 90 91 const ( 92 GlobalChannelId common.ChannelId = 0 93 ) 94 95 var nextChannelId common.ChannelId 96 var nextSpatialChannelId common.ChannelId 97 98 // Cache the status so we don't have to check all the index in the sync map, until a channel is removed. 99 var nonSpatialChannelFull bool = false 100 var spatialChannelFull bool = false 101 102 var allChannels *xsync.MapOf[common.ChannelId, *Channel] 103 var globalChannel *Channel 104 105 func InitChannels() { 106 if allChannels != nil { 107 return 108 } 109 110 allChannels = xsync.NewTypedMapOf[common.ChannelId, *Channel](UintIdHasher[common.ChannelId]()) 111 112 nextChannelId = 0 113 nextSpatialChannelId = GlobalSettings.SpatialChannelIdStart 114 var err error 115 globalChannel, err = CreateChannel(channeldpb.ChannelType_GLOBAL, nil) 116 if err != nil { 117 rootLogger.Panic("failed to create global channel", zap.Error(err)) 118 } 119 120 for chType, settings := range GlobalSettings.ChannelSettings { 121 if settings.DataMsgFullName == "" { 122 continue 123 } 124 125 msgType, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(settings.DataMsgFullName)) 126 if err != nil { 127 rootLogger.Error("failed to find message type for channel data", 128 zap.String("channelType", chType.String()), 129 zap.String("msgFullName", settings.DataMsgFullName), 130 zap.Error(err), 131 ) 132 continue 133 } 134 135 RegisterChannelDataType(chType, msgType.New().Interface()) 136 } 137 } 138 139 func GetChannel(id common.ChannelId) *Channel { 140 ch, ok := allChannels.Load(id) 141 if ok { 142 return ch 143 } else { 144 return nil 145 } 146 } 147 148 var ErrNonSpatialChannelFull = errors.New("non-spatial channels are full") 149 var ErrSpatialChannelFull = errors.New("spatial channels are full") 150 var ErrEntityChannelFull = errors.New("entity channels are full") 151 152 func createChannelWithId(channelId common.ChannelId, t channeldpb.ChannelType, owner ConnectionInChannel) *Channel { 153 ch := &Channel{ 154 id: channelId, 155 channelType: t, 156 ownerConnection: owner, 157 ownerLock: sync.RWMutex{}, 158 subscribedConnections: make(map[ConnectionInChannel]*ChannelSubscription), 159 subLock: sync.RWMutex{}, 160 /* Channel data is not created by default. See handleCreateChannel(). 161 data: ReflectChannelData(t, nil), 162 */ 163 inMsgQueue: make(chan channelMessage, 1024), 164 fanOutQueue: list.New(), 165 startTime: time.Now(), 166 tickInterval: time.Duration(GlobalSettings.GetChannelSettings(t).TickIntervalMs) * time.Millisecond, 167 tickFrames: 0, 168 logger: &Logger{rootLogger.With( 169 zap.String("channelType", t.String()), 170 zap.Uint32("channelId", uint32(channelId)), 171 )}, 172 removing: 0, 173 } 174 175 if ch.channelType == channeldpb.ChannelType_ENTITY { 176 ch.spatialNotifier = GetSpatialController() 177 ch.entityController = &FlatEntityGroupController{} 178 ch.entityController.Initialize(ch) 179 } 180 181 if ch.HasOwner() { 182 ch.state = ChannelState_OPEN 183 } else { 184 ch.state = ChannelState_INIT 185 } 186 187 allChannels.Store(ch.id, ch) 188 go ch.Tick() 189 190 channelNum.WithLabelValues(ch.channelType.String()).Inc() 191 192 Event_ChannelCreated.Broadcast(ch) 193 return ch 194 } 195 196 // Go-routine safe - should only be called in the GLOBAL channel 197 func CreateChannel(t channeldpb.ChannelType, owner ConnectionInChannel) (*Channel, error) { 198 if t == channeldpb.ChannelType_GLOBAL && globalChannel != nil { 199 return nil, errors.New("failed to create GLOBAL channel as it already exists") 200 } 201 202 var channelId common.ChannelId 203 var ok bool 204 if t == channeldpb.ChannelType_SPATIAL { 205 if spatialChannelFull { 206 return nil, ErrSpatialChannelFull 207 } 208 channelId, ok = GetNextIdTyped[common.ChannelId, *Channel](allChannels, nextSpatialChannelId, GlobalSettings.SpatialChannelIdStart, GlobalSettings.EntityChannelIdStart-1) 209 if ok { 210 nextSpatialChannelId = channelId 211 } else { 212 spatialChannelFull = true 213 return nil, ErrSpatialChannelFull 214 } 215 /* Entity channels use fixed channelId (= netId) 216 } else if t == channeldpb.ChannelType_ENTITY { 217 if entityChannelFull { 218 return nil, ErrEntityChannelFull 219 } 220 channelId, ok = GetNextIdTyped[common.ChannelId, *Channel](allChannels, nextEntityChannelId, GlobalSettings.EntityChannelIdStart, math.MaxUint32) 221 if ok { 222 nextEntityChannelId = channelId 223 } else { 224 entityChannelFull = true 225 return nil, ErrEntityChannelFull 226 } 227 */ 228 } else { 229 if nonSpatialChannelFull { 230 return nil, ErrNonSpatialChannelFull 231 } 232 channelId, ok = GetNextIdTyped[common.ChannelId, *Channel](allChannels, nextChannelId, 1, GlobalSettings.SpatialChannelIdStart-1) 233 if ok { 234 nextChannelId = channelId 235 } else { 236 nonSpatialChannelFull = true 237 return nil, ErrNonSpatialChannelFull 238 } 239 } 240 241 return createChannelWithId(channelId, t, owner), nil 242 } 243 244 func RemoveChannel(ch *Channel) { 245 Event_ChannelRemoving.Broadcast(ch) 246 247 if ch.channelType == channeldpb.ChannelType_ENTITY { 248 ch.entityController.Uninitialize(ch) 249 Event_AuthComplete.UnlistenFor(ch) 250 } 251 252 atomic.AddInt32(&ch.removing, 1) 253 close(ch.inMsgQueue) 254 allChannels.Delete(ch.id) 255 // Reset the channel full status cache 256 if ch.channelType == channeldpb.ChannelType_SPATIAL { 257 spatialChannelFull = false 258 nextSpatialChannelId = ch.id 259 } else if ch.channelType == channeldpb.ChannelType_ENTITY { 260 } else { 261 nonSpatialChannelFull = false 262 nextChannelId = ch.id 263 } 264 265 channelNum.WithLabelValues(ch.channelType.String()).Dec() 266 267 Event_ChannelRemoved.Broadcast(ch.id) 268 } 269 270 func (ch *Channel) Id() common.ChannelId { 271 return ch.id 272 } 273 274 func (ch *Channel) Type() channeldpb.ChannelType { 275 return ch.channelType 276 } 277 278 func (ch *Channel) IsRemoving() bool { 279 return ch.removing > 0 280 } 281 282 func (ch *Channel) PutMessage(msg common.Message, handler MessageHandlerFunc, conn *Connection, pack *channeldpb.MessagePack) { 283 if ch.IsRemoving() { 284 return 285 } 286 ch.inMsgQueue <- channelMessage{ctx: MessageContext{ 287 MsgType: channeldpb.MessageType(pack.MsgType), 288 Msg: msg, 289 Connection: conn, 290 Channel: ch, 291 Broadcast: pack.Broadcast, 292 StubId: pack.StubId, 293 ChannelId: pack.ChannelId, 294 arrivalTime: ch.GetTime(), 295 }, handler: handler} 296 } 297 298 func (ch *Channel) PutMessageContext(ctx MessageContext, handler MessageHandlerFunc) { 299 if ch.IsRemoving() { 300 return 301 } 302 303 ch.inMsgQueue <- channelMessage{ctx: ctx, handler: handler} 304 } 305 306 // Put the message into the channel's message queue. This method is used internally to make sure the message is handled in the channel's goroutine, to avoid race condition. 307 // 308 // For the MessageContext, the Connection is set as the channel's ownerConnection, and the ChannelId is set as the channel's id. 309 func (ch *Channel) PutMessageInternal(msgType channeldpb.MessageType, msg common.Message) { 310 if ch.IsRemoving() { 311 return 312 } 313 314 entry, exists := MessageMap[msgType] 315 if !exists { 316 ch.logger.Error("can't find message handler", zap.String("msgType", msgType.String())) 317 return 318 } 319 320 ch.inMsgQueue <- channelMessage{ctx: MessageContext{ 321 MsgType: msgType, 322 Msg: msg, 323 Connection: ch.GetOwner(), 324 Channel: ch, 325 Broadcast: 0, 326 StubId: 0, 327 ChannelId: uint32(ch.id), 328 arrivalTime: ch.GetTime(), 329 }, handler: entry.handler} 330 } 331 332 // Runs a function in the channel's go-routine. 333 // Any code that modifies the channel's data outside the the channel's go-routine should be run in this way. 334 func (ch *Channel) Execute(callback func(ch *Channel)) { 335 ch.inMsgQueue <- channelMessage{handler: func(_ MessageContext) { 336 callback(ch) 337 }} 338 } 339 340 func (ch *Channel) GetTime() ChannelTime { 341 return ChannelTime(time.Since(ch.startTime)) 342 } 343 344 func (ch *Channel) Tick() { 345 for { 346 if ch.IsRemoving() { 347 return 348 } 349 350 tickStart := time.Now() 351 352 // Run the code of SpatialController only in GLOBAL channel, to avoid any race condition. 353 if ch.channelType == channeldpb.ChannelType_GLOBAL && spatialController != nil { 354 spatialController.Tick() 355 } 356 357 ch.tickFrames++ 358 359 ch.tickMessages(tickStart) 360 361 ch.subLock.RLock() 362 ch.tickData(ch.GetTime()) 363 ch.tickConnections() 364 ch.subLock.RUnlock() 365 366 tickDuration := time.Since(tickStart) 367 channelTickDuration.WithLabelValues(ch.channelType.String()).Set(float64(tickDuration) / float64(time.Millisecond)) 368 369 time.Sleep(ch.tickInterval - tickDuration) 370 } 371 } 372 373 func (ch *Channel) tickMessages(tickStart time.Time) { 374 for len(ch.inMsgQueue) > 0 { 375 cm := <-ch.inMsgQueue 376 377 // No message in the context, just execute the handler. 378 if cm.ctx.Msg == nil { 379 cm.handler(cm.ctx) 380 continue 381 } 382 383 if cm.ctx.Connection == nil { 384 ch.Logger().Warn("drops message as the sender is lost", zap.Uint32("msgType", uint32(cm.ctx.MsgType))) 385 continue 386 } 387 cm.handler(cm.ctx) 388 if ch.tickInterval > 0 && time.Since(tickStart) >= ch.tickInterval { 389 ch.Logger().Warn("spent too long handling messages, will delay the left to the next tick", 390 zap.Duration("duration", time.Since(tickStart)), 391 zap.Int("remaining", len(ch.inMsgQueue)), 392 ) 393 break 394 } 395 } 396 } 397 398 func (ch *Channel) tickConnections() { 399 // defer func() { 400 // ch.subLock.RUnlock() 401 // }() 402 // ch.subLock.RLock() 403 404 for conn := range ch.subscribedConnections { 405 if conn.IsClosing() { 406 // Unsub the connection from the channel 407 delete(ch.subscribedConnections, conn) 408 conn.Logger().Info("removed subscription of a disconnected endpoint", zap.Uint32("channelId", uint32(ch.id))) 409 if ch.GetOwner() == conn { 410 // Reset the owner if it's removed 411 ch.SetOwner(nil) 412 if ch.channelType == channeldpb.ChannelType_GLOBAL { 413 Event_GlobalChannelUnpossessed.Broadcast(struct{}{}) 414 } 415 conn.Logger().Info("found removed ownner connection of channel", zap.Uint32("channelId", uint32(ch.id))) 416 if GlobalSettings.GetChannelSettings(ch.channelType).RemoveChannelAfterOwnerRemoved { 417 atomic.AddInt32(&ch.removing, 1) 418 419 // DO NOT remove the GLOBAL channel! 420 if ch != globalChannel { 421 // Only the GLOBAL channel can handle the channel removal 422 globalChannel.PutMessage(&channeldpb.RemoveChannelMessage{ 423 ChannelId: uint32(ch.id), 424 }, handleRemoveChannel, nil, &channeldpb.MessagePack{ 425 Broadcast: 0, 426 StubId: 0, 427 ChannelId: uint32(GlobalChannelId), 428 }) 429 } 430 431 ch.Logger().Info("removing channel after the owner is removed") 432 return 433 } 434 } else if conn != nil { 435 if ownerConn := ch.GetOwner(); ownerConn != nil { 436 ownerConn.sendUnsubscribed(MessageContext{}, ch, conn.(*Connection), 0) 437 } 438 } 439 } 440 } 441 } 442 443 func (ch *Channel) Broadcast(ctx MessageContext) { 444 defer func() { 445 ch.subLock.RUnlock() 446 }() 447 ch.subLock.RLock() 448 449 for conn := range ch.subscribedConnections { 450 //c := GetConnection(connId) 451 if conn == nil { 452 continue 453 } 454 if channeldpb.BroadcastType_ALL_BUT_SENDER.Check(ctx.Broadcast) && conn == ctx.Connection { 455 continue 456 } 457 if channeldpb.BroadcastType_ALL_BUT_OWNER.Check(ctx.Broadcast) && conn == ch.GetOwner() { 458 continue 459 } 460 if channeldpb.BroadcastType_ALL_BUT_CLIENT.Check(ctx.Broadcast) && conn.GetConnectionType() == channeldpb.ConnectionType_CLIENT { 461 continue 462 } 463 if channeldpb.BroadcastType_ALL_BUT_SERVER.Check(ctx.Broadcast) && conn.GetConnectionType() == channeldpb.ConnectionType_SERVER { 464 continue 465 } 466 conn.Send(ctx) 467 } 468 } 469 470 // Goroutine-safe read of the subscribed connections 471 func (ch *Channel) GetAllConnections() map[ConnectionInChannel]struct{} { 472 defer func() { 473 ch.subLock.RUnlock() 474 }() 475 ch.subLock.RLock() 476 477 conns := make(map[ConnectionInChannel]struct{}) 478 for conn := range ch.subscribedConnections { 479 conns[conn] = struct{}{} 480 } 481 return conns 482 } 483 484 // Return true if the connection can 1)remove; 2)sub/unsub another connection to/from; the channel. 485 func (c *Connection) HasAuthorityOver(ch *Channel) bool { 486 // The global owner has authority over everything. 487 if globalChannel.GetOwner() == c { 488 return true 489 } 490 if ch.GetOwner() == c { 491 return true 492 } 493 return false 494 } 495 496 func (ch *Channel) String() string { 497 return fmt.Sprintf("Channel(%s %d)", ch.channelType.String(), ch.id) 498 } 499 500 func (ch *Channel) Logger() *Logger { 501 return ch.logger 502 } 503 504 func (ch *Channel) HasOwner() bool { 505 conn := ch.GetOwner() 506 return conn != nil && !conn.IsClosing() 507 } 508 509 func (chA *Channel) IsSameOwner(chB *Channel) bool { 510 connA := chA.GetOwner() 511 return connA != nil && !connA.IsClosing() && connA == chB.GetOwner() 512 } 513 514 func (ch *Channel) SendMessageToOwner(msgType uint32, msg common.Message) bool { 515 conn := ch.GetOwner() 516 if conn != nil && !conn.IsClosing() { 517 conn.Send(MessageContext{ 518 MsgType: channeldpb.MessageType(msgType), 519 Msg: msg, 520 ChannelId: uint32(ch.id), 521 Broadcast: 0, 522 StubId: 0, 523 }) 524 return true 525 } 526 527 return false 528 } 529 530 func (ch *Channel) SendToOwner(ctx MessageContext) bool { 531 conn := ch.GetOwner() 532 if conn != nil && !conn.IsClosing() { 533 conn.Send(ctx) 534 return true 535 } 536 537 return false 538 } 539 540 // Implementation for ConnectionInChannel interface 541 func (c *Connection) IsNil() bool { 542 return c == nil 543 } 544 545 func (c *Channel) GetOwner() ConnectionInChannel { 546 c.ownerLock.RLock() 547 defer c.ownerLock.RUnlock() 548 549 return c.ownerConnection 550 } 551 552 func (c *Channel) SetOwner(conn ConnectionInChannel) { 553 c.ownerLock.Lock() 554 defer c.ownerLock.Unlock() 555 556 // Race condition: 557 // 1. set to nil when the owner unsubscribes from the entity channel. 558 // 2. set to dst server conn when the entity of the channel is handed over to the dst server. 559 c.ownerConnection = conn 560 }