All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(common/cool): 更新GetClient函数支持端口参数 更新GetClient函数签名以接收端口参数,并修改客户端映射键的计算方式, 添加GetClientOnly函数用于仅通过uid获取客户端。 fix(common/rpc): 修复RPC调用中的客户端获取方法 将GetClient调用替换为GetClientOnly,确保正确的客户端获取逻辑。 refactor(logic/controller): 重命名Port字段为UID并优化道具列表处理 将Controller结构体中的Port字段重命名为UID以更好地反映其用途, 优化GetUserItemList函数中道具列表的初始化和填充逻辑。 perf(logic): 调整性能分析web服务启动位置 将PprofWeb服务从全局启动移至调试模式下启动,优化服务配置。 refactor(logic/server): 更新服务器UID生成逻辑 修改Maincontroller的UID字段设置方式,使用服务器ID和端口组合生成唯一标识。 refactor(logic/service/player): 移除未使用的导入并优化怪物生成 移除未使用的service导入,优化怪物生成逻辑中的地图数据访问。 feat(logic/service/space): 添加PitS缓存映射并重构空间初始化 添加新的PitS字段
203 lines
6.0 KiB
Go
203 lines
6.0 KiB
Go
// Package controller 提供游戏逻辑的控制器层,负责处理各种游戏功能的请求分发。
|
||
// 包含用户、宠物、战斗、物品、地图、任务等各个模块的控制器方法。
|
||
// 通过反射和标签系统自动注册 cmd 处理方法,实现请求到处理函数的映射。
|
||
package controller
|
||
|
||
import (
|
||
"blazing/cool"
|
||
"blazing/logic/service/common"
|
||
"fmt"
|
||
"strconv"
|
||
|
||
"strings"
|
||
|
||
"bytes"
|
||
"context"
|
||
"reflect"
|
||
|
||
"github.com/gogf/gf/v2/os/glog"
|
||
"github.com/lunixbochs/struc"
|
||
)
|
||
|
||
var Maincontroller = &Controller{} //注入service
|
||
|
||
// Controller 分发cmd逻辑实现
|
||
type Controller struct {
|
||
UID uint32
|
||
RPCClient *struct {
|
||
Kick func(uint32) error
|
||
|
||
RegisterLogic func(uint32, uint32) error
|
||
}
|
||
}
|
||
|
||
// ParseCmd 将字节数组数据解析到指定类型的变量中
|
||
// 该函数使用struc库进行数据解包操作
|
||
// 参数 data: 需要解析的字节数据
|
||
// 返回值: 解析后的指定类型实例
|
||
func ParseCmd[T any](data []byte) T {
|
||
var result T
|
||
// 使用struc.Unpack将字节数据解包到result变量中
|
||
struc.Unpack(bytes.NewBuffer(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.MethodByName(method.Name)
|
||
|
||
// 获取方法第一个参数的类型(请求结构体)
|
||
if methodValue.Type().NumIn() == 0 {
|
||
continue
|
||
}
|
||
|
||
// 解析请求结构体中的cmd标签
|
||
for _, cmd := range getCmd(methodValue.Type().In(0)) {
|
||
if cmd == 0 { // 说明不是有效的注册方法
|
||
glog.Warning(context.Background(), "方法参数必须包含CMD参数", method.Name, "跳过注册")
|
||
continue
|
||
}
|
||
|
||
// 根据服务器类型过滤cmd
|
||
// 登录服务器只处理小于1000的cmd
|
||
if !isGame && cmd > 1000 {
|
||
continue
|
||
}
|
||
|
||
// 游戏服务器只处理大于等于1000的cmd
|
||
if isGame && cmd < 1000 {
|
||
continue
|
||
}
|
||
|
||
// 注册命令处理函数
|
||
|
||
if cool.Config.ServerInfo.IsDebug != 0 {
|
||
fmt.Println("注册方法", cmd, method.Name)
|
||
|
||
}
|
||
|
||
cmdInfo := cool.Cmd{
|
||
Func: methodValue,
|
||
Req: methodValue.Type().In(0).Elem(),
|
||
|
||
// Res: , // TODO 待实现对不同用户初始化方法以取消全局cmdcache
|
||
}
|
||
// 获取req的实际类型(如ReqLogin)
|
||
reqType := reflect.TypeOf(cmdInfo.Req).Elem()
|
||
// 预编译创建req实例的函数:返回结构体指针
|
||
cmdInfo.NewReqFunc = func() interface{} {
|
||
return reflect.New(reqType).Interface()
|
||
}
|
||
cool.CmdCache[cmd] = cmdInfo
|
||
// if exists { // 方法已存在
|
||
// glog.Error(context.Background(), "命令处理方法已存在,跳过注册", cmd, method.Name)
|
||
// }
|
||
}
|
||
}
|
||
}
|
||
|
||
var targetType = reflect.TypeOf(common.TomeeHeader{})
|
||
|
||
// 默认返回值(无匹配字段/解析失败时)
|
||
const defaultCmdValue = 0
|
||
|
||
// getCmd 从结构体类型中提取绑定的cmd指令(递归查找嵌套结构体,支持值/指针类型的TomeeHeader)
|
||
// 参数 typ: 待解析的结构体类型(支持多层指针)
|
||
// 返回值: 解析到的cmd切片,无匹配/解析失败时返回[defaultCmdValue]
|
||
func getCmd(typ reflect.Type) []uint32 {
|
||
// 递归解引用所有指针类型(处理 *struct、**struct 等场景)
|
||
for typ.Kind() == reflect.Ptr {
|
||
typ = typ.Elem()
|
||
}
|
||
|
||
// 非结构体类型直接返回默认值
|
||
if typ.Kind() != reflect.Struct {
|
||
return []uint32{defaultCmdValue}
|
||
}
|
||
|
||
// 遍历结构体字段,查找TomeeHeader字段并解析cmd
|
||
for i := 0; i < typ.NumField(); i++ {
|
||
field := typ.Field(i)
|
||
|
||
// 尝试解析当前字段的cmd标签
|
||
cmdSlice, err := parseCmdTagWithStructField(field)
|
||
if err == nil { // 解析成功,直接返回结果
|
||
return cmdSlice
|
||
}
|
||
|
||
// 递归处理嵌套结构体(值/指针类型)
|
||
nestedTyp := field.Type
|
||
if nestedTyp.Kind() == reflect.Ptr {
|
||
nestedTyp = nestedTyp.Elem()
|
||
}
|
||
if nestedTyp.Kind() == reflect.Struct {
|
||
// 递归查找,找到有效cmd则立即返回
|
||
if nestedCmd := getCmd(nestedTyp); len(nestedCmd) > 0 && nestedCmd[0] != defaultCmdValue {
|
||
return nestedCmd
|
||
}
|
||
}
|
||
}
|
||
|
||
// 未找到目标字段/所有解析失败,返回默认值
|
||
return []uint32{defaultCmdValue}
|
||
}
|
||
|
||
// parseCmdTagWithStructField 校验字段是否为TomeeHeader(值/指针)并解析cmd标签
|
||
// 参数 field: 结构体字段元信息
|
||
// 返回值: 解析后的cmd切片,非目标类型/解析失败返回错误
|
||
func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, error) {
|
||
// 判断字段类型是否为 TomeeHeader 或 *TomeeHeader
|
||
var isTomeeHeader bool
|
||
switch {
|
||
case field.Type == targetType: // 值类型
|
||
isTomeeHeader = true
|
||
case field.Type.Kind() == reflect.Ptr && field.Type.Elem() == targetType: // 指针类型
|
||
isTomeeHeader = true
|
||
default:
|
||
isTomeeHeader = false
|
||
}
|
||
|
||
// 非目标类型返回错误
|
||
if !isTomeeHeader {
|
||
return nil, fmt.Errorf("field %s (type: %v) is not common.TomeeHeader or *common.TomeeHeader",
|
||
field.Name, field.Type)
|
||
}
|
||
|
||
// 提取cmd标签
|
||
cmdStr := field.Tag.Get("cmd")
|
||
if cmdStr == "" {
|
||
return nil, fmt.Errorf("field %s cmd tag is empty", field.Name)
|
||
}
|
||
|
||
// 高性能解析标签为uint32切片(替代gconv,减少第三方依赖且可控)
|
||
parts := strings.Split(cmdStr, "|")
|
||
result := make([]uint32, 0, len(parts))
|
||
for idx, s := range parts {
|
||
// 去除空白字符(兼容标签中意外的空格)
|
||
s = strings.TrimSpace(s)
|
||
if s == "" {
|
||
return nil, fmt.Errorf("field %s cmd tag part %d is empty", field.Name, idx)
|
||
}
|
||
|
||
// 手动解析uint32,比gconv更可控,避免隐式转换问题
|
||
num, err := strconv.ParseUint(s, 10, 32)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)",
|
||
field.Name, idx, err, s)
|
||
}
|
||
result = append(result, uint32(num))
|
||
}
|
||
|
||
return result, nil
|
||
}
|