16. 时间和定时器

iFun引擎内部使用monotonic clock,为了便于开发人员作业,也提供wall clock,作为参考。此外,还提供用于定期引发event的timer。各种方式的说明如下。

16.1. Monotonic clock

Wall clock的优点是与现实世界的时间单位一致,但当同步服务器时间时,时间值会发生漂移。此时,可能会导致服务器内部未能执行须要在特定时刻处理的作业,或是可能会执行两次。因此,iFun引擎的所有时间单位均使用Monotonic clock,建议游戏服务器开发人员也使用monotonic clock。

Monotonic clock正如其名,是一种时间值单调递增的时间。即,同步服务器的时间后,即使慢1秒,也能稳定返回增加的时间值。monotonic clock功能在最新的OS中均有提供,iFun引擎的monotomic clock使用system中提供的monotonic clock。iFun引擎的Monotomic clock提供微秒单位的精密度。

16.1.1. Monotonic clock 应用方法

#include <funapi.h>

void test_function(MonotonicClock::Value old) {
  // 현재 시간은 다음처럼 얻습니다.
  MonotonicClock::Value t = MonotonicClock::Now();

  // 시간 비교는 일반 integer 와 같습니다.
  if (old < t) {
    // old 는 t 보다는 작네요.
  }

  // struct timespec 으로의 변경은 다음처럼 합니다.
  struc timesepc ts;
  MonotonicClock::ToTimespec(t, &ts);
}
using funapi;

void TestFunction(Int64 old)
{
  // 현재 시간은 다음처럼 얻습니다.
  Int64 t = MonotonicClock.Now;

  // 시간 비교는 일반 integer 와 같습니다.
  if (old < t) {
    // old 는 t 보다는 작네요.
  }
}

16.2. Wall clock

Monotonic clock在修改服务器时间的情况下也可以稳定使用,但它并不是可以让人读取的状态,所以调试时较为困难。因此,iFun引擎还提供了Wall clock。

iFun引擎中的Wall clock会记忆游戏服务器延迟瞬间的现实clock和Monotonic clock值之间的差异,此后在需要Wall clock时,对反映了相应差异的值作出体现。因此,iFun引擎中的Wall clock也是单调递增的。

所以在修改OS时间时,iFun引擎的Wall clock值和OS的时间值会相异。因此建议在iFun引擎中将Wall clock作为参考使用。

16.2.1. Wall clock 应用方法

#include <funapi.h>

void test_function(WallClock::Value old) {
  // 현재 시간은 다음처럼 얻습니다.
  WallClock::Value t = WallClock::Now();

  // 시간 비교는 일반 integer 와 같습니다.
  if (old < t) {
    // old 는 t 보다는 작네요.
  }

  // 로그로 쉽게 출력할 수 있습니다.
  LOG(INFO) << t;

  // Monotonic clock 값으로 변환할 수도 있습니다.
  MonotonicClock::Value m = WallClock::ToMonotonicClock(t);
}
using funapi;

public void TestFunction(System.DateTime old)
{
  // 현재 시간은 다음처럼 얻습니다.
  System.DateTime dt = WallClock.Now;

  // 시간 비교는 일반 DateTime 과 같습니다.
  if (old < t) {
    // old 는 t 보다는 작네요.
  }

  // Monotonic clock 값으로 변환할 수도 있습니다.
  Int64 m = WallClock.ToMonotonicClock (dt);
}

16.2.2. 转换为Timestamp

可按如下所示,将Wall clock 转换成整数值timestamp。

// 현재 시간에 대한 timestamp 를 얻거나
int64_t ts = WallClock::GetTimestampInSec();

// WallClock::Value 값을 timestamp 로 변환할 수 있습니다.
WallClock::Value t = WallClock::Now();
ts = WallClock::GetTimestampInSec(t);

// 반대로 timestamp 값을 WallClock::Value 로도 변환할 수 있습니다.
t = WallClock::FromTimestampInSec(ts);

// 밀리초 단위로도 가능합니다.
ts = WallClock::GetTimestampInMsec();
ts = WallClock::GetTimestampInMsec(t);
t = WallClock::FromTimestampInMsec(ts);
// 현재 시간에 대한 timestamp 를 얻거나
Int64 ts = WallClock.GetTimestampInSec ();


// System.DateTime 값을 timestamp 로 변환할 수 있습니다.
System.DateTime dateTime = WallClock.Now;
ts = WallClock.GetTimestampInSec (dateTime);

// 반대로 timestamp 값을 System.DateTime 로도 변환할 수 있습니다.
dateTime = WallClock.FromTimestampInSec (ts);

// 밀리초 단위로도 가능합니다.
ts = WallClock.GetTimestampInMsec ();
ts = WallClock.GetTimestampInMsec (dateTime);
dateTime = WallClock.FromTimestampInMsec (ts);

16.2.3. 时间字符串的表示

按如下所示,可将Wall clock转化成字符串形式。

Note

时间字符串通过ISO-extended形式表示。 例) 2016-09-21T03:30:16.787663

// 현재 시간에 대한 문자열을 얻거나
string ts = WallClock::GetTimestring();

// WallClock::Value 값을 문자열로 변환할 수 있습니다.
WallClock::Value t = WallClock::Now();
ts = WallClock::GetTimestring(t);

// 반대로 문자열을 WallClock::Value 로도 변환할 수 있습니다.
if (not WallClock::FromTimestring(ts, &t)) {
  // ts 가 올바른 iso-extended time string 이 아니면 실패합니다.
}
// 현재 시간에 대한 문자열을 얻거나
string timestr = WallClock.GetTimestring ();

// System.DateTime 값을 문자열로 변환할 수 있습니다.
System.DateTime t = WallClock.Now;
timestr = WallClock.ToTimestring (t);

Log.Info (timestr);
// 반대로 문자열을 System.DateTime 로도 변환할 수 있습니다.
if (!WallClock.FromTimestring (timestr, out t)) {
  // timestr 가 올바른 iso-extended time string 이 아니면 실패합니다.
}

16.3. 定时器

游戏服务器有时需要定期处理某项作业,为此,需要一种被称为‘Tick’的重复timer。iFun引擎支持重复定时器和一次性定时器。iFun引擎定时器通过上传定时器expire时调用的处理器的方式来使用。以下是其示例。

16.3.1. 定时器应用方法

#include <funapi.h>

extern void handler1(Timer::Id tid, const WallClock::Value &at);
extern void handler2(Timer::Id tid, const WallClock::Value &at);
extern void handler3(Timer::Id tid, const WallClock::Value &at);

void test_function() {
  // 특정 시간에 expire 되게 하려면 ExpireAt 을 이용합니다.
  // 아래 예는 지금으로부터 500msec 뒤에 expire 하도록 설정합니다
  Timer::ExpireAt(WallClock::Now() + boost::posix_time::millisec(500), handler1);

  // 상대 시간 뒤에 expire 되게 하려면 ExpireAfter 를 이용합니다.
  Timer::ExpireAfter(boost::posix_time::millisec(500), handler2);

  // 반복해서 expire 되는 timer 는 interval 과 핸들러를 지정합니다.
  // 아래는 500msec 단위로 expire 되는 timer 입니다.
  Timer::ExpireRepeatedly(boost::posix_time::millisec(500), handler3);
}

Important

Timer::ExpireAt(), Timer::ExpireAfter(), Timer::ExpireRepeatedly(), Timer::Cancel() 함수들은 의도하지 않은 중복 호출을 방지하기 위하여 오브젝트 롤백을 감지하는 기능이 내장되어 있습니다. 오브젝트 서브시스템의 기능을 이용하신다면 事务 을 참고하세요.

using funapi;

public void TestFunction()
{
  // 특정 시간에 expire 되게 하려면 ExpireAt 을 이용합니다.
  // 아래 예는 지금으로부터 500msec 뒤에 expire 하도록 설정합니다
  Timer.ExpireAt (
      WallClock.Now + WallClock.FromMsec(1500), TimerHandler1);


  // delegate를 이용해 타이머를 호출 할수도 있습니다.
  Timer.Handler timer_handler2 = (
      /*UInt64 tid*/ tid,
      /*System.DateTime*/ value) => {
    Log.Info ("timer_handler_2");
  };

  // 반복해서 expire 되는 timer 는 interval 과 핸들러를 지정합니다.
  // 아래는 500msec 단위로 expire 되는 timer 입니다.
  // delegate를 이용해 호출하겠습니다.
  Timer.ExpireRepeatedly (WallClock.FromSec (1), timer_handler2);

  // 반복해서 expire 되는 timer 는 interval 과 핸들러를 지정합니다.
  // 아래는 500msec 단위로 expire 되는 timer 입니다.
  Timer.ExpireAfter (WallClock.FromMsec(500), TimerHandler3);
}

public void TimerHandler1(UInt64 tid, DateTime value)
{
  Log.Info ("TimerHandler_1");
}

public void TimerHandler3(UInt64 tid, DateTime value)
{
  Log.Info ("TimerHandler_3");
}

Important

Timer.ExpireAt(), Timer.ExpireAfter(), Timer.ExpireRepeatedly(), Timer::Cancel() 함수들은 의도하지 않은 중복 호출을 방지하기 위하여 오브젝트 롤백을 감지하는 기능이 내장되어 있습니다. 오브젝트 서브시스템의 기능을 이용하신다면 事务 을 참고하세요.