游戏服务器之定时器
1.1 简单介绍
在游戏中的关于定时任务的应用很多,在此就不举例了。所谓定时任务,简单来说就是t毫秒后执行相应的任务。因为这里用的例子都是博主工作时用到的游戏服务器中的,所以相关代码会比较详尽,如若只是了解定时器的大概实现,按照博主所标注的顺序,只看重点即可。
1.2 相关api介绍
在windows平台下有两个相关的函数:
BOOL WINAPI QueryPerformanceFrequency(
Out LARGE_INTEGER *lpFrequency
);
Parameters:lpFrequency [out]
A pointer to a variable that receives the current performance-counter frequency, in counts per second. If the installed hardware doesn’t support a high-resolution performance counter, this parameter can be zero (this will not occur on systems that run Windows XP or later。
从MSDN上的手册可以看到这个参数,用来接收当前硬件平台的每秒钟的计数频率。博主猜测这个频率跟晶振频率CPU频率啥的有关系。
Return value
the return value is nonzero.If the function fails, the return value is zero.
可以看到函数成功返回非零值,否则返回0
BOOL WINAPI QueryPerformanceCounter(
Out LARGE_INTEGER *lpPerformanceCount
);
Parameters:lpPerformanceCount [out]
A pointer to a variable that receives the current performance-counter value, in counts.
该参数用来接受平台的计数值。
Return value
返回值也是成功返回一个非零值,否则返回0.
1.3 基本原理
由上可知,有了每秒钟的计数频率,和当前计数。那么我们就可以愉快计算时间间隔了。
时间间隔 = 当前计数 - 之前计数/计数频率
现在还有一个是需要在一个while循环中检测各项定时器是否到达,在我们的游戏服务器中也就是随着服务器的心跳检测。每个心跳的过程中去检测相关object的的定时器是否到达。
1.4 相关代码
头文件.h
class TimerFix
{
public:
TimerFix();
void Restart();
double Elapsed(bool restart=false);
private:
//std::clock_t m_StartTime;
LARGE_INTEGER m_StartTime;//获取定时器开始计数
LARGE_INTEGER m_Freq; //频率
LARGE_INTEGER m_EndTime; //获取当前计数
};
实现.cpp
namespace MMOLibrary
{
TimerFix::TimerFix()
{
QueryPerformanceFrequency(&m_Freq);//获取频率
QueryPerformanceCounter(&m_StartTime);//获取当前计数
m_EndTime = m_StartTime;
}
void TimerFix::Restart()
{
QueryPerformanceCounter(&m_StartTime);//重新计数时,重新获取计数
m_EndTime = m_StartTime;
}
double TimerFix::Elapsed(bool restart) //获取时间间隔
{
//std::clock_t c = std::clock();
QueryPerformanceCounter(&m_EndTime);
double dtime = (double)(m_EndTime.QuadPart - m_StartTime.QuadPart) / (double)m_Freq.QuadPart;
if (restart) m_StartTime = m_EndTime;
return dtime;
}
}
1.5应用举例
下面举一个栗子,来解释说明在我们的游戏中是如何具体使用的。
//相关声明,便于读者理解代码具体含义
//GamePlayer中成员变量及函数
typedef std::map<int, double> TimerMap;
TimerMap mTimer;
GameServerLib* mGSL;
int SetPlayerTimer(int i,int esp);
//GameServerLi中的成员变量及成员函数
//TimerFix的具体实现上面已有
TimerFix* mTimerFix;
TimerFix* GetTimerFix(){return mTimerFix;}
//mGSL为GamePlayer的基类中的GameServerLib * 类型的成员变量
player:set_timer(10,2000) //player这里的player是游戏中的一个GamePlayer的对象
//这里的第一个参数标识哪个定时器,因为游戏中可能会有多种定时任务,第二个参数是时间,单位是毫秒
//下面是set_timer的实现
int GamePlayer::SetPlayerTimer(int i,int esp)
{
if( i >=1 && i<= 25)
{
//这里的基本操作是,先获取当前时间距离定时器开始计时或者初始化时的时间间隔
//然后加上需要的时间间隔,将相应的时间间隔存进map里,map的key标识时具体哪个定时器
mTimer[i-1] = mGSL->GetTimerFix()->Elapsed() + esp/1000.0;
if( esp == 0 )
{
mTimer[i-1] = 0;
}
return i;
}
return 0;
}
1.6随着服务器心跳,update的时候检测定时器
下面是我们游戏中非常详细的相关实现的具体过程
class Application:
{
virtual void OnUpdate()=0;//纯虚函数
}
class CGameServerApp :
public LEUD::Application,//继承过来
void CGameServerApp::OnUpdate()
//相关具体OnUpdate具体实现及有关声明
//下面根据数字顺序理解即可
while(WAIT_OBJECT_0 != WaitForMultipleObjects(size,ev,FALSE,m_HeartBeat)){
__try{
OnUpdate(); //这里是数字顺序 1
RunCommand();
}__except(UnhandledExceptionFilter(GetExceptionInformation())){
}
}
void CGameServerApp::OnUpdate()
{
PERF_NODE("m_pGameServerLib");
//GameServerLib* m_pGameServerLib;
m_pGameServerLib->Update(); //这里是数字顺序 2
}
void GameServerLib::Update()
{
double tim = GetTimerFix()->Elapsed(); //注意这里
PERF_NODE("mSessionsUpdate");
if( mUpdateTime + mUpdateDuration < tim ) //这里是数字顺序 3
{
mUpdateTime = tim;
SessionUpdateFuntor funtor;
funtor.This = this;
funtor.time = tim;
//这里是数字顺序4
mSessions.erase(std::remove_if(mSessions.begin(),mSessions.end(),funtor),mSessions.end());
}
}
class SessionUpdateFuntor
{
public:
GameServerLib* This;
double time;
bool operator() (GameSession* & session)
{
//需要判断当前session是否断开连接
if( session->GetState()==GameSession::SESSION_STATE_IS_END ) //
{
This->DestroySession(session);
return true;
}
session->Update(time); //这里是数字顺序5
return false;
}
};
void GameSession::Update(double time)
{
if( mState != SESSION_STATE_IS_RUN ) return ;
mPlayer->Update(time); //这里是数字顺序6
}
void GamePlayer::Update(double ti)
{
{
PERF_NODE("mTimer");
for( TimerMap::iterator pos = mTimer.begin(); //这里的mTimer就是我们当初set_timer时所用的
pos != mTimer.end();
++ pos )
{
if( pos->second > 0.0 && ti > pos->second )
{
pos->second = 0.0;
//这里用map的key去拼成个字符串
char ss[512];_snprintf_s(ss,_countof(ss),511,"player.onTimer%d",(pos->first+1));ss[511]=0;
//执行相应的player的对应的定时器,调用相应的lua脚本
mGSL->GetCtoLuaEngine()->OnlyPlayer(this,ss);//这里时数字7结束
}
}
}
}