美文网首页
Golang 游戏leaf系列(九) 官方issues摘录

Golang 游戏leaf系列(九) 官方issues摘录

作者: 合肥黑 | 来源:发表于2019-05-31 17:08 被阅读0次

    在查看官方issues时,也发现了一些个人觉得有用的讨论,摘录一下

    一、issues 希望可以增强下log的功能

    Q:leaf的log蛮好用的了,可以log到文本。但是现在需要做日志分析。要能支持json格式,异步log等等。希望可以加强下。
    看了下zap日志库,感觉过去复杂了。有没有简单实用,能分析的日志库?
    A:Leaf 的 log 的定位并非用于数据分析,最好额外加一套用于数据分析的机制,同时也保留 Leaf 的 log。

    二、issues 服务端崩溃

    zcwtop:请问下,你是如何设计“一局游戏开一个Goroutine”的。能否给个简单的demo或者思路。
    我自己写,一局游戏基本上是每个操作都放在goroutine里运行的,用的是leaf的顺序goroutine。这样感觉goroutine会非常多,不知道这样设计是不是错了?

    exfun:为什么每个操作都要单独开goroutine。我之前就是开太多了,几十局游戏开运行完全跑不出问题。跑几万的次数就崩溃,这就是我开个issue求助的原因,找BUG找半天。我听了作者了。尽量少开,一局游戏的逻辑操作都放一个goroutine里执行,数据同步放到另外一个goroutine专门处理,我们用的Postgress。这样防止并发下未知的RACE。简单点好

    zcwtop:是啊。我现在感觉是太多了。我现在的设计是这样的,假如一局游戏的结构是

    type T struct {
        *g // leaf 平行的go
        ...
    }
    
    func (t *T) add {
        t.Go(func() {
            // ...
        }, func(){
            //...
        })
    }
    
    func (t *T) del {
        t.Go(func() {
            // ...
        }, func(){
            //...
        })
    }
    

    基本上每个操作都是这样。如果改成一个go运行,我该如何改? 或者你是如何写的,能否给个参考。

    exfun:我没有用leaf这个,用go 关键字声明的。leaf的只用了它的消息和工具类这些。我是一局游戏比如game *Game, 然后会在一个goroutinue里面执行,比如 go game.Process() , 然后在process里面完成单局所有的事情。游戏局完了之后,这个goroutine就会执行完,等待GC回收。

    zcwtop:那是不是这个Process是一个死循环,用select多路复用等待通道消息,然后执行。我理解是不是类似这样处理:

    func (g *Game) Process(...) {
        begin()
        //...
    
        for {
            select {
            case c:=<-通道1:
                handle1()
            case c := <-通道2:
                handle2()
            }
        }
    
        //...
    
        end()
    }
    func (g *Game)handle1() {
        //...
    }
    func (g *Game)handle2() {
        //...
    }
    

    exfun:写好点应该是这样的,leaf负责消息转发chan通知,来了就处理,注意游戏结束条件。别一直结束不了卡住,这样就会有问题。超时的地方得处理!我们是在线答题的游戏,比较简单。

    zcwtop:人多的时候一定要注意别资源竞争(多核下就很容易崩溃),要么用锁,原子,条件变量控制。或者用chan来传递消息。以下是我一个Game一个goroutine来控制游戏过程的流程Process() 。重构后的代码,比之前清晰多了,这也是我第一次写golang,还需要学习的东西很多。

    func (GR *GameRoom) Process() {
        // 处理游戏过程结束/正常/异常
        defer func() {
            GR.End()
            if e := recover(); e != nil {
                panic(e)
            }
        }()
        GR.Playing = true
        gameStartTime := conf.GameConfig.StartTime
        robotTime := conf.GameConfig.RobotTime
    Playing:
        for {
            select {
            case m := <-GR.MsgChan:
                player := m.P
                switch m.Mtype {
                case MSG_JOIN:
                    GR.onJoin(player)
                case MSG_ANSWER:
                    GR.onAnswer(player, m.Answer, m.Pm)
                }
            case wf := <-GR.WaitChan:
                switch wf {
                case WAIT_GAME:
                    // 游戏开始倒计时,补充一部分AI
                    joinRobots(GR, utils.RandIntFrom2(18, 25))
                    // 等待是否补充足房间
                    time.AfterFunc(time.Second*time.Duration(robotTime), func() {
                        GR.WaitChan <- ON_RO_JOIN
                    })
                case ON_RO_JOIN:
                    // 不足AI
                    joinRobots(GR, 0)
                    // 剩余倒计时
                    time.AfterFunc(time.Second*time.Duration(gameStartTime-robotTime), func() {
                        GR.WaitChan <- ON_GAME_START
                    })
                case ON_GAME_START:
                    // 游戏开始
                    GR.Start()
                case ON_RO_ANS_1:
                    // 机器人第一次回答
                    helpRobotAnswer(GR, true, false)
                    time.AfterFunc(time.Second*time.Duration(1), func() {
                        GR.WaitChan <- ON_RO_ANS_2
                    })
                case ON_RO_ANS_2:
                    // 机器人第二次回答
                    helpRobotAnswer(GR, false, false)
                    time.AfterFunc(time.Second*time.Duration(1), func() {
                        GR.WaitChan <- ON_RO_ANS_3
                    })
                case ON_RO_ANS_3:
                    helpRobotAnswer(GR, false, true)
                    // 倒计时回答结束
                    time.AfterFunc(time.Second*time.Duration(GR.AnswerTimeout-4), func() {
                        GR.WaitChan <- ON_ANSWER_END
                    })
                case ON_ANSWER_END: // 回答结束,结算
                    GR.onAnswerOver()
                case ON_SEND_QUESTION: // 出题
                    GR.sendQuestion()
                case MSG_END_GAME: // 游戏结束
                    //break game
                    break Playing
                    // goto END
                }
            }
        }
        //END:
        log.Debug("Game goroutine over ->%s", GR.Uuid)
    }
    
    三、Leaf 中如何做消息广播

    游戏服务器一定需要用户管理,最常见的方式就是建立用户 ID 到用户实例的映射关系(还有可能存在用户帐号 ID、用户名到用户实例的映射关系)。例如:

    users = make(map[int]*User)
    

    User 本身为了简单,可以直接组合 Agent:

    type User struct {
        gate.Agent
    }
    

    这样的话,广播消息就是:

    for _, user := range users {
        user.WriteMsg(msg)
    }
    

    一个最简单的广播的例子:打开 Leafserver game/internel/chanrpc.go 文件,加入一个全局变量。

    var agents = make(map[gate.Agent]struct{})
    agents 的管理:
    
    // agent 被创建时
    func rpcNewAgent(args []interface{}) {
        a := args[0].(gate.Agent)
        agents[a] = struct{}{}
    }
    
    // agent 被关闭时
    func rpcCloseAgent(args []interface{}) {
        a := args[0].(gate.Agent)
        delete(agents, a)
    }
    由此可见 agents 中保存了当前所有连接,广播的处理:
    
    for a := range agents {
        a.WriteMsg(msg)
    }
    

    相关文章

      网友评论

          本文标题:Golang 游戏leaf系列(九) 官方issues摘录

          本文链接:https://www.haomeiwen.com/subject/evgwtctx.html