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  }