// Package controller 提供游戏逻辑的控制器层,负责处理各种游戏功能的请求分发。 // 包含用户、宠物、战斗、物品、地图、任务等各个模块的控制器方法。 // 通过反射和标签系统自动注册 cmd 处理方法,实现请求到处理函数的映射。 package controller import ( "blazing/common/rpc" "blazing/cool" "blazing/logic/service/common" "bytes" "context" "fmt" "reflect" "strconv" "strings" "sync" "github.com/gogf/gf/v2/os/glog" "github.com/lunixbochs/struc" "github.com/panjf2000/gnet/v2" ) // Maincontroller 是控制器层共享变量。 var Maincontroller = &Controller{} //注入service // Controller 分发cmd逻辑实现 type Controller struct { UID uint32 RPCClient *struct { Kick func(uint32) error RegisterLogic func(uint32, uint32) error MatchJoinOrUpdate func(rpc.PVPMatchJoinPayload) error MatchCancel func(uint32) error } } // ParseCmd 将字节数组数据解析到指定类型的变量中 // 该函数使用struc库进行数据解包操作 // 参数 data: 需要解析的字节数据 // 返回值: 解析后的指定类型实例 func ParseCmd[T any](data []byte) T { var result T // 使用struc.Unpack将字节数据解包到result变量中 struc.Unpack(bytes.NewReader(data), &result) return result } // Init 初始化控制器,注册所有cmd处理方法 // 参数 isGame: 标识是否为游戏服务器(true)或登录服务器(false) func Init(isGame bool) { controllerValue := reflect.ValueOf(Maincontroller) controllerType := controllerValue.Type() for i := 0; i < controllerType.NumMethod(); i++ { method := controllerType.Method(i) methodValue := controllerValue.Method(i) methodType := methodValue.Type() if methodType.NumIn() == 0 { continue } reqArgType := methodType.In(0) if reqArgType.Kind() != reflect.Ptr || reqArgType.Elem().Kind() != reflect.Struct { glog.Warning(context.Background(), "方法首参必须为结构体指针", method.Name, "跳过注册") continue } reqType := reqArgType.Elem() binding := getCmdBinding(reqType) for _, cmd := range binding.cmds { if cmd == 0 { glog.Warning(context.Background(), "方法参数必须包含CMD参数", method.Name, "跳过注册") continue } if methodType.NumIn() != 2 { glog.Warning(context.Background(), "方法参数数量必须为2", method.Name, "跳过注册") continue } if !isGame && cmd > 1000 { continue } if isGame && cmd < 1000 { continue } if cool.Config.ServerInfo.IsDebug != 0 { fmt.Println("注册方法", cmd, method.Name) } reqTypeForNew := reqType cmdInfo := cool.Cmd{ Func: methodValue, Req: reqType, HeaderFieldIndex: append([]int(nil), binding.headerFieldIndex...), UseConn: methodType.In(1) == connType, NewReqFunc: func() interface{} { return reflect.New(reqTypeForNew).Interface() }, NewReqValue: func() reflect.Value { return reflect.New(reqTypeForNew) }, } if _, exists := cool.CmdCache[cmd]; exists { panic(fmt.Sprintf("命令处理方法已存在,跳过注册 %d %s", cmd, method.Name)) } cool.CmdCache[cmd] = cmdInfo } } } var ( targetType = reflect.TypeOf(common.TomeeHeader{}) connType = reflect.TypeOf((*gnet.Conn)(nil)).Elem() cmdTypeCache sync.Map ) // 默认返回值(无匹配字段/解析失败时) const defaultCmdValue = 0 type cmdBinding struct { cmds []uint32 headerFieldIndex []int } func normalizeStructType(typ reflect.Type) reflect.Type { for typ.Kind() == reflect.Ptr { typ = typ.Elem() } return typ } // getCmdBinding 从结构体类型中提取绑定的cmd指令和头字段位置。 func getCmdBinding(typ reflect.Type) cmdBinding { typ = normalizeStructType(typ) if cached, ok := cmdTypeCache.Load(typ); ok { return cached.(cmdBinding) } if typ.Kind() != reflect.Struct { binding := cmdBinding{cmds: []uint32{defaultCmdValue}} cmdTypeCache.Store(typ, binding) return binding } if binding, ok := findCmdBinding(typ, make(map[reflect.Type]struct{})); ok { cmdTypeCache.Store(typ, binding) return binding } binding := cmdBinding{cmds: []uint32{defaultCmdValue}} cmdTypeCache.Store(typ, binding) return binding } func findCmdBinding(typ reflect.Type, visiting map[reflect.Type]struct{}) (cmdBinding, bool) { typ = normalizeStructType(typ) if typ.Kind() != reflect.Struct { return cmdBinding{}, false } if _, seen := visiting[typ]; seen { return cmdBinding{}, false } visiting[typ] = struct{}{} defer delete(visiting, typ) for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) cmdSlice, isHeader, err := parseCmdTagWithStructField(field) if isHeader && err == nil { return cmdBinding{ cmds: cmdSlice, headerFieldIndex: append([]int(nil), field.Index...), }, true } nestedTyp := normalizeStructType(field.Type) if nestedTyp.Kind() != reflect.Struct { continue } nestedBinding, ok := findCmdBinding(nestedTyp, visiting) if !ok { continue } fieldIndex := make([]int, 0, len(field.Index)+len(nestedBinding.headerFieldIndex)) fieldIndex = append(fieldIndex, field.Index...) fieldIndex = append(fieldIndex, nestedBinding.headerFieldIndex...) nestedBinding.headerFieldIndex = fieldIndex return nestedBinding, true } return cmdBinding{}, false } // parseCmdTagWithStructField 校验字段是否为TomeeHeader(值/指针)并解析cmd标签 // 参数 field: 结构体字段元信息 // 返回值: 解析后的cmd切片,是否为目标类型,解析失败错误 func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, bool, error) { if field.Type != targetType && !(field.Type.Kind() == reflect.Ptr && field.Type.Elem() == targetType) { return nil, false, nil } cmdStr := field.Tag.Get("cmd") if cmdStr == "" { return nil, true, fmt.Errorf("field %s cmd tag is empty", field.Name) } result := make([]uint32, 0, strings.Count(cmdStr, "|")+1) remain := cmdStr for idx := 0; ; idx++ { part, next, found := strings.Cut(remain, "|") s := strings.TrimSpace(part) if s == "" { return nil, true, fmt.Errorf("field %s cmd tag part %d is empty", field.Name, idx) } num, err := strconv.ParseUint(s, 10, 32) if err != nil { return nil, true, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)", field.Name, idx, err, s) } result = append(result, uint32(num)) if !found { break } remain = next } return result, true, nil }