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  }