github.com/metaworking/channeld@v0.7.3/pkg/channeld/message_spatial.go (about) 1 package channeld 2 3 import ( 4 "github.com/metaworking/channeld/pkg/channeldpb" 5 "github.com/metaworking/channeld/pkg/common" 6 "go.uber.org/zap" 7 "google.golang.org/protobuf/proto" 8 ) 9 10 type SpatialDampingSettings struct { 11 MaxDistance uint 12 FanOutIntervalMs uint32 13 DataFieldMasks []string 14 } 15 16 var spatialDampingSettings []*SpatialDampingSettings = []*SpatialDampingSettings{ 17 { 18 MaxDistance: 0, 19 FanOutIntervalMs: 20, 20 }, 21 { 22 MaxDistance: 1, 23 FanOutIntervalMs: 50, 24 }, 25 { 26 MaxDistance: 2, 27 FanOutIntervalMs: 100, 28 }, 29 } 30 31 func getSpatialDampingSettings(dist uint) *SpatialDampingSettings { 32 for _, s := range spatialDampingSettings { 33 if dist <= s.MaxDistance { 34 return s 35 } 36 } 37 return nil 38 } 39 40 // Executed in the spatial channels 41 func handleUpdateSpatialInterest(ctx MessageContext) { 42 msg, ok := ctx.Msg.(*channeldpb.UpdateSpatialInterestMessage) 43 if !ok { 44 ctx.Connection.Logger().Error("message is not a UpdateSpatialInterestMessage, will not be handled.") 45 return 46 } 47 48 if spatialController == nil { 49 ctx.Connection.Logger().Error("cannot update spatial interest as the spatial controller does not exist") 50 return 51 } 52 53 clientConn := GetConnection(ConnectionId(msg.ConnId)) 54 if clientConn == nil { 55 ctx.Connection.Logger().Error("cannot find client connection to update spatial interest", zap.Uint32("clientConnId", msg.ConnId)) 56 return 57 } 58 59 spatialChIds, err := spatialController.QueryChannelIds(msg.Query) 60 if err != nil { 61 ctx.Connection.Logger().Error("error querying spatial channel ids", zap.Error(err)) 62 return 63 } 64 65 channelsToSub := make(map[common.ChannelId]*channeldpb.ChannelSubscriptionOptions) 66 for chId, dist := range spatialChIds { 67 dampSettings := getSpatialDampingSettings(dist) 68 if dampSettings == nil { 69 channelsToSub[chId] = &channeldpb.ChannelSubscriptionOptions{ 70 // DataAccess: Pointer(channeldpb.ChannelDataAccess_NO_ACCESS), 71 FanOutIntervalMs: proto.Uint32(GlobalSettings.GetChannelSettings(channeldpb.ChannelType_SPATIAL).DefaultFanOutIntervalMs), 72 } 73 } else { 74 channelsToSub[chId] = &channeldpb.ChannelSubscriptionOptions{ 75 // DataAccess: Pointer(channeldpb.ChannelDataAccess_NO_ACCESS), 76 FanOutIntervalMs: proto.Uint32(dampSettings.FanOutIntervalMs), 77 DataFieldMasks: dampSettings.DataFieldMasks, 78 } 79 } 80 } 81 82 existingsSubs := make(map[common.ChannelId]*channeldpb.ChannelSubscriptionOptions) 83 clientConn.spatialSubscriptions.Range(func(chId common.ChannelId, subOptions *channeldpb.ChannelSubscriptionOptions) bool { 84 existingsSubs[chId] = subOptions 85 return true 86 }) 87 88 channelsToUnsub := Difference(existingsSubs, channelsToSub) 89 90 for chId := range channelsToUnsub { 91 ctxUnsub := MessageContext{ChannelId: ctx.ChannelId} 92 if ctxUnsub.Channel = GetChannel(chId); ctxUnsub.Channel == nil { 93 continue 94 } 95 ctxUnsub.MsgType = channeldpb.MessageType_UNSUB_FROM_CHANNEL 96 ctxUnsub.Msg = &channeldpb.UnsubscribedFromChannelMessage{ 97 ConnId: msg.ConnId, 98 } 99 ctxUnsub.Connection = clientConn 100 ctxUnsub.StubId = ctx.StubId 101 102 // Make sure the unsub message is handled in the channel's goroutine 103 if ctxUnsub.Channel == ctx.Channel { 104 handleUnsubFromChannel(ctxUnsub) 105 } else { 106 ctxUnsub.Channel.PutMessageContext(ctxUnsub, handleUnsubFromChannel) 107 } 108 } 109 110 for chId, subOptions := range channelsToSub { 111 ctxSub := MessageContext{ChannelId: ctx.ChannelId} 112 if ctxSub.Channel = GetChannel(chId); ctxSub.Channel == nil { 113 continue 114 } 115 ctxSub.MsgType = channeldpb.MessageType_SUB_TO_CHANNEL 116 ctxSub.Msg = &channeldpb.SubscribedToChannelMessage{ 117 ConnId: msg.ConnId, 118 SubOptions: subOptions, 119 } 120 ctxSub.Connection = clientConn 121 122 // Make sure the sub message is handled in the channel's goroutine 123 if ctxSub.Channel == ctx.Channel { 124 handleSubToChannel(ctxSub) 125 } else { 126 ctxSub.Channel.PutMessageContext(ctxSub, handleSubToChannel) 127 } 128 } 129 } 130 131 func handleCreateSpatialChannel(ctx MessageContext, msg *channeldpb.CreateChannelMessage) { 132 if ctx.Connection.GetConnectionType() != channeldpb.ConnectionType_SERVER { 133 ctx.Connection.Logger().Error("illegal attemp to create Spatial channel from client connection") 134 return 135 } 136 137 if spatialController == nil { 138 ctx.Connection.Logger().Error("illegal attemp to create Spatial channel as there's no controller") 139 return 140 } 141 142 channels, err := spatialController.CreateChannels(ctx) 143 if err != nil { 144 ctx.Connection.Logger().Error("failed to create Spatial channel", zap.Error(err)) 145 return 146 } 147 148 resultMsg := &channeldpb.CreateSpatialChannelsResultMessage{ 149 SpatialChannelId: make([]uint32, len(channels)), 150 Metadata: msg.Metadata, 151 OwnerConnId: uint32(ctx.Connection.Id()), 152 } 153 154 for i := range channels { 155 resultMsg.SpatialChannelId[i] = uint32(channels[i].id) 156 } 157 158 ctx.MsgType = channeldpb.MessageType_CREATE_SPATIAL_CHANNEL 159 ctx.Msg = resultMsg 160 ctx.Connection.Send(ctx) 161 // Also send the response to the GLOBAL channel owner. 162 if ownerConn := globalChannel.GetOwner(); ownerConn != nil && ownerConn != ctx.Connection && !ownerConn.IsClosing() { 163 ctx.StubId = 0 164 ownerConn.Send(ctx) 165 } 166 167 for _, newChannel := range channels { 168 // Subscribe to channel after creation 169 cs, _ := ctx.Connection.SubscribeToChannel(newChannel, msg.SubOptions) 170 if cs != nil { 171 ctx.Connection.sendSubscribed(ctx, newChannel, ctx.Connection, 0, &cs.options) 172 } 173 } 174 175 ctx.Connection.Logger().Info("created spatial channel(s)", zap.Uint32s("channelIds", resultMsg.SpatialChannelId)) 176 177 // Send the regions info upon the spatial channels creation 178 regions, err := spatialController.GetRegions() 179 if err != nil { 180 ctx.Connection.Logger().Error("failed to send Spatial regions info upon the spatial channels creation", 181 zap.Uint32s("channelIds", resultMsg.SpatialChannelId)) 182 return 183 } 184 ctx.MsgType = channeldpb.MessageType_SPATIAL_REGIONS_UPDATE 185 ctx.Msg = &channeldpb.SpatialRegionsUpdateMessage{ 186 Regions: regions, 187 } 188 ctx.Connection.Send(ctx) 189 } 190 191 func handleCreateEntityChannel(ctx MessageContext) { 192 // Only the global and spatial channels can create the entity channels 193 if ctx.Channel != globalChannel && ctx.Channel.Type() != channeldpb.ChannelType_SPATIAL { 194 ctx.Connection.Logger().Error("illegal attemp to create entity channel outside the GLOBAL or SPATIAL channels") 195 return 196 } 197 198 msg, ok := ctx.Msg.(*channeldpb.CreateEntityChannelMessage) 199 if !ok { 200 ctx.Connection.Logger().Error("message is not a CreateEntityChannelMessage, will not be handled.") 201 return 202 } 203 204 entityChId := common.ChannelId(msg.EntityId) 205 if entityChId < GlobalSettings.EntityChannelIdStart { 206 ctx.Connection.Logger().Error("illegal attemp to create entity channel with invalid entityId", zap.Uint32("entityId", uint32(entityChId))) 207 return 208 } 209 210 if entityCh := GetChannel(entityChId); entityCh != nil && !entityCh.IsRemoving() { 211 // This could happen when the UE server is restarted but the channeld is not. 212 ctx.Connection.Logger().Warn("illegal attemp to create entity channel with duplicated entityId", zap.Uint32("entityId", uint32(entityChId))) 213 return 214 } 215 216 newChannel := createChannelWithId(entityChId, channeldpb.ChannelType_ENTITY, ctx.Connection) 217 newChannel.Logger().Info("created entity channel", 218 zap.Uint32("ownerConnId", uint32(newChannel.GetOwner().Id())), 219 ) 220 221 newChannel.metadata = msg.Metadata 222 if msg.Data != nil { 223 dataMsg, err := msg.Data.UnmarshalNew() 224 if err != nil { 225 newChannel.Logger().Error("failed to unmarshal data message for the new channel", zap.Error(err)) 226 } else { 227 newChannel.InitData(dataMsg, msg.MergeOptions) 228 } 229 } else { 230 // Channel data should always be initialized 231 newChannel.InitData(nil, msg.MergeOptions) 232 } 233 234 ctx.Msg = &channeldpb.CreateChannelResultMessage{ 235 ChannelType: newChannel.channelType, 236 Metadata: newChannel.metadata, 237 OwnerConnId: uint32(ctx.Connection.Id()), 238 ChannelId: uint32(newChannel.id), 239 } 240 ctx.Connection.Send(ctx) 241 242 // Should we also send the result to the GLOBAL channel owner? 243 244 if msg.IsWellKnown { 245 // Subscribe ALL the connections to the entity channel 246 allConnections.Range(func(_ ConnectionId, conn *Connection) bool { 247 // Ignore the well-known entity for server 248 if conn.GetConnectionType() == channeldpb.ConnectionType_SERVER { 249 return true 250 } 251 /* 252 */ 253 254 // FIXME: different subOptions for different connection? 255 cs, shouldSend := conn.SubscribeToChannel(newChannel, nil) 256 if shouldSend { 257 conn.sendSubscribed(MessageContext{}, newChannel, conn, 0, &cs.options) 258 newChannel.Logger().Debug("subscribed existing connection for the well-known entity", zap.Uint32("connId", uint32(conn.Id()))) 259 } 260 return true 261 }) 262 263 // Add hook to subscribe the new connection to the entity channel 264 Event_AuthComplete.ListenFor(newChannel, func(data AuthEventData) { 265 // Ignore the well-known entity for server 266 if data.Connection.GetConnectionType() == channeldpb.ConnectionType_SERVER { 267 return 268 } 269 270 if data.AuthResult == channeldpb.AuthResultMessage_SUCCESSFUL { 271 // FIXME: different subOptions for different connection? 272 // Add some delay so the client won't have to spawn the entity immediately after the auth. 273 subOptions := &channeldpb.ChannelSubscriptionOptions{FanOutDelayMs: Pointer(int32(1000))} 274 cs, shouldSend := data.Connection.SubscribeToChannel(newChannel, subOptions) 275 if shouldSend { 276 data.Connection.sendSubscribed(MessageContext{}, newChannel, data.Connection, 0, &cs.options) 277 newChannel.Logger().Debug("subscribed new connection for the well-known entity", zap.Uint32("connId", uint32(data.Connection.Id()))) 278 } 279 } 280 }) 281 } 282 283 // Subscribe the owner to channel after creation 284 cs, _ := ctx.Connection.SubscribeToChannel(newChannel, msg.SubOptions) 285 if cs != nil { 286 ctx.Connection.sendSubscribed(ctx, newChannel, ctx.Connection, 0, &cs.options) 287 } 288 289 /* We could sub all the connections in the spatial channel to the entity channel here, 290 * but channeld doesn't know the sub options for each connection. So each connection 291 * should subscribe to the entity channel by itself after received the Spawn message. 292 */ 293 } 294 295 func handleQuerySpatialChannel(ctx MessageContext) { 296 if ctx.Channel != globalChannel { 297 ctx.Connection.Logger().Error("illegal attemp to query spatial channel outside the GLOBAL channel") 298 return 299 } 300 301 msg, ok := ctx.Msg.(*channeldpb.QuerySpatialChannelMessage) 302 if !ok { 303 ctx.Connection.Logger().Error("message is not a QuerySpatialChannelMessage, will not be handled.") 304 return 305 } 306 307 if spatialController == nil { 308 ctx.Connection.Logger().Error("cannot query spatial channel as the spatial controller does not exist") 309 return 310 } 311 312 channelIds := make([]uint32, len(msg.SpatialInfo)) 313 for i, info := range msg.SpatialInfo { 314 channelId, err := spatialController.GetChannelId(common.SpatialInfo{ 315 X: info.X, 316 Y: info.Y, 317 Z: info.Z, 318 }) 319 if err != nil { 320 ctx.Connection.Logger().Warn("failed to GetChannelId", zap.Error(err), 321 zap.Float64("x", info.X), zap.Float64("y", info.Y), zap.Float64("z", info.Z)) 322 } 323 channelIds[i] = uint32(channelId) 324 } 325 326 ctx.Msg = &channeldpb.QuerySpatialChannelResultMessage{ 327 ChannelId: channelIds, 328 } 329 ctx.Connection.Send(ctx) 330 }