实现思路
这篇笔记给出一个仿 Cocos2d-x 中 DelayTime 的简易定时器实现。思路是用一个 DelayTime 类封装“延迟时间 + 回调函数”,再用一个 FrameManger 管理所有待触发的 DelayTime,在主循环里按照固定频率推进时间,到点就触发回调。
主循环依赖 QueryPerformanceCounter / QueryPerformanceFrequency 做高精度计时,把一次完整的 Loop 看作一帧(目标 60 帧),每帧让队列里所有定时器的 nowTime 自增一次,达到目标帧数就执行回调并重置计数。
完整代码
#include"iostream"
#include"windows.h"
#include "functional"
#include "time.h"
#include "math.h"
using namespace std;
class Ref
{
};
class DelayTime: public Ref
{
public:
static DelayTime*create(float time, const function<void(void)> &f)
{
auto ss = new DelayTime;
ss->func = f;
ss->time = time*60;
return ss;
}
function<void()> func;
void run( )
{
func();
}
float time;
float nowTime = 0;
};
int a = 0, b = 0;
class FrameManger :public Ref
{
public:
FrameManger()
{
for (int i = 0; i < 100; i++)
{
queue[i] = nullptr;
}
}
void addChild(DelayTime *pChild)
{
queue[count++] = pChild;
}
void Loop()
{
Sleep(rand()%5);
for (int i = 0;i<count;i++)
{
if (queue[i] == nullptr)return;
queue[i]->nowTime++;
if (queue[i]->nowTime >= queue[i]->time)
{
queue[i]->run();
cout << " "<<clock()-a<<endl;
queue[i]->nowTime = 0;
a = clock();
}
}
}
DelayTime* queue[100];
int count = 0;
};
int main()
{
FrameManger*fr = new FrameManger;
fr->addChild(DelayTime::create(1.0, [=]{cout << "callback_1"; }));
fr->addChild(DelayTime::create(2.0, [=]{cout << "callback_2"; }));
a = clock();
LARGE_INTEGER nFreq;
LARGE_INTEGER nLast;
LARGE_INTEGER nNow;
long long Interval;
QueryPerformanceFrequency(&nFreq);
Interval = (long long)(1/60.0 * nFreq.QuadPart);
cout << "x=" << nFreq.QuadPart << endl;
QueryPerformanceCounter(&nLast);
while (1)
{
QueryPerformanceCounter(&nNow);
if (nNow.QuadPart - nLast.QuadPart >Interval)//计算cpu频率计数差值
{
nLast.QuadPart = nNow.QuadPart;
fr->Loop();
}
else
{
Sleep(0);
}
}
system("pause");
return 0;
}
关键点说明
- 帧化时间:
DelayTime::create里把用户传入的秒数乘以 60(ss->time = time*60),这样内部统一用“帧数”作为时间单位,Loop每被调用一次就让nowTime自增 1。 - 回调容器:
function<void()> func用来存放任意可调用对象,create接收const function<void(void)> &f,这样 lambda、函数指针、仿函数都能塞进来。 - 固定帧率驱动:主循环里用
QueryPerformanceFrequency拿到计数器频率,Interval = (long long)(1/60.0 * nFreq.QuadPart)算出“一帧对应多少个高精度计数”,两次QueryPerformanceCounter的差值超过Interval就推进一帧,从而逼近 60 帧。 - 调度队列:
FrameManger用一个长度 100 的裸指针数组queue[100]存放DelayTime*,addChild按顺序追加,Loop里遇到空指针就返回,相当于一个最朴素的定长环形任务池。 - 触发逻辑:当某个
DelayTime的nowTime累积到time(即达到目标帧数)时,调用run()触发回调,然后把nowTime归零,使这个定时器变成“周期触发”。 - 时间误差观察:每次回调都会打印
clock()-a,并把a更新为当前clock(),可以用来观察两次回调之间的真实耗时,验证 60 帧驱动下的调度误差。
使用示例
在 main 中先创建 FrameManger,再往里添加两个 DelayTime:一个 1 秒后触发 callback_1,一个 2 秒后触发 callback_2。随后进入高精度定时驱动的 while (1) 主循环,由 fr->Loop() 负责按帧推进所有定时器。
这是一个面向学习的最小可运行版本,没有处理定时器的删除、容量扩容、异常回调等问题,但足够说明 DelayTime 类的核心调度思想。