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

     1  package unreal
     2  
     3  import (
     4  	"github.com/metaworking/channeld/pkg/channeld"
     5  	"github.com/metaworking/channeld/pkg/channeldpb"
     6  	"github.com/metaworking/channeld/pkg/common"
     7  	"github.com/metaworking/channeld/pkg/unrealpb"
     8  	"go.uber.org/zap"
     9  	"google.golang.org/protobuf/proto"
    10  )
    11  
    12  func InitMessageHandlers() {
    13  	channeld.RegisterMessageHandler(uint32(unrealpb.MessageType_SPAWN), &channeldpb.ServerForwardMessage{}, handleUnrealSpawnObject)
    14  	channeld.RegisterMessageHandler(uint32(unrealpb.MessageType_DESTROY), &channeldpb.ServerForwardMessage{}, handleUnrealDestroyObject)
    15  }
    16  
    17  // Executed in the spatial channels or the GLOBAL channel (no-spatial scenario)
    18  func handleUnrealSpawnObject(ctx channeld.MessageContext) {
    19  	// server -> channeld -> client
    20  	msg, ok := ctx.Msg.(*channeldpb.ServerForwardMessage)
    21  	if !ok {
    22  		ctx.Connection.Logger().Error("message is not a ServerForwardMessage, will not be handled.")
    23  		return
    24  	}
    25  
    26  	spawnMsg := &unrealpb.SpawnObjectMessage{}
    27  	err := proto.Unmarshal(msg.Payload, spawnMsg)
    28  	if err != nil {
    29  		ctx.Connection.Logger().Error("failed to unmarshal SpawnObjectMessage")
    30  		return
    31  	}
    32  
    33  	if spawnMsg.Obj == nil {
    34  		ctx.Connection.Logger().Error("SpawnObjectMessage doesn't have the 'Obj' field")
    35  		return
    36  	}
    37  
    38  	if spawnMsg.Obj.NetGUID == nil || *spawnMsg.Obj.NetGUID == 0 {
    39  		ctx.Connection.Logger().Error("invalid NetGUID in SpawnObjectMessage")
    40  		return
    41  	}
    42  
    43  	/*
    44  		if len(spawnMsg.Obj.Context) == 0 {
    45  			ctx.Connection.Logger().Warn("empty context in SpawnObjectMessage", zap.Uint32("netId", *spawnMsg.Obj.NetGUID))
    46  		}
    47  	*/
    48  
    49  	// Update the message's spatial channelId based on the actor's location
    50  	oldChId := *spawnMsg.ChannelId
    51  	if spawnMsg.Location != nil {
    52  		// Swap the Y and Z as UE uses the Z-Up rule but channeld uses the Y-up rule.
    53  		spatialChId, err := channeld.GetSpatialController().GetChannelId(common.SpatialInfo{
    54  			X: float64(*spawnMsg.Location.X),
    55  			Y: float64(*spawnMsg.Location.Z),
    56  			Z: float64(*spawnMsg.Location.Y),
    57  		})
    58  		if err != nil {
    59  			ctx.Connection.Logger().Warn("failed to GetChannelId", zap.Error(err),
    60  				zap.Float32("x", *spawnMsg.Location.X),
    61  				zap.Float32("y", *spawnMsg.Location.Y),
    62  				zap.Float32("z", *spawnMsg.Location.Z))
    63  			return
    64  		}
    65  		*spawnMsg.ChannelId = uint32(spatialChId)
    66  		if *spawnMsg.ChannelId != oldChId {
    67  			newPayload, err := proto.Marshal(spawnMsg)
    68  			if err == nil {
    69  				msg.Payload = newPayload
    70  				// Update the channel and let the new channel handle the message. Otherwise race conditions may happen.
    71  				ctx.Channel = channeld.GetChannel(spatialChId)
    72  				if ctx.Channel != nil {
    73  					ctx.Channel.Execute(func(ch *channeld.Channel) {
    74  						addSpatialEntity(ch, spawnMsg.Obj)
    75  					})
    76  					ctx.Channel.PutMessageContext(ctx, channeld.HandleServerToClientUserMessage)
    77  				} else {
    78  					ctx.Connection.Logger().Error("failed to handle the ServerForwardMessage as the new spatial channel doesn't exist", zap.Uint32("newChId", *spawnMsg.ChannelId))
    79  				}
    80  			} else {
    81  				ctx.Connection.Logger().Error("failed to marshal the new payload")
    82  			}
    83  		} else {
    84  			// ChannelId is not updated; handle the forward message in current channel.
    85  			addSpatialEntity(ctx.Channel, spawnMsg.Obj)
    86  			channeld.HandleServerToClientUserMessage(ctx)
    87  		}
    88  	} else {
    89  		addSpatialEntity(ctx.Channel, spawnMsg.Obj)
    90  		channeld.HandleServerToClientUserMessage(ctx)
    91  	}
    92  
    93  	/*
    94  		defer allSpawnedObjLock.Unlock()
    95  		allSpawnedObjLock.Lock()
    96  		allSpawnedObj[*spawnMsg.Obj.NetGUID] = spawnMsg.Obj
    97  		channeld.RootLogger().Debug("stored UnrealObjectRef from spawn message",
    98  			zap.Uint32("netId", *spawnMsg.Obj.NetGUID),
    99  			zap.Uint32("oldChId", oldChId),
   100  			zap.Uint32("newChId", *spawnMsg.ChannelId),
   101  		)
   102  	*/
   103  
   104  	// Entity channel should already be created by the spatial server.
   105  	entityChannel := channeld.GetChannel(common.ChannelId(*spawnMsg.Obj.NetGUID))
   106  	if entityChannel == nil {
   107  		return
   108  	}
   109  
   110  	// Set the objRef of the entity channel's data
   111  	entityChannel.Execute(func(ch *channeld.Channel) {
   112  		if entityData, ok := ch.GetDataMessage().(UnrealObjectEntityData); ok {
   113  			entityData.SetObjRef(spawnMsg.Obj)
   114  			ch.Logger().Debug("set entity data's objRef")
   115  		}
   116  	})
   117  }
   118  
   119  // Entity channel data that contains an UnrealObjectRef should implement this interface.
   120  type UnrealObjectEntityData interface {
   121  	SetObjRef(objRef *unrealpb.UnrealObjectRef)
   122  }
   123  
   124  func addSpatialEntity(ch *channeld.Channel, objRef *unrealpb.UnrealObjectRef) {
   125  	if ch.Type() != channeldpb.ChannelType_SPATIAL {
   126  		return
   127  	}
   128  
   129  	if ch.GetDataMessage() == nil {
   130  		return
   131  	}
   132  
   133  	spatialChannelData, ok := ch.GetDataMessage().(*unrealpb.SpatialChannelData)
   134  	if !ok {
   135  		ch.Logger().Warn("channel data is not a SpatialChannelData",
   136  			zap.String("dataType", string(ch.GetDataMessage().ProtoReflect().Descriptor().FullName())))
   137  		return
   138  	}
   139  
   140  	entityState := &unrealpb.SpatialEntityState{ObjRef: &unrealpb.UnrealObjectRef{}}
   141  	proto.Merge(entityState.ObjRef, objRef)
   142  	spatialChannelData.Entities[*objRef.NetGUID] = entityState
   143  	ch.Logger().Debug("added spatial entity", zap.Uint32("netId", *objRef.NetGUID))
   144  }
   145  
   146  func removeSpatialEntity(ch *channeld.Channel, netId uint32) {
   147  	if ch.Type() != channeldpb.ChannelType_SPATIAL {
   148  		return
   149  	}
   150  
   151  	if ch.GetDataMessage() == nil {
   152  		return
   153  	}
   154  
   155  	spatialChannelData, ok := ch.GetDataMessage().(*unrealpb.SpatialChannelData)
   156  	if !ok {
   157  		ch.Logger().Warn("channel data is not a SpatialChannelData",
   158  			zap.String("dataType", string(ch.GetDataMessage().ProtoReflect().Descriptor().FullName())))
   159  		return
   160  	}
   161  
   162  	delete(spatialChannelData.Entities, netId)
   163  	ch.Logger().Debug("removed spatial entity", zap.Uint32("netId", netId))
   164  }
   165  
   166  func handleUnrealDestroyObject(ctx channeld.MessageContext) {
   167  	// server -> channeld -> client
   168  	msg, ok := ctx.Msg.(*channeldpb.ServerForwardMessage)
   169  	if !ok {
   170  		ctx.Connection.Logger().Error("message is not a ServerForwardMessage, will not be handled.")
   171  		return
   172  	}
   173  
   174  	destroyMsg := &unrealpb.DestroyObjectMessage{}
   175  	err := proto.Unmarshal(msg.Payload, destroyMsg)
   176  	if err != nil {
   177  		ctx.Connection.Logger().Error("failed to unmarshal DestroyObjectMessage")
   178  		return
   179  	}
   180  
   181  	removeSpatialEntity(ctx.Channel, destroyMsg.NetId)
   182  	// Send/broadcast the message
   183  	channeld.HandleServerToClientUserMessage(ctx)
   184  
   185  	entityCh := channeld.GetChannel(common.ChannelId(destroyMsg.NetId))
   186  	if entityCh != nil {
   187  		entityCh.Logger().Info("removing entity channel from unrealpb.DestroyObjectMessage")
   188  		channeld.RemoveChannel(entityCh)
   189  	}
   190  }