package instance import ( "context" "fmt" "git.hlsq.asia/mmorpg/service-common/discover" "git.hlsq.asia/mmorpg/service-common/log" "git.hlsq.asia/mmorpg/service-common/proto/rs/grpc_pb" "git.hlsq.asia/mmorpg/service-common/proto/sc/sc_pb" "git.hlsq.asia/mmorpg/service-common/utils" "git.hlsq.asia/mmorpg/service-scene/internal/grpc_server/stream_client" "git.hlsq.asia/mmorpg/service-scene/internal/npc" "go.uber.org/zap" "google.golang.org/protobuf/proto" "runtime/debug" "sync" "time" ) // Instance 场景类 type Instance struct { wg sync.WaitGroup players map[string]*npc.PlayerNode // 存储所有玩家节点 [usn] ctx context.Context // 停止指令 cancel context.CancelFunc // 停止函数 logger *zap.SugaredLogger // 日志 lastLogicTime int64 // 上次逻辑帧执行时间(毫秒时间戳) SID string // 服务ID InstanceID int32 // 副本ID UniqueNo string // 唯一编号 EventIn chan proto.Message // 消息入口 } // NewScene 初始化场景 func NewScene(sid string, instanceID int32) *Instance { s := &Instance{ players: make(map[string]*npc.PlayerNode), SID: sid, InstanceID: instanceID, UniqueNo: utils.SnowflakeInstance().Generate().String(), EventIn: make(chan proto.Message, 1024), } s.logger = log.GetLogger().Named(fmt.Sprintf("instance %v:%v", s.InstanceID, s.UniqueNo)) s.ctx, s.cancel = context.WithCancel(context.Background()) return s } func (i *Instance) Start(ttl int64) { i.wg.Add(1) go func() { defer i.wg.Done() defer func() { if err := recover(); err != nil { i.logger.Infof("instance err: %v", err) debug.PrintStack() } }() Mgr.Add(i.UniqueNo, i) defer func() { i.logger.Infof("instance destroy") discover.UnRegisterInstance(i.UniqueNo) close(i.EventIn) Mgr.Delete(i.UniqueNo) }() if err := discover.RegisterInstance(i.SID, i.InstanceID, i.UniqueNo, ttl); err != nil { i.logger.Errorf("register discover error") return } // 场景心跳 tick := time.NewTicker(50 * time.Millisecond) i.lastLogicTime = time.Now().UnixMilli() for { select { case <-i.ctx.Done(): return case <-tick.C: now := time.Now().UnixMilli() i.onLogic(now - i.lastLogicTime) i.lastLogicTime = now case e := <-i.EventIn: i.onEvent(e) } } }() i.logger.Infof("new scene start") } // 网络帧,将玩家的操作储存起来,到逻辑帧再处理玩家操作 func (i *Instance) onEvent(e proto.Message) { switch v := e.(type) { case *grpc_pb.EnterReq: i.players[v.USN] = npc.NewPlayerNode(v.GatewaySID, v.USN) case *grpc_pb.ActionReq: if node, ok := i.players[v.USN]; ok { node.AddAction(v) } case *grpc_pb.LeaveReq: delete(i.players, v.USN) } } // 逻辑帧 func (i *Instance) onLogic(delta int64) { positionUpdate := make([]*sc_pb.PositionInfo, 0) sid := "" // 处理玩家指令 for _, node := range i.players { if node.LogicAction(delta) { positionUpdate = append(positionUpdate, &sc_pb.PositionInfo{ USN: node.USN, X: int32(node.Position[0] * 100), Y: int32(node.Position[1] * 100), }) sid = node.GatewaySID } } if len(positionUpdate) > 0 { payload, _ := proto.Marshal(&sc_pb.S2C_Position{ Info: positionUpdate, }) if err := stream_client.SendMessageToGateway(sid, stream_client.FunToClient, &grpc_pb.ToClientReq{ USN: "", MessageID: int32(sc_pb.MessageID_MESSAGE_ID_POSITION), Payload: payload, }); err != nil { i.logger.Errorf("send action err: %v", err) } } }