diff --git a/pkg/channeld/event.go b/pkg/channeld/event.go index 4d6ec37..c7a916c 100644 --- a/pkg/channeld/event.go +++ b/pkg/channeld/event.go @@ -23,6 +23,13 @@ var Event_AuthComplete = &Event[AuthEventData]{} var Event_FsmDisallowed = &Event[*Connection]{} +type EntityChannelSpatiallyOwnedEventData struct { + EntityChannel *Channel + SpatialChanel *Channel +} + +var Event_EntityChannelSpatiallyOwned = &Event[EntityChannelSpatiallyOwnedEventData]{} + type EventData interface { } diff --git a/pkg/channeld/message_spatial.go b/pkg/channeld/message_spatial.go index 9038747..9298a09 100644 --- a/pkg/channeld/message_spatial.go +++ b/pkg/channeld/message_spatial.go @@ -225,6 +225,46 @@ func handleCreateEntityChannel(ctx MessageContext) { newChannel.Logger().Error("failed to unmarshal data message for the new channel", zap.Error(err)) } else { newChannel.InitData(dataMsg, msg.MergeOptions) + + // If the entity channel is created from GLOBAL channel(master server), but its data contains spatial info, + // we should set the owner of the entity channel to the spatial channel's. + if ctx.Channel == globalChannel { + dataMsgWithSpatialInfo, ok := dataMsg.(EntityChannelDataWithSpatialInfo) + if ok { + spatialInfo := dataMsgWithSpatialInfo.GetSpatialInfo() + if spatialInfo != nil { + spatialChId, err := spatialController.GetChannelId(*spatialInfo) + if err != nil { + ctx.Connection.Logger().Error("failed to set the entity channel owner to the spatial channel's", zap.Error(err)) + } else { + spatialCh := GetChannel(spatialChId) + if spatialCh == nil { + newChannel.Logger().Error("failed to set the entity channel owner as the spatial channel does not exist", + zap.Uint32("spatialChId", uint32(spatialChId))) + } else { + ownerConn := spatialCh.GetOwner() + if ownerConn != nil && !ownerConn.IsClosing() { + newChannel.SetOwner(ownerConn) + newChannel.Logger().Info("set the entity channel owner to the spatial channel's", + zap.Uint32("spatialChId", uint32(spatialChId))) + + Event_EntityChannelSpatiallyOwned.Broadcast(EntityChannelSpatiallyOwnedEventData{newChannel, spatialCh}) + + // Sub-and-send happens at the end of this function + + // Set the messge context so that the CreateChannelResultMessage will be sent to the owner + // instead of the message sender (the master server). + ctx.Connection = ownerConn + ctx.ChannelId = uint32(spatialChId) + } else { + newChannel.Logger().Warn("the entity's owning spatial channel does not have an owner connection", + zap.Uint32("spatialChId", uint32(spatialChId))) + } + } + } + } + } + } } } else { // Channel data should always be initialized diff --git a/pkg/channeld/spatial.go b/pkg/channeld/spatial.go index 68b9d6e..17fe2ee 100644 --- a/pkg/channeld/spatial.go +++ b/pkg/channeld/spatial.go @@ -380,6 +380,10 @@ func (ctl *StaticGrid2DSpatialController) GetAdjacentChannels(spatialChannelId c return channelIds, nil } +type EntityChannelDataWithSpatialInfo interface { + GetSpatialInfo() *common.SpatialInfo +} + func (ctl *StaticGrid2DSpatialController) CreateChannels(ctx MessageContext) ([]*Channel, error) { ctl.initServerConnections() serverIndex := ctl.nextServerIndex() @@ -461,6 +465,14 @@ func (ctl *StaticGrid2DSpatialController) CreateChannels(ctx MessageContext) ([] Msg: readyMsg, }) } + + // ...and the master server too. + if globalChannel.ownerConnection != nil { + globalChannel.ownerConnection.Send(MessageContext{ + MsgType: channeldpb.MessageType_SPATIAL_CHANNELS_READY, + Msg: readyMsg, + }) + } } return channels, nil diff --git a/pkg/unreal/message.go b/pkg/unreal/message.go index b446d06..da4a709 100644 --- a/pkg/unreal/message.go +++ b/pkg/unreal/message.go @@ -12,6 +12,8 @@ import ( func InitMessageHandlers() { channeld.RegisterMessageHandler(uint32(unrealpb.MessageType_SPAWN), &channeldpb.ServerForwardMessage{}, handleUnrealSpawnObject) channeld.RegisterMessageHandler(uint32(unrealpb.MessageType_DESTROY), &channeldpb.ServerForwardMessage{}, handleUnrealDestroyObject) + + channeld.Event_EntityChannelSpatiallyOwned.Listen(handleEntityChannelSpatiallyOwned) } // Executed in the spatial channels or the GLOBAL channel (no-spatial scenario) @@ -49,12 +51,7 @@ func handleUnrealSpawnObject(ctx channeld.MessageContext) { // Update the message's spatial channelId based on the actor's location oldChId := *spawnMsg.ChannelId if spawnMsg.Location != nil { - // Swap the Y and Z as UE uses the Z-Up rule but channeld uses the Y-up rule. - spatialChId, err := channeld.GetSpatialController().GetChannelId(common.SpatialInfo{ - X: float64(*spawnMsg.Location.X), - Y: float64(*spawnMsg.Location.Z), - Z: float64(*spawnMsg.Location.Y), - }) + spatialChId, err := channeld.GetSpatialController().GetChannelId(*spawnMsg.Location.ToSpatialInfo()) if err != nil { ctx.Connection.Logger().Warn("failed to GetChannelId", zap.Error(err), zap.Float32("x", *spawnMsg.Location.X), @@ -121,6 +118,8 @@ type UnrealObjectEntityData interface { SetObjRef(objRef *unrealpb.UnrealObjectRef) } +// Add the SpatialEntityState to the spatial channel data. If an entity doesn't exist in the spatial channel data, +// handover will not work properly. func addSpatialEntity(ch *channeld.Channel, objRef *unrealpb.UnrealObjectRef) { if ch.Type() != channeldpb.ChannelType_SPATIAL { return @@ -188,3 +187,15 @@ func handleUnrealDestroyObject(ctx channeld.MessageContext) { channeld.RemoveChannel(entityCh) } } + +func handleEntityChannelSpatiallyOwned(data channeld.EntityChannelSpatiallyOwnedEventData) { + dataMsgWithObjRef, ok := data.EntityChannel.GetDataMessage().(unrealpb.EntityChannelDataWithObjRef) + if !ok { + data.EntityChannel.Logger().Error("spatial-owned entity channel data doesn't implement EntityChannelDataWithObjRef") + return + } + + data.SpatialChanel.Execute(func(ch *channeld.Channel) { + addSpatialEntity(ch, dataMsgWithObjRef.GetObjRef()) + }) +} diff --git a/pkg/unrealpb/extension.go b/pkg/unrealpb/extension.go index 465b3a1..c2272f4 100644 --- a/pkg/unrealpb/extension.go +++ b/pkg/unrealpb/extension.go @@ -8,6 +8,21 @@ import ( "github.com/metaworking/channeld/pkg/common" ) +func (vec *FVector) ToSpatialInfo() *common.SpatialInfo { + info := &common.SpatialInfo{} + if vec.X != nil { + info.X = float64(*vec.X) + } + // Swap the Y and Z as UE uses the Z-Up rule but channeld uses the Y-up rule. + if vec.Y != nil { + info.Z = float64(*vec.Y) + } + if vec.Z != nil { + info.Y = float64(*vec.Z) + } + return info +} + // Implement [channeld.HandoverDataWithPayload] func (data *HandoverData) ClearPayload() { data.ChannelData = nil diff --git a/pkg/unrealpb/unreal_common.proto b/pkg/unrealpb/unreal_common.proto index e9c1353..2900458 100644 --- a/pkg/unrealpb/unreal_common.proto +++ b/pkg/unrealpb/unreal_common.proto @@ -110,7 +110,7 @@ message HandoverContext { } // Wrapped in ChannelDataHandoverMessage.data -// Implements [channeld.HandoverDataPayload] +// Implements [channeld.HandoverDataWithPayload] message HandoverData { repeated HandoverContext context = 1; // The channel data message that will be handed over to the destination channel.