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 */