core/TimerSys.cpp
author Scott Ehlert <ds@alliedmods.net>
Sun Mar 30 07:00:22 2008 +0000 (2008-03-30)
changeset 1952 b7aa2b626807
child 2289 44045ead61cd
permissions -rw-r--r--
Spring Cleaning, Part Ichi (1)
Various minor things done to project files
Updated sample extension project file and updated makefile to the new unified version (more changes likely on the way)
Updated regex project file and makefile
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include <time.h>
    33 #include "TimerSys.h"
    34 #include "ForwardSys.h"
    35 #include "sourcemm_api.h"
    36 #include "frame_hooks.h"
    37 #include "ConVarManager.h"
    38 
    39 #define TIMER_MIN_ACCURACY		0.1
    40 
    41 TimerSystem g_Timers;
    42 double g_fUniversalTime = 0.0f;
    43 float g_fGameStartTime = 0.0f;	/* Game game start time, non-universal */
    44 double g_fTimerThink = 0.0f;		/* Timer's next think time */
    45 const double *g_pUniversalTime = &g_fUniversalTime;
    46 ConVar *mp_timelimit = NULL;
    47 int g_TimeLeftMode = 0;
    48 
    49 ConVar sm_time_adjustment("sm_time_adjustment", "0", 0, "Adjusts the server time in seconds");
    50 
    51 inline double GetSimulatedTime()
    52 {
    53 	return g_fUniversalTime;
    54 }
    55 
    56 time_t GetAdjustedTime(time_t *buf)
    57 {
    58 	time_t val = time(NULL) + sm_time_adjustment.GetInt();
    59 	if (buf)
    60 	{
    61 		*buf = val;
    62 	}
    63 	return val;
    64 }
    65 
    66 class DefaultMapTimer : 
    67 	public IMapTimer,
    68 	public SMGlobalClass,
    69 	public IConVarChangeListener
    70 {
    71 public:
    72 	DefaultMapTimer()
    73 	{
    74 		m_bInUse = false;
    75 	}
    76 
    77 	void OnSourceModLevelChange(const char *mapName)
    78 	{
    79 		g_fGameStartTime = 0.0f;
    80 	}
    81 
    82 	int GetMapTimeLimit()
    83 	{
    84 		return mp_timelimit->GetInt();
    85 	}
    86 
    87 	void SetMapTimerStatus(bool enabled)
    88 	{
    89 		if (enabled && !m_bInUse)
    90 		{
    91 			Enable();
    92 		} 
    93 		else if (!enabled && m_bInUse)
    94 		{
    95 			Disable();
    96 		}
    97 		m_bInUse = enabled;
    98 	}
    99 
   100 	void ExtendMapTimeLimit(int extra_time)
   101 	{
   102 		if (extra_time == 0)
   103 		{
   104 			mp_timelimit->SetValue(0);
   105 			return;
   106 		}
   107 
   108 		extra_time /= 60;
   109 
   110 		mp_timelimit->SetValue(mp_timelimit->GetInt() + extra_time);
   111 	}
   112 
   113 	void OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue)
   114 	{
   115 		g_Timers.MapTimeLeftChanged();
   116 	}
   117 
   118 private:
   119 	void Enable()
   120 	{
   121 		g_ConVarManager.AddConVarChangeListener("mp_timelimit", this);
   122 	}
   123 
   124 	void Disable()
   125 	{
   126 		g_ConVarManager.RemoveConVarChangeListener("mp_timelimit", this);
   127 	}
   128 
   129 private:
   130 	bool m_bInUse;
   131 } s_DefaultMapTimer;
   132 
   133 /**
   134  * If the ticking process has run amok (should be impossible), we 
   135  * take care of this by "skipping" the in-between time, to prevent 
   136  * a bazillion times from firing on accident.  This has the result  
   137  * that a drastic jump in time will continue acting normally.  Users 
   138  * may not expect this, but... I think it is the best solution.
   139  */
   140 inline double CalcNextThink(double last, float interval)
   141 {
   142 	if (g_fUniversalTime - last - interval <= TIMER_MIN_ACCURACY)
   143 	{
   144 		return last + interval;
   145 	}
   146 	else
   147 	{
   148 		return g_fUniversalTime + interval;
   149 	}
   150 }
   151 
   152 void ITimer::Initialize(ITimedEvent *pCallbacks, float fInterval, float fToExec, void *pData, int flags)
   153 {
   154 	m_Listener = pCallbacks;
   155 	m_Interval = fInterval;
   156 	m_ToExec = fToExec;
   157 	m_pData = pData;
   158 	m_Flags = flags;
   159 	m_InExec = false;
   160 	m_KillMe = false;
   161 }
   162 
   163 TimerSystem::TimerSystem()
   164 {
   165 	m_pMapTimer = NULL;
   166 	m_bHasMapTickedYet = false;
   167 	m_bHasMapSimulatedYet = false;
   168 	m_fLastTickedTime = 0.0f;
   169 }
   170 
   171 TimerSystem::~TimerSystem()
   172 {
   173 	CStack<ITimer *>::iterator iter;
   174 	for (iter=m_FreeTimers.begin(); iter!=m_FreeTimers.end(); iter++)
   175 	{
   176 		delete (*iter);
   177 	}
   178 	m_FreeTimers.popall();
   179 }
   180 
   181 void TimerSystem::OnSourceModAllInitialized()
   182 {
   183 	g_ShareSys.AddInterface(NULL, this);
   184 	m_pOnGameFrame = g_Forwards.CreateForward("OnGameFrame", ET_Ignore, 0, NULL);
   185 	m_pOnMapTimeLeftChanged = g_Forwards.CreateForward("OnMapTimeLeftChanged", ET_Ignore, 0, NULL);
   186 }
   187 
   188 void TimerSystem::OnSourceModGameInitialized()
   189 {
   190 	mp_timelimit = icvar->FindVar("mp_timelimit");
   191 
   192 	if (m_pMapTimer == NULL && mp_timelimit != NULL)
   193 	{
   194 		SetMapTimer(&s_DefaultMapTimer);
   195 	}
   196 }
   197 
   198 void TimerSystem::OnSourceModShutdown()
   199 {
   200 	SetMapTimer(NULL);
   201 	g_Forwards.ReleaseForward(m_pOnGameFrame);
   202 	g_Forwards.ReleaseForward(m_pOnMapTimeLeftChanged);
   203 }
   204 
   205 void TimerSystem::OnSourceModLevelEnd()
   206 {
   207 	m_bHasMapTickedYet = false;
   208 	m_bHasMapSimulatedYet = false;
   209 }
   210 
   211 void TimerSystem::GameFrame(bool simulating)
   212 {
   213 	if (simulating && m_bHasMapTickedYet)
   214 	{
   215 		g_fUniversalTime += gpGlobals->curtime - m_fLastTickedTime;
   216 		if (!m_bHasMapSimulatedYet)
   217 		{
   218 			m_bHasMapSimulatedYet = true;
   219 			MapTimeLeftChanged();
   220 		}
   221 	}
   222 	else 
   223 	{
   224 		g_fUniversalTime += gpGlobals->interval_per_tick;
   225 	}
   226 
   227 	m_fLastTickedTime = gpGlobals->curtime;
   228 	m_bHasMapTickedYet = true;
   229 
   230 	if (g_fUniversalTime >= g_fTimerThink)
   231 	{
   232 		RunFrame();
   233 
   234 		g_fTimerThink = CalcNextThink(g_fTimerThink, TIMER_MIN_ACCURACY);
   235 	}
   236 
   237 	RunFrameHooks(simulating);
   238 
   239 	if (m_pOnGameFrame->GetFunctionCount())
   240 	{
   241 		m_pOnGameFrame->Execute(NULL);
   242 	}
   243 }
   244 
   245 void TimerSystem::RunFrame()
   246 {
   247 	ITimer *pTimer;
   248 	TimerIter iter;
   249 
   250 	double curtime = GetSimulatedTime();
   251 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); )
   252 	{
   253 		pTimer = (*iter);
   254 		if (curtime >= pTimer->m_ToExec)
   255 		{
   256 			pTimer->m_InExec = true;
   257 			pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   258 			pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   259 			iter = m_SingleTimers.erase(iter);
   260 			m_FreeTimers.push(pTimer);
   261 		} 
   262 		else 
   263 		{
   264 			break;
   265 		}
   266 	}
   267 
   268 	ResultType res;
   269 	for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); )
   270 	{
   271 		pTimer = (*iter);
   272 		if (curtime >= pTimer->m_ToExec)
   273 		{
   274 			pTimer->m_InExec = true;
   275 			res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   276 			if (pTimer->m_KillMe || (res == Pl_Stop))
   277 			{
   278 				pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   279 				iter = m_LoopTimers.erase(iter);
   280 				m_FreeTimers.push(pTimer);
   281 				continue;
   282 			}
   283 			pTimer->m_InExec = false;
   284 			pTimer->m_ToExec = CalcNextThink(pTimer->m_ToExec, pTimer->m_Interval);
   285 		}
   286 		iter++;
   287 	}
   288 }
   289 
   290 ITimer *TimerSystem::CreateTimer(ITimedEvent *pCallbacks, float fInterval, void *pData, int flags)
   291 {
   292 	ITimer *pTimer;
   293 	TimerIter iter;
   294 	float to_exec = GetSimulatedTime() + fInterval;
   295 
   296 	if (m_FreeTimers.empty())
   297 	{
   298 		pTimer = new ITimer;
   299 	} else {
   300 		pTimer = m_FreeTimers.front();
   301 		m_FreeTimers.pop();
   302 	}
   303 
   304 	pTimer->Initialize(pCallbacks, fInterval, to_exec, pData, flags);
   305 
   306 	if (flags & TIMER_FLAG_REPEAT)
   307 	{
   308 		m_LoopTimers.push_back(pTimer);
   309 		goto return_timer;
   310 	}
   311 
   312 	if (m_SingleTimers.size() >= 1)
   313 	{
   314 		iter = --m_SingleTimers.end();
   315 		if ((*iter)->m_ToExec <= to_exec)
   316 		{
   317 			goto normal_insert_end;
   318 		}
   319 	}
   320 
   321 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++)
   322 	{
   323 		if ((*iter)->m_ToExec >= to_exec)
   324 		{
   325 			m_SingleTimers.insert(iter, pTimer);
   326 			goto return_timer;
   327 		}
   328 	}
   329 
   330 normal_insert_end:
   331 	m_SingleTimers.push_back(pTimer);
   332 
   333 return_timer:
   334 	return pTimer;
   335 }
   336 
   337 void TimerSystem::FireTimerOnce(ITimer *pTimer, bool delayExec)
   338 {
   339 	ResultType res;
   340 
   341 	if (pTimer->m_InExec)
   342 	{
   343 		return;
   344 	}
   345 
   346 	pTimer->m_InExec = true;
   347 	res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   348 
   349 	if (!(pTimer->m_Flags & TIMER_FLAG_REPEAT))
   350 	{
   351 		pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   352 		m_SingleTimers.remove(pTimer);
   353 		m_FreeTimers.push(pTimer);
   354 	} 
   355 	else 
   356 	{
   357 		if ((res != Pl_Stop) && !pTimer->m_KillMe)
   358 		{
   359 			if (delayExec)
   360 			{
   361 				pTimer->m_ToExec = GetSimulatedTime() + pTimer->m_Interval;
   362 			}
   363 			pTimer->m_InExec = false;
   364 			return;
   365 		}
   366 		pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   367 		m_LoopTimers.remove(pTimer);
   368 		m_FreeTimers.push(pTimer);
   369 	}
   370 }
   371 
   372 void TimerSystem::KillTimer(ITimer *pTimer)
   373 {
   374 	if (pTimer->m_KillMe)
   375 	{
   376 		return;
   377 	}
   378 
   379 	if (pTimer->m_InExec)
   380 	{
   381 		pTimer->m_KillMe = true;
   382 		return;
   383 	}
   384 
   385 	pTimer->m_InExec = true; /* The timer it's not really executed but this check needs to be done */
   386 	pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   387 
   388 	if (pTimer->m_Flags & TIMER_FLAG_REPEAT)
   389 	{
   390 		m_LoopTimers.remove(pTimer);
   391 	} else {
   392 		m_SingleTimers.remove(pTimer);
   393 	}
   394 
   395 	m_FreeTimers.push(pTimer);
   396 }
   397 
   398 CStack<ITimer *> s_tokill;
   399 void TimerSystem::RemoveMapChangeTimers()
   400 {
   401 	ITimer *pTimer;
   402 	TimerIter iter;
   403 
   404 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++)
   405 	{
   406 		pTimer = (*iter);
   407 		if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE)
   408 		{
   409 			s_tokill.push(pTimer);
   410 		}
   411 	}
   412 
   413 	for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); iter++)
   414 	{
   415 		pTimer = (*iter);
   416 		if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE)
   417 		{
   418 			s_tokill.push(pTimer);
   419 		}
   420 	}
   421 
   422 	while (!s_tokill.empty())
   423 	{
   424 		KillTimer(s_tokill.front());
   425 		s_tokill.pop();
   426 	}
   427 }
   428 
   429 IMapTimer *TimerSystem::SetMapTimer(IMapTimer *pTimer)
   430 {
   431 	IMapTimer *old = m_pMapTimer;
   432 
   433 	m_pMapTimer = pTimer;
   434 
   435 	if (m_pMapTimer)
   436 	{
   437 		m_pMapTimer->SetMapTimerStatus(true);
   438 	}
   439 
   440 	if (old)
   441 	{
   442 		old->SetMapTimerStatus(false);
   443 	}
   444 
   445 	return old;
   446 }
   447 
   448 IMapTimer *TimerSystem::GetMapTimer()
   449 {
   450 	return m_pMapTimer;
   451 }
   452 
   453 void TimerSystem::MapTimeLeftChanged()
   454 {
   455 	m_pOnMapTimeLeftChanged->Execute(NULL);
   456 }
   457 
   458 void TimerSystem::NotifyOfGameStart(float offset)
   459 {
   460 	g_fGameStartTime = gpGlobals->curtime + offset;
   461 }
   462 
   463 float TimerSystem::GetTickedTime()
   464 {
   465 	return g_fUniversalTime;
   466 }
   467 
   468 bool TimerSystem::GetMapTimeLeft(float *time_left)
   469 {
   470 	if (!m_pMapTimer)
   471 	{
   472 		return false;
   473 	}
   474 
   475 	int time_limit;
   476 	if (!m_bHasMapSimulatedYet || (time_limit = m_pMapTimer->GetMapTimeLimit()) < 1)
   477 	{
   478 		*time_left = -1.0f;
   479 	}
   480 	else
   481 	{
   482 		*time_left = (g_fGameStartTime + time_limit * 60.0f) - gpGlobals->curtime;
   483 	}
   484 
   485 	return true;
   486 }
   487