• Libuv实现帧率控制


    Libuv实现帧率控制

    概念

      服务端帧率控制,保证在一段固定的时间内执行完所有事情(包括网络I/O等),如果有空余时间,那么我们Sleep等待一段时间。如果超时我们需要追帧

    注意点

    1. 只要在程序中只有一个进程的情况下控制服务器的帧率,那么我们在Libuv中注册的定时器永远会存在一定的误差且误差时间无法确定。

    2. 在Libuv中的idle、prepare、check句柄队列中控制帧率,会无法有效的计算一次Loop循环所耗费的具体时间。因为我们只能依靠loop提供的时间,通过uv_now方法拿到本次循环(uv_run)即将执行的时间,但是由于loop提供的时间每次都未考虑定时器队列和pending队列的执行耗时,所以我们在idle、prepare、check句柄中计算每帧的耗时,是根本不精确的。

    效果图
    效果图

    1. 在定时器队列中控制帧率,首先不能保证Libuv每次循环都触发回调方法。其次该方式也无法有效的计算整个Libuv循环所耗费的具体时间。

    解决方案:在Libuv主循环之外控制帧率,这样既可以有效计算整个Libuv循环循环一次所耗费的具体时间,也能保证Libuv每循环一次后都能进行帧率控制。

    TIPS:关于UpdateFrame逻辑更新在哪里处理,不管是在idle、prepare、check句柄中处理,或者跟帧率控制一样在Libuv主循环之外处理,均可。

    在博主的上篇介绍帧率控制的文件中,明显是有误区的,本篇文章进行纠正。

    链接:https://ufgnix0802.blog.csdn.net/article/details/126754967

    实现

    TIPS:uv_hrtime是Libuv提供的获取时间API,内部实现比较简单。

    	//数据结构
        uv_idle_t m_mainLoop;
        bool      m_quit;
    
        //帧率控制
        uint64_t  m_timerStart;
        uint64_t  m_timerEnd;
        int       m_frameRate;
        int       m_repeat;
        DWORD     m_durationFrameTime;
        DWORD     m_realTime;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    	if (0 != uv_idle_start(&m_mainLoop, MainLoop)) {		//在Idle句柄中执行UpdateFrame
            LOG_ERROR << "uv_idle_start err";
            goto Exit;
        }
        m_mainLoop.data = &pFunc;
    
        m_timerStart = uv_hrtime() / 1000000;                   //纳秒转毫秒,即单位为毫秒。记录第一次逻辑帧循环时间
        m_timerEnd   = 0;
        m_repeat     = 1;
    
    	//Windows不是一个实时操作系统,即Sleep存在误差,比如Sleep(30),那么实际可能Sleep(45)。
    	//具体可参考:https://blog.liboliu.com/a/78
    	timeBeginPeriod(1);
        while (true) {
            //Update
            uv_run(&m_loop, UV_RUN_ONCE);
    
            m_timerEnd          = uv_hrtime() / 1000000;        //纳秒转毫秒,即单位为毫秒,每次uv_run运行完获取一下截止时间
            m_durationFrameTime = (DWORD)(m_frameRate * m_repeat);//m_frameRate是我们自己设计的控制逻辑帧,比如1000,即1s。m_repeat会记录当前是第几次循环。那么控制逻辑帧 x 循环次数 = 累积循环帧时间
            m_repeat++;
            m_realTime = (DWORD)(m_timerEnd - m_timerStart);
    
            //规定的帧率内执行完所有事件,则Sleep;否则追帧。
            if (m_durationFrameTime < m_realTime) {
                LOG_INFO << "超出控制帧,追帧";
            }
            else {
                LOG_INFO << "m_durationFrameTime - m_realTime = sleep time:"
                    << m_durationFrameTime << " - " << m_realTime << " = " <<
                    m_durationFrameTime - m_realTime;
                Sleep(m_durationFrameTime - m_realTime);
            }
    
            if (m_quit) {		//退出标记位,bool类型
                LOG_INFO << "Service quiting...";
                break;
            }
        }
        //恢复时钟精度
        timeEndPeriod(1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    追帧算法

      当我们在规定的控制逻辑帧内处理完所有的事务,那么我们空余的时间应该让进程休息。那么关键来了,如果在规定的控制逻辑帧内没有执行完我们的事务,怎么办呢?那就是追帧,我们立刻执行下一帧,而下一帧的空余时间用来弥补当前帧规定逻辑时间内之外超出的时间。下面为上述追帧算法的图示;

    效果图

  • 相关阅读:
    (210)Verilog HDL:设计一个电路之Rule 110
    许战海方法论日文版正式发布,多家日媒转发
    CPU是海王?聊聊 主/子线程 和 同/异步 的关系
    格式化的u盘怎么恢复数据?
    剑指offer(C++)-JZ19:正则表达式匹配(算法-动态规划)
    Day01——瑞吉外卖
    蓝桥杯练习题七 - 第几天(c++)
    6- 华为云查看容器日志
    linux上搭建sftp服务器
    NoClassDefFoundError产生原因,及解决办法
  • 原文地址:https://blog.csdn.net/qq135595696/article/details/127930180