github.com/metaworking/channeld@v0.7.3/pkg/channeld/spatial.go (about)

     1  package channeld
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"os"
     9  
    10  	"github.com/metaworking/channeld/pkg/channeldpb"
    11  	"github.com/metaworking/channeld/pkg/common"
    12  	"go.uber.org/zap"
    13  	"google.golang.org/protobuf/encoding/protojson"
    14  	"google.golang.org/protobuf/types/known/anypb"
    15  )
    16  
    17  type SpatialController interface {
    18  	// Initialize the spatial controller parameters from the json config file.
    19  	LoadConfig(config []byte) error
    20  	// Notify() is called in the spatial channels (shared instance)
    21  	common.SpatialInfoChangedNotifier
    22  	// Called in GLOBAL and spatial channels
    23  	GetChannelId(info common.SpatialInfo) (common.ChannelId, error)
    24  	// Called in the spatial channels
    25  	QueryChannelIds(query *channeldpb.SpatialInterestQuery) (map[common.ChannelId]uint, error)
    26  	// Called in GLOBAL channel
    27  	GetRegions() ([]*channeldpb.SpatialRegion, error)
    28  	// Called in the spatial channels
    29  	GetAdjacentChannels(spatialChannelId common.ChannelId) ([]common.ChannelId, error)
    30  	// Create spatial channels for a spatial server.
    31  	// Called in GLOBAL channel
    32  	CreateChannels(ctx MessageContext) ([]*Channel, error)
    33  	// Called in GLOBAL channel
    34  	Tick()
    35  }
    36  
    37  // A channeld instance should have only one SpatialController
    38  var spatialController SpatialController
    39  
    40  func InitSpatialController() {
    41  	if !GlobalSettings.SpatialControllerConfig.HasValue {
    42  		rootLogger.Info("spatial controller config is not set, spatial controller will not be created")
    43  		return
    44  	}
    45  
    46  	cfgPath := GlobalSettings.SpatialControllerConfig.Value
    47  	sccData, err := os.ReadFile(cfgPath)
    48  
    49  	if err != nil {
    50  		rootLogger.Panic("failed to read spatial controller config", zap.Error(err), zap.String("cfgPath", cfgPath))
    51  	}
    52  
    53  	// Unmarshal the spatial controller config to a map[string]string
    54  	var sccMap map[string]json.RawMessage
    55  	if err := json.Unmarshal(sccData, &sccMap); err != nil {
    56  		rootLogger.Panic("failed to unmarshall spatial controller config", zap.Error(err), zap.String("cfgPath", cfgPath))
    57  	}
    58  	// Unmarshal the spatial controller type to a string
    59  	// spatialControllerType := strings.Trim(string(sccMap["SpatialControllerType"]), "\"\\")
    60  
    61  	config, exists := sccMap["Config"]
    62  	if !exists {
    63  		rootLogger.Panic("'Config' does not exist in json", zap.String("cfgPath", cfgPath))
    64  	}
    65  	// TODO instance of spatialController should be created from the SpatialControllerConfig.SpatialControllerType
    66  	// current implementation only supports StaticGrid2DSpatialController
    67  	ctl := &StaticGrid2DSpatialController{}
    68  	ctl.LoadConfig(config)
    69  	spatialController = ctl
    70  	rootLogger.Info("created spatial controller",
    71  		zap.String("cfgPath", cfgPath),
    72  		// zap.String("spatialControllerType", spatialControllerType),
    73  	)
    74  }
    75  
    76  func GetSpatialController() SpatialController {
    77  	return spatialController
    78  }
    79  
    80  const (
    81  	MinY = -3.40282347e+38 / 2
    82  	MaxY = 3.40282347e+38 / 2
    83  )
    84  
    85  // Divide the world into GridCols x GridRows static squares on the XZ plane. Each square(grid) represents a spatial channel.
    86  // Typically, a player's view distance is 150m, so a grid is sized at 50x50m.
    87  // A 100x100 km world has 2000x2000 grids, which needs about 2^22 spatial channels.
    88  // By default, we support up to 2^32-2^16 grid-based spatial channels.
    89  type StaticGrid2DSpatialController struct {
    90  	SpatialController
    91  
    92  	/* Defines how the world is divided into grids */
    93  	// The width of a grid in simulation/engine units
    94  	GridWidth float64
    95  	// The heights of a grid in the simulation/engine units
    96  	GridHeight float64
    97  	// How many grids the world has in X axis. The width of the world = GridWidth x GridCols.
    98  	GridCols uint32
    99  	// How many grids the world has in Z axis. The height of the world = GridHeight x GridRows.
   100  	GridRows uint32
   101  
   102  	// In the left-handed coordinate system, the difference between the world origin and the bottom-left corner of the first grid, in the simulation/engine units.
   103  	// This is how we uses the offset value to calculate which grid a (x,z) point is in: gridX = Floor((x - OffsetX) / GridWidth), gridY = Floor((y - OffsetY) / GridHeight)
   104  	// If the world origin is exactly in the middle of the world, the offset should be (-WorldWidth*0.5, -WorldHeight*0.5).
   105  	WorldOffsetX float64
   106  	WorldOffsetZ float64
   107  
   108  	/* Defines the authority area of a spatial server, as well as the number of the servers (= ServerCols * ServerRows) */
   109  	// How many servers the world has in X axis.
   110  	ServerCols uint32
   111  	// How many servers the world has in Z axis.
   112  	ServerRows uint32
   113  
   114  	/* Defines the extra interest area a spatial server has, adjacent to the authority area */
   115  	// For each side of a server's grids (authority area), how many grids(spatial channels) the server subscribes to, as the extend of its interest area.
   116  	// For example, ServerInterestBorderSize = 1 means a spatial server of 3x3 grids has interest area of 4x4 grids.
   117  	// Remarks: the value should always be less than the size of the authority area (=Min(GridCols/ServerCols, GridRows/ServerRows))
   118  	ServerInterestBorderSize uint32
   119  
   120  	//serverIndex       uint32
   121  	serverConnections []ConnectionInChannel
   122  
   123  	gridSize float64
   124  }
   125  
   126  func (ctl *StaticGrid2DSpatialController) WorldWidth() float64 {
   127  	return ctl.GridWidth * float64(ctl.GridCols)
   128  }
   129  
   130  func (ctl *StaticGrid2DSpatialController) WorldHeight() float64 {
   131  	return ctl.GridHeight * float64(ctl.GridRows)
   132  }
   133  
   134  func (ctl *StaticGrid2DSpatialController) GridSize() float64 {
   135  	if ctl.gridSize == 0 && ctl.GridWidth > 0 && ctl.GridHeight > 0 {
   136  		ctl.gridSize = math.Sqrt(ctl.GridWidth*ctl.GridWidth + ctl.GridHeight*ctl.GridHeight)
   137  	}
   138  	return ctl.gridSize
   139  }
   140  
   141  func (ctl *StaticGrid2DSpatialController) LoadConfig(config []byte) error {
   142  	err := json.Unmarshal(config, ctl)
   143  	if err != nil {
   144  		return err
   145  	}
   146  	if ctl.GridWidth <= 0 || ctl.GridHeight <= 0 {
   147  		return errors.New("GridWidth and GridHeight should be positive")
   148  	}
   149  	if ctl.GridCols <= 0 || ctl.GridRows <= 0 {
   150  		return errors.New("GridCols and GridRows should be positive")
   151  	}
   152  	if ctl.ServerCols <= 0 || ctl.ServerRows <= 0 {
   153  		return errors.New("ServerCols and ServerRows should be positive")
   154  	}
   155  	if ctl.ServerInterestBorderSize <= 0 {
   156  		return errors.New("ServerInterestBorderSize should be positive")
   157  	}
   158  	return nil
   159  }
   160  
   161  func (ctl *StaticGrid2DSpatialController) GetChannelId(info common.SpatialInfo) (common.ChannelId, error) {
   162  	return ctl.GetChannelIdWithOffset(info, ctl.WorldOffsetX, ctl.WorldOffsetZ)
   163  }
   164  
   165  func (ctl *StaticGrid2DSpatialController) GetChannelIdNoOffset(info common.SpatialInfo) (common.ChannelId, error) {
   166  	return ctl.GetChannelIdWithOffset(info, 0, 0)
   167  }
   168  
   169  func (ctl *StaticGrid2DSpatialController) GetChannelIdWithOffset(info common.SpatialInfo, offsetX float64, offsetZ float64) (common.ChannelId, error) {
   170  	gridX := int(math.Floor((info.X - offsetX) / ctl.GridWidth))
   171  	if gridX < 0 || gridX >= int(ctl.GridCols) {
   172  		return 0, fmt.Errorf("gridX=%d when X=%f. GridX should be in [0,%d)", gridX, info.X, ctl.GridCols)
   173  	}
   174  	gridY := int(math.Floor((info.Z - offsetZ) / ctl.GridHeight))
   175  	if gridY < 0 || gridY >= int(ctl.GridRows) {
   176  		return 0, fmt.Errorf("gridY=%d when Z=%f. GridY should be in [0,%d)", gridY, info.Z, ctl.GridRows)
   177  	}
   178  	index := uint32(gridX) + uint32(gridY)*ctl.GridCols
   179  	return common.ChannelId(index) + GlobalSettings.SpatialChannelIdStart, nil
   180  }
   181  
   182  func (ctl *StaticGrid2DSpatialController) QueryChannelIds(query *channeldpb.SpatialInterestQuery) (map[common.ChannelId]uint, error) {
   183  	if query == nil {
   184  		return nil, fmt.Errorf("query is nil")
   185  	}
   186  
   187  	result := make(map[common.ChannelId]uint)
   188  
   189  	if query.SpotsAOI != nil {
   190  		for i, spot := range query.SpotsAOI.Spots {
   191  			chId, err := ctl.GetChannelId(common.SpatialInfo{X: spot.X, Y: spot.Y, Z: spot.Z})
   192  			if err != nil {
   193  				continue
   194  			}
   195  			if i < len(query.SpotsAOI.Dists) {
   196  				result[chId] = uint(query.SpotsAOI.Dists[i])
   197  			} else {
   198  				// If distance is not specified, the spot will be considered as always at the nearest distance.
   199  				result[chId] = 0
   200  			}
   201  		}
   202  	}
   203  
   204  	if query.BoxAOI != nil {
   205  		center := &common.SpatialInfo{X: query.BoxAOI.Center.X, Y: 0, Z: query.BoxAOI.Center.Z}
   206  
   207  		stepZ := math.Min(query.BoxAOI.Extent.Z, ctl.GridHeight) * 0.5
   208  		if stepZ <= 0 {
   209  			return nil, fmt.Errorf("invalid box extentZ=%f, gridHeight=%f", query.BoxAOI.Extent.Z, ctl.GridHeight)
   210  		}
   211  
   212  		stepX := math.Min(query.BoxAOI.Extent.X, ctl.GridWidth) * 0.5
   213  		if stepX <= 0 {
   214  			return nil, fmt.Errorf("invalid box extendX=%f, gridWidth=%f", query.BoxAOI.Extent.X, ctl.GridWidth)
   215  		}
   216  
   217  		for z := center.Z - query.BoxAOI.Extent.Z; z <= center.Z+query.BoxAOI.Extent.Z; z += stepZ {
   218  			for x := center.X - query.BoxAOI.Extent.X; x <= center.X+query.BoxAOI.Extent.X; x += stepX {
   219  				spot := common.SpatialInfo{X: x, Y: 0, Z: z}
   220  				chId, err := ctl.GetChannelId(spot)
   221  				if err != nil {
   222  					continue
   223  				}
   224  				result[chId] = uint(math.Ceil(center.Dist2D(&spot) / ctl.GridSize()))
   225  			}
   226  		}
   227  
   228  		centerChId, err := ctl.GetChannelId(*center)
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  		result[centerChId] = 0
   233  	}
   234  
   235  	if query.SphereAOI != nil {
   236  		r := query.SphereAOI.Radius
   237  		center := &common.SpatialInfo{X: query.SphereAOI.Center.X, Y: 0, Z: query.SphereAOI.Center.Z}
   238  
   239  		stepZ := math.Min(r, ctl.GridHeight) * 0.5
   240  		if stepZ <= 0 {
   241  			return nil, fmt.Errorf("invalid radius=%f, gridHeight=%f", r, ctl.GridHeight)
   242  		}
   243  
   244  		stepX := math.Min(r, ctl.GridWidth) * 0.5
   245  		if stepX <= 0 {
   246  			return nil, fmt.Errorf("invalid radius=%f, gridWidth=%f", r, ctl.GridWidth)
   247  		}
   248  
   249  		for z := center.Z - r; z <= center.Z+r; z += stepZ {
   250  			for x := center.X - r; x <= center.X+r; x += stepX {
   251  				if (x-center.X)*(x-center.X)+(z-center.Z)*(z-center.Z) > r*r {
   252  					continue
   253  				}
   254  				spot := common.SpatialInfo{X: x, Y: 0, Z: z}
   255  				chId, err := ctl.GetChannelId(spot)
   256  				if err != nil {
   257  					continue
   258  				}
   259  				result[chId] = uint(math.Ceil(center.Dist2D(&spot) / ctl.GridSize()))
   260  			}
   261  		}
   262  
   263  		centerChId, err := ctl.GetChannelId(*center)
   264  		if err != nil {
   265  			return nil, err
   266  		}
   267  		result[centerChId] = 0
   268  	}
   269  
   270  	if query.ConeAOI != nil {
   271  		r := query.ConeAOI.Radius
   272  		center := &common.SpatialInfo{X: query.ConeAOI.Center.X, Y: 0, Z: query.ConeAOI.Center.Z}
   273  		coneDir := &common.SpatialInfo{X: query.ConeAOI.Direction.X, Y: 0, Z: query.ConeAOI.Direction.Z}
   274  		coneDir.Normalize2D()
   275  
   276  		stepZ := math.Min(r, ctl.GridHeight) * 0.5
   277  		if stepZ <= 0 {
   278  			return nil, fmt.Errorf("invalid radius=%f, gridHeight=%f", r, ctl.GridHeight)
   279  		}
   280  
   281  		stepX := math.Min(r, ctl.GridWidth) * 0.5
   282  		if stepX <= 0 {
   283  			return nil, fmt.Errorf("invalid radius=%f, gridWidth=%f", r, ctl.GridWidth)
   284  		}
   285  
   286  		for z := math.Max(ctl.WorldOffsetZ, center.Z-r); z <= math.Min(ctl.WorldOffsetZ+ctl.WorldHeight(), center.Z+r); z += stepZ {
   287  			for x := math.Max(ctl.WorldOffsetX, center.X-r); x <= math.Min(ctl.WorldOffsetX+ctl.WorldWidth(), center.X+r); x += stepX {
   288  				if (x-center.X)*(x-center.X)+(z-center.Z)*(z-center.Z) > r*r {
   289  					continue
   290  				}
   291  				spot := common.SpatialInfo{X: x, Y: 0, Z: z}
   292  				dir := common.SpatialInfo{X: spot.X - center.X, Y: 0, Z: spot.Z - center.Z}
   293  				dir.Normalize2D()
   294  				dot := dir.Dot2D(coneDir)
   295  				cos := math.Cos(query.ConeAOI.Angle) // * 0.5)
   296  				const epsilon = 0.0                  //0.001
   297  				if dot < cos-epsilon {
   298  					continue
   299  				}
   300  
   301  				chId, err := ctl.GetChannelId(spot)
   302  				if err != nil {
   303  					continue
   304  				}
   305  				result[chId] = uint(math.Ceil(center.Dist2D(&spot) / ctl.GridSize()))
   306  			}
   307  		}
   308  
   309  		centerChId, err := ctl.GetChannelId(*center)
   310  		if err != nil {
   311  			return nil, err
   312  		}
   313  		result[centerChId] = 0
   314  	}
   315  
   316  	return result, nil
   317  }
   318  
   319  func (ctl *StaticGrid2DSpatialController) GetRegions() ([]*channeldpb.SpatialRegion, error) {
   320  	// How many grids a server has in X axis
   321  	serverGridCols := ctl.GridCols / ctl.ServerCols
   322  	if ctl.GridCols%ctl.ServerCols > 0 {
   323  		serverGridCols++
   324  	}
   325  	// How many grids a server has in Z axis
   326  	serverGridRows := ctl.GridRows / ctl.ServerRows
   327  	if ctl.GridRows%ctl.ServerRows > 0 {
   328  		serverGridRows++
   329  	}
   330  
   331  	regions := make([]*channeldpb.SpatialRegion, ctl.GridCols*ctl.GridRows)
   332  	//var MinFloat64 = math.Inf(-1)
   333  	for y := uint32(0); y < ctl.GridRows; y++ {
   334  		for x := uint32(0); x < ctl.GridCols; x++ {
   335  			index := x + y*ctl.GridCols
   336  			serverX := x / serverGridCols
   337  			serverY := y / serverGridRows
   338  
   339  			regions[index] = &channeldpb.SpatialRegion{
   340  				Min: &channeldpb.SpatialInfo{
   341  					X: ctl.WorldOffsetX + ctl.GridWidth*float64(x),
   342  					Y: MinY,
   343  					Z: ctl.WorldOffsetZ + ctl.GridHeight*float64(y),
   344  				},
   345  				Max: &channeldpb.SpatialInfo{
   346  					X: ctl.WorldOffsetX + ctl.GridWidth*float64(x+1),
   347  					Y: MaxY,
   348  					Z: ctl.WorldOffsetZ + ctl.GridHeight*float64(y+1),
   349  				},
   350  				ChannelId:   uint32(GlobalSettings.SpatialChannelIdStart) + index,
   351  				ServerIndex: serverX + serverY*ctl.ServerCols,
   352  			}
   353  		}
   354  	}
   355  	return regions, nil
   356  }
   357  
   358  func (ctl *StaticGrid2DSpatialController) GetAdjacentChannels(spatialChannelId common.ChannelId) ([]common.ChannelId, error) {
   359  	index := uint32(spatialChannelId - GlobalSettings.SpatialChannelIdStart)
   360  	gridX := int32(index % ctl.GridCols)
   361  	gridY := int32(index / ctl.GridCols)
   362  	channelIds := make([]common.ChannelId, 0)
   363  	for y := gridY - 1; y <= gridY+1; y++ {
   364  		if y < 0 || y > int32(ctl.GridRows-1) {
   365  			continue
   366  		}
   367  
   368  		for x := gridX - 1; x <= gridX+1; x++ {
   369  			if x < 0 || x > int32(ctl.GridCols-1) {
   370  				continue
   371  			}
   372  			if x == gridX && y == gridY {
   373  				continue
   374  			}
   375  
   376  			channelIndex := uint32(x) + uint32(y)*ctl.GridCols
   377  			channelIds = append(channelIds, common.ChannelId(channelIndex)+GlobalSettings.SpatialChannelIdStart)
   378  		}
   379  	}
   380  	return channelIds, nil
   381  }
   382  
   383  func (ctl *StaticGrid2DSpatialController) CreateChannels(ctx MessageContext) ([]*Channel, error) {
   384  	ctl.initServerConnections()
   385  	serverIndex := ctl.nextServerIndex()
   386  	if serverIndex >= ctl.ServerCols*ctl.ServerRows {
   387  		return nil, fmt.Errorf("failed to create spatail channel as all %d grids are allocated to %d servers", ctl.GridCols*ctl.GridRows, ctl.ServerCols*ctl.ServerRows)
   388  	}
   389  
   390  	msg, ok := ctx.Msg.(*channeldpb.CreateChannelMessage)
   391  	if !ok {
   392  		return nil, errors.New("ctx.Msg is not a CreateChannelMessage, will not be handled")
   393  	}
   394  
   395  	// How many grids a server has in X axis
   396  	serverGridCols := ctl.GridCols / ctl.ServerCols
   397  	if ctl.GridCols%ctl.ServerCols > 0 {
   398  		serverGridCols++
   399  	}
   400  	// How many grids a server has in Z axis
   401  	serverGridRows := ctl.GridRows / ctl.ServerRows
   402  	if ctl.GridRows%ctl.ServerRows > 0 {
   403  		serverGridRows++
   404  	}
   405  
   406  	channelIds := make([]common.ChannelId, serverGridCols*serverGridRows)
   407  	serverX := serverIndex % ctl.ServerCols
   408  	serverY := serverIndex / ctl.ServerCols
   409  	var spatialInfo common.SpatialInfo
   410  	for y := uint32(0); y < serverGridRows; y++ {
   411  		for x := uint32(0); x < serverGridCols; x++ {
   412  			spatialInfo.X = float64(serverX*serverGridCols+x) * ctl.GridWidth
   413  			spatialInfo.Z = float64(serverY*serverGridRows+y) * ctl.GridHeight
   414  			channelId, err := ctl.GetChannelIdNoOffset(spatialInfo)
   415  			if err != nil {
   416  				return nil, err
   417  			}
   418  			channelIds[x+y*serverGridCols] = channelId
   419  		}
   420  	}
   421  
   422  	channels := make([]*Channel, len(channelIds))
   423  	for index, channelId := range channelIds {
   424  		channel := createChannelWithId(channelId, channeldpb.ChannelType_SPATIAL, ctx.Connection)
   425  		if msg.Data != nil {
   426  			dataMsg, err := msg.Data.UnmarshalNew()
   427  			if err != nil {
   428  				return nil, fmt.Errorf("failed to unmarshal data message for the new channel: %v", err)
   429  			} else {
   430  				channel.InitData(dataMsg, msg.MergeOptions)
   431  			}
   432  		} else {
   433  			// Channel data should always be initialized
   434  			channel.InitData(nil, msg.MergeOptions)
   435  		}
   436  
   437  		channels[index] = channel
   438  	}
   439  
   440  	// Save the connection for later use
   441  	ctl.serverConnections[serverIndex] = ctx.Connection
   442  	//ctl.serverIndex++
   443  	serverIndex = ctl.nextServerIndex()
   444  	// When all spatial channels are created, subscribe each server to its adjacent grids(channels) if exists.
   445  	if serverIndex == ctl.ServerCols*ctl.ServerRows {
   446  		for i := uint32(0); i < serverIndex; i++ {
   447  			err := ctl.subToAdjacentChannels(i, serverGridCols, serverGridRows, msg.SubOptions)
   448  			if err != nil {
   449  				return channels, fmt.Errorf("failed to sub to adjacent channels of server connection %d, err: %v", ctl.serverConnections[i].Id(), err)
   450  			}
   451  		}
   452  
   453  		// ...and send the SpatialChannelsReadyMessage to all the spatial servers.
   454  		readyMsg := &channeldpb.SpatialChannelsReadyMessage{
   455  			ServerIndex: serverIndex,
   456  			ServerCount: ctl.ServerCols * ctl.ServerRows,
   457  		}
   458  		for _, serverConn := range ctl.serverConnections {
   459  			serverConn.Send(MessageContext{
   460  				MsgType: channeldpb.MessageType_SPATIAL_CHANNELS_READY,
   461  				Msg:     readyMsg,
   462  			})
   463  		}
   464  	}
   465  
   466  	return channels, nil
   467  }
   468  
   469  func (ctl *StaticGrid2DSpatialController) subToAdjacentChannels(serverIndex uint32, serverGridCols uint32, serverGridRows uint32, subOptions *channeldpb.ChannelSubscriptionOptions) error {
   470  	if ctl.ServerInterestBorderSize == 0 {
   471  		return nil
   472  	}
   473  
   474  	serverConn := ctl.serverConnections[serverIndex]
   475  	serverX := serverIndex % ctl.ServerCols
   476  	serverY := serverIndex / ctl.ServerCols
   477  	spatialInfo := common.SpatialInfo{
   478  		X: float64(serverX*serverGridCols) * ctl.GridWidth,
   479  		Z: float64(serverY*serverGridRows) * ctl.GridHeight,
   480  	}
   481  	serverChannelId, err := ctl.GetChannelIdNoOffset(spatialInfo)
   482  	if err != nil {
   483  		return err
   484  	}
   485  	serverChannel := GetChannel(serverChannelId)
   486  	if serverChannel == nil {
   487  		return fmt.Errorf("failed to subscribe to adjacent channels for %d as it doesn't exist", serverChannelId)
   488  	}
   489  
   490  	// Right border
   491  	if serverX > 0 {
   492  		for y := uint32(0); y < serverGridRows; y++ {
   493  			for x := uint32(1); x <= ctl.ServerInterestBorderSize; x++ {
   494  				spatialInfo.X = float64(serverX*serverGridCols-x) * ctl.GridWidth
   495  				spatialInfo.Z = float64(serverY*serverGridRows+y) * ctl.GridHeight
   496  				channelId, err := ctl.GetChannelIdNoOffset(spatialInfo)
   497  				if err != nil {
   498  					return err
   499  				}
   500  				channelToSub := GetChannel(channelId)
   501  				if channelToSub == nil {
   502  					return fmt.Errorf("failed to subscribe border channel %d as it doesn't exist", channelId)
   503  				}
   504  				cs, shouldSend := serverConn.SubscribeToChannel(channelToSub, subOptions)
   505  				if shouldSend {
   506  					serverConn.sendSubscribed(MessageContext{}, channelToSub, serverConn, 0, &cs.options)
   507  				}
   508  			}
   509  		}
   510  	}
   511  
   512  	// Left border
   513  	if serverX < ctl.ServerCols-1 {
   514  		for y := uint32(0); y < serverGridRows; y++ {
   515  			for x := uint32(0); x < ctl.ServerInterestBorderSize; x++ {
   516  				spatialInfo.X = float64((serverX+1)*serverGridCols+x) * ctl.GridWidth
   517  				spatialInfo.Z = float64(serverY*serverGridRows+y) * ctl.GridHeight
   518  				channelId, err := ctl.GetChannelIdNoOffset(spatialInfo)
   519  				if err != nil {
   520  					return err
   521  				}
   522  				channelToSub := GetChannel(channelId)
   523  				if channelToSub == nil {
   524  					return fmt.Errorf("failed to subscribe border channel %d as it doesn't exist", channelId)
   525  				}
   526  				cs, shouldSend := serverConn.SubscribeToChannel(channelToSub, subOptions)
   527  				if shouldSend {
   528  					serverConn.sendSubscribed(MessageContext{}, channelToSub, serverConn, 0, &cs.options)
   529  				}
   530  			}
   531  		}
   532  	}
   533  
   534  	// Top border
   535  	if serverY > 0 {
   536  		for y := uint32(1); y <= ctl.ServerInterestBorderSize; y++ {
   537  			for x := uint32(0); x < serverGridCols; x++ {
   538  				spatialInfo.X = float64(serverX*serverGridCols+x) * ctl.GridWidth
   539  				spatialInfo.Z = float64(serverY*serverGridRows-y) * ctl.GridHeight
   540  				channelId, err := ctl.GetChannelIdNoOffset(spatialInfo)
   541  				if err != nil {
   542  					return err
   543  				}
   544  				channelToSub := GetChannel(channelId)
   545  				if channelToSub == nil {
   546  					return fmt.Errorf("failed to subscribe border channel %d as it doesn't exist", channelId)
   547  				}
   548  				cs, shouldSend := serverConn.SubscribeToChannel(channelToSub, subOptions)
   549  				if shouldSend {
   550  					serverConn.sendSubscribed(MessageContext{}, channelToSub, serverConn, 0, &cs.options)
   551  				}
   552  			}
   553  		}
   554  	}
   555  
   556  	// Bottom border
   557  	if serverY < ctl.ServerRows-1 {
   558  		for y := uint32(0); y < ctl.ServerInterestBorderSize; y++ {
   559  			for x := uint32(0); x < serverGridCols; x++ {
   560  				spatialInfo.X = float64(serverX*serverGridCols+x) * ctl.GridWidth
   561  				spatialInfo.Z = float64((serverY+1)*serverGridRows+y) * ctl.GridHeight
   562  				channelId, err := ctl.GetChannelIdNoOffset(spatialInfo)
   563  				if err != nil {
   564  					return err
   565  				}
   566  				channelToSub := GetChannel(channelId)
   567  				if channelToSub == nil {
   568  					return fmt.Errorf("failed to subscribe border channel %d as it doesn't exist", channelId)
   569  				}
   570  				cs, shouldSend := serverConn.SubscribeToChannel(channelToSub, subOptions)
   571  				if shouldSend {
   572  					serverConn.sendSubscribed(MessageContext{}, channelToSub, serverConn, 0, &cs.options)
   573  				}
   574  			}
   575  		}
   576  	}
   577  	return nil
   578  }
   579  
   580  var dataMarshalOptions = protojson.MarshalOptions{Multiline: false}
   581  
   582  type HandoverDataWithPayload interface {
   583  	// Clear the payload of the handover data so it won't be send to the connection that don't have the interest
   584  	ClearPayload()
   585  }
   586  
   587  type HandoverDataMerger interface {
   588  	// Entity channel data merges itself to the spatial channel data for handover.
   589  	// If fullData is true, merge the full entity data into the spatial channel data; otherwise, merge the entity identifier only.
   590  	MergeTo(spatialChannelData common.Message, fullData bool) error
   591  }
   592  
   593  // Spatial channel data shoud implement this interface to support entity spawn, destory, and handover
   594  type SpatialChannelEntityUpdater interface {
   595  	AddEntity(EntityId, common.Message) error
   596  	RemoveEntity(EntityId) error
   597  }
   598  
   599  // Runs in the source spatial(V1)/entity(V2) channel (shared instance)
   600  func (ctl *StaticGrid2DSpatialController) Notify(oldInfo common.SpatialInfo, newInfo common.SpatialInfo, handoverDataProvider func(common.ChannelId, common.ChannelId, interface{})) {
   601  	srcChannelId, err := ctl.GetChannelId(oldInfo)
   602  	if err != nil {
   603  		rootLogger.Error("failed to calculate srcChannelId", zap.Error(err), zap.String("oldInfo", oldInfo.String()))
   604  		return
   605  	}
   606  	dstChannelId, err := ctl.GetChannelId(newInfo)
   607  	if err != nil {
   608  		rootLogger.Error("failed to calculate dstChannelId", zap.Error(err), zap.String("newInfo", newInfo.String()))
   609  		return
   610  	}
   611  	// No migration between channels
   612  	if dstChannelId == srcChannelId {
   613  		return
   614  	}
   615  
   616  	srcChannel := GetChannel(srcChannelId)
   617  	if srcChannel == nil {
   618  		rootLogger.Error("channel doesn't exist, failed to handover channel data", zap.Uint32("srcChannelId", uint32(srcChannelId)))
   619  		return
   620  	}
   621  	if !srcChannel.HasOwner() {
   622  		rootLogger.Error("channel doesn't have owner, failed to handover channel data", zap.Uint32("srcChannelId", uint32(srcChannelId)))
   623  	}
   624  
   625  	dstChannel := GetChannel(dstChannelId)
   626  	if dstChannel == nil {
   627  		rootLogger.Error("channel doesn't exist, failed to handover channel data", zap.Uint32("dstChannelId", uint32(dstChannelId)))
   628  		return
   629  	}
   630  	if !srcChannel.HasOwner() {
   631  		rootLogger.Error("channel doesn't have owner, failed to handover channel data", zap.Uint32("dstChannelId", uint32(dstChannelId)))
   632  	}
   633  
   634  	var handoverEntityId EntityId
   635  	handoverDataProvider(srcChannelId, dstChannelId, &handoverEntityId)
   636  
   637  	rootLogger.Debug("handover group", zap.Uint32("entityId", uint32(handoverEntityId)))
   638  
   639  	spatialDataMsg, err := ReflectChannelDataMessage(channeldpb.ChannelType_SPATIAL)
   640  	if err != nil {
   641  		rootLogger.Error("failed to create handover data message for spatial channel", zap.Error(err))
   642  		return
   643  	}
   644  
   645  	if initializer, ok := spatialDataMsg.(ChannelDataInitializer); ok {
   646  		initializer.Init()
   647  	}
   648  
   649  	/*
   650  		// Has the entity data set for each SpatialEntityState.
   651  		spatialDataMsgFull := spatialDataMsg.ProtoReflect().New().Interface()
   652  		if initializer, ok := spatialDataMsgFull.(ChannelDataInitializer); ok {
   653  			initializer.Init()
   654  		}
   655  	*/
   656  
   657  	entityChannel := GetChannel(common.ChannelId(handoverEntityId))
   658  	if entityChannel == nil {
   659  		rootLogger.Warn("failed to handover entity as the channel doesn't exist", zap.Uint32("entityId", uint32(handoverEntityId)))
   660  		return
   661  	}
   662  
   663  	handoverEntities := entityChannel.GetHandoverEntities(handoverEntityId)
   664  	// No handover happens
   665  	if len(handoverEntities) == 0 {
   666  		return
   667  	}
   668  
   669  	// Step 1: Handle the cross-server handover
   670  	// Should be done as soon as possible to prevent the src spatial server from sending the entity channel data update.
   671  	if !srcChannel.IsSameOwner(dstChannel) {
   672  		for entityId := range handoverEntities {
   673  			entityCh := GetChannel(common.ChannelId(entityId))
   674  			if entityCh == nil {
   675  				continue
   676  			}
   677  
   678  			if ownerConn := srcChannel.GetOwner(); ownerConn != nil && !ownerConn.IsClosing() && !ownerConn.HasInterestIn(dstChannelId) {
   679  				// Unsub the src spatial server from the entity channel if the server has no interest in the dst spatial channel
   680  				ownerConn.UnsubscribeFromChannel(entityCh)
   681  				ownerConn.sendUnsubscribed(MessageContext{}, entityCh, nil, 0)
   682  			}
   683  
   684  			// Set the owner of the entity channel to the dst spatial server, so the src spatial server's residual update will be ignored.
   685  			// Otherwise repeating handover may happen!
   686  			entityCh.SetOwner(dstChannel.GetOwner())
   687  		}
   688  	}
   689  
   690  	// Step 2-1: Remove the entities from the src spatial channel's data
   691  	srcChannel.Execute(func(ch *Channel) {
   692  		updater, ok := ch.GetDataMessage().(SpatialChannelEntityUpdater)
   693  		if !ok {
   694  			ch.Logger().Warn("spatial channel data doesn't implement SpatialChannelEntityUpdater")
   695  			return
   696  		}
   697  		for entityId := range handoverEntities {
   698  			if err := updater.RemoveEntity(entityId); err != nil {
   699  				ch.Logger().Warn("failed to remove entity from spatial channel data", zap.Error(err))
   700  			} else {
   701  				ch.Logger().Debug("removed entity from spatial channel data", zap.Uint32("entityId", uint32(entityId)))
   702  			}
   703  		}
   704  	})
   705  
   706  	// Step 2-2: Add the entities to the dst spatial channel's data
   707  	dstChannel.Execute(func(ch *Channel) {
   708  		updater, ok := ch.GetDataMessage().(SpatialChannelEntityUpdater)
   709  		if !ok {
   710  			ch.Logger().Warn("spatial channel data doesn't implement SpatialChannelEntityUpdater")
   711  			return
   712  		}
   713  		for entityId, entityData := range handoverEntities {
   714  			if entityData == nil {
   715  				ch.Logger().Warn("failed to add entity to spatial channel as it doesn't have data", zap.Uint32("entityId", uint32(entityId)))
   716  				continue
   717  			}
   718  			if err := updater.AddEntity(entityId, entityData); err != nil {
   719  				ch.Logger().Warn("failed to add entity to spatial channel data", zap.Error(err))
   720  			} else {
   721  				ch.Logger().Debug("added entity to spatial channel data", zap.Uint32("entityId", uint32(entityId)))
   722  			}
   723  		}
   724  	})
   725  
   726  	// Step 3-1: Merge the entities to the spatial data message
   727  	for entityId, entityData := range handoverEntities {
   728  		if entityData == nil {
   729  			rootLogger.Warn("failed to handover entity as its channel doesn't have data", zap.Uint32("entityId", uint32(entityId)))
   730  			continue
   731  		}
   732  
   733  		if handoverMerger, ok := entityData.(HandoverDataMerger); ok {
   734  			handoverMerger.MergeTo(spatialDataMsg, false)
   735  		} else {
   736  			rootLogger.Warn("entity data doesn't implement HandoverDataMerger",
   737  				zap.Uint32("entityId", uint32(entityId)),
   738  				zap.String("msgName", string(entityData.ProtoReflect().Descriptor().FullName())),
   739  			)
   740  		}
   741  	}
   742  
   743  	// Step 3-2: Prepare the handover message
   744  	handoverAnyData, err := anypb.New(spatialDataMsg)
   745  	if err != nil {
   746  		rootLogger.Error("failed to marshal spatial handover data", zap.Error(err))
   747  		return
   748  	}
   749  
   750  	handoverMsgCtx := MessageContext{
   751  		MsgType: channeldpb.MessageType_CHANNEL_DATA_HANDOVER,
   752  		Msg: &channeldpb.ChannelDataHandoverMessage{
   753  			SrcChannelId:  uint32(srcChannelId),
   754  			DstChannelId:  uint32(dstChannelId),
   755  			Data:          handoverAnyData,
   756  			ContextConnId: uint32(srcChannel.latestDataUpdateConnId),
   757  		},
   758  		Broadcast: 0,
   759  		StubId:    0,
   760  		ChannelId: uint32(dstChannelId),
   761  	}
   762  
   763  	// Step 4: Send the handover message to all connections in the srcChannel and dstChannel
   764  	srcChannelSubConns := srcChannel.GetAllConnections()
   765  	dstChannelSubConns := dstChannel.GetAllConnections()
   766  
   767  	// Step 4-1: Send to the connections in the srcChannel. The handover message doesn't contain the entity data.
   768  	for conn := range srcChannelSubConns {
   769  		// Avoid duplicate sending
   770  		if _, exists := dstChannelSubConns[conn]; exists {
   771  			continue
   772  		}
   773  
   774  		conn.Send(handoverMsgCtx)
   775  	}
   776  
   777  	// Step 4-2: Send to the connections in the dstChannel. The handover message contains the entity data if the connection hasn't subscribed to the entity channel yet.
   778  	// Also, subscribe the connection to the entity channel if it hasn't subscribed yet.
   779  	subOptions := &channeldpb.ChannelSubscriptionOptions{
   780  		SkipSelfUpdateFanOut: Pointer(true),
   781  		// Since we already wrap the entity data in the handover message, no need to fan out again.
   782  		SkipFirstFanOut: Pointer(true),
   783  	}
   784  
   785  	for conn := range dstChannelSubConns {
   786  		handoverDataMsg := spatialDataMsg.ProtoReflect().New().Interface()
   787  
   788  		for entityId, entityData := range handoverEntities {
   789  			entityCh := GetChannel(common.ChannelId(entityId))
   790  			if entityCh == nil {
   791  				rootLogger.Warn("failed to handover entity as its channel doesn't exist", zap.Uint32("entityId", uint32(entityId)))
   792  				continue
   793  			}
   794  
   795  			if entityData == nil {
   796  				rootLogger.Warn("failed to handover entity as its channel doesn't have data", zap.Uint32("entityId", uint32(entityId)))
   797  				continue
   798  			}
   799  
   800  			// Only the owner can update the entity
   801  			if conn == entityCh.GetOwner() {
   802  				subOptions.DataAccess = Pointer(channeldpb.ChannelDataAccess_WRITE_ACCESS)
   803  			} else {
   804  				subOptions.DataAccess = Pointer(channeldpb.ChannelDataAccess_READ_ACCESS)
   805  			}
   806  			// TODO: set subOptions from the entity's replication settings in the engine.
   807  
   808  			// Subscribe to the entity channel for every connection in the dst spatial channel
   809  			// FIXME: Do not subscribe UE client to the PlayerController or PlayerState entity channel
   810  			cs, shouldSend := conn.SubscribeToChannel(entityCh, subOptions)
   811  			if cs == nil {
   812  				continue
   813  			}
   814  			// If the data access changes, the SubscribedToChannelResultMessage must be sent, otherwise the connection may have problem sending the ChannelDataUpdateMessage..
   815  			if shouldSend {
   816  				conn.sendSubscribed(MessageContext{}, entityCh, conn, 0, &cs.options)
   817  			}
   818  
   819  			handoverMerger, hasMerger := entityData.(HandoverDataMerger)
   820  			if hasMerger {
   821  				// Send the handover data with full states to the new subscribers,
   822  				// in order to initialize the PlayerController properly.
   823  				handoverMerger.MergeTo(handoverDataMsg, shouldSend)
   824  			} else {
   825  				rootLogger.Warn("entity data doesn't implement HandoverDataMerger",
   826  					zap.Uint32("entityId", uint32(handoverEntityId)),
   827  					zap.String("msgName", string(entityData.ProtoReflect().Descriptor().FullName())),
   828  				)
   829  			}
   830  		}
   831  
   832  		anyData, err := anypb.New(handoverDataMsg)
   833  		if err != nil {
   834  			rootLogger.Error("failed to marshal handover data message", zap.Error(err))
   835  			continue
   836  		}
   837  
   838  		handoverMsgCtx.Msg = &channeldpb.ChannelDataHandoverMessage{
   839  			SrcChannelId:  uint32(srcChannelId),
   840  			DstChannelId:  uint32(dstChannelId),
   841  			Data:          anyData,
   842  			ContextConnId: uint32(srcChannel.latestDataUpdateConnId),
   843  		}
   844  		conn.Send(handoverMsgCtx)
   845  	}
   846  }
   847  
   848  func (ctl *StaticGrid2DSpatialController) initServerConnections() {
   849  	if ctl.serverConnections == nil {
   850  		ctl.serverConnections = make([]ConnectionInChannel, ctl.ServerCols*ctl.ServerRows)
   851  	}
   852  }
   853  
   854  func (ctl *StaticGrid2DSpatialController) nextServerIndex() uint32 {
   855  	var i int = 0
   856  	for i = 0; i < len(ctl.serverConnections); i++ {
   857  		if ctl.serverConnections[i] == nil || ctl.serverConnections[i].IsClosing() {
   858  			break
   859  		}
   860  	}
   861  	return uint32(i)
   862  }
   863  
   864  func (ctl *StaticGrid2DSpatialController) Tick() {
   865  	ctl.initServerConnections()
   866  	for i := 0; i < len(ctl.serverConnections); i++ {
   867  		if ctl.serverConnections[i] != nil && ctl.serverConnections[i].IsClosing() {
   868  			ctl.serverConnections[i] = nil
   869  			rootLogger.Info("reset spatial server connection", zap.Int("serverIndex", i))
   870  		}
   871  	}
   872  }
   873  
   874  /*
   875  // Used for sending message between channels
   876  var internalDummyConnection = &Connection{
   877  	id:              math.MaxUint32,
   878  	connectionType:  channeldpb.ConnectionType_NO_CONNECTION,
   879  	compressionType: channeldpb.CompressionType_NO_COMPRESSION,
   880  	conn:            nil,
   881  	reader:          nil,
   882  	writer:          nil,
   883  	sender:          nil, //&queuedMessageSender{},
   884  	sendQueue:       nil, //make(chan MessageContext, 128),
   885  	logger: logger.With(
   886  		zap.String("connType", "Internal"),
   887  	),
   888  	removing: 0,
   889  }
   890  */