17. Time and timer

iFun Engine uses a monotonic clock internally and provides a wall clock for developers’ convenience. It also provides a timer to periodically initiate events. These features are explained below.

17.1. Monotonic clock

The wall clock has the advantage of having the same units as the real world, but time can deviate during synchronization with server time. At such times, an internal server task may not run or may run twice. For this reason, iFun Engine uses a monotonic clock for all time units, and we recommend that game server developers also use this monotonic clock.

As its name implies, the monotonic clock increases time monotonically. In other words, it reliably returns increased time, even if time goes back 1 second due to server time synchronization. All recent OSes provide monotonic clocks, and iFun Engine’s monotonic clock uses the one provided by the system. iFun Engine’s monotonic clock is precise down to the microsecond.

17.1.1. Using the monotonic clock

#include <funapi.h>

void test_function(MonotonicClock::Value old) {
  // Reads the current time like this:
  MonotonicClock::Value t = MonotonicClock::Now();

  // Clock values can be treated as regular integers for comparison.
  if (old < t) {
    // old is smaller than t.
  }

  // Converting to struct timespec is like this:
  struc timesepc ts;
  MonotonicClock::ToTimespec(t, &ts);
}
using funapi;

void TestFunction(Int64 old)
{
  // Reads the current time like this:
  Int64 t = MonotonicClock.Now;

  // Clock values can be treated as regular integers for comparison.
  if (old < t) {
    // old is smaller than t.
  }
}

17.2. Wall clock

The advantage of the monotonic clock is that it can be used reliably even when server time changes, but it is difficult to debug as it cannot be read by humans. iFun Engine provides a wall clock for this reason.

The wall clock in iFun Engine keeps track of the difference between the monotonic clock value and real world time at the instant the game server starts, then reflects this value when the wall clock is needed. For this reason, iFun Engine’s wall clock also increases monotonically.

iFun Engine’s wall clock time may deviate from OS time if the OS time is adjusted. For that reason, iFun Engine’s wall clock is best used for reference only.

17.2.1. Using the wall clock

#include <funapi.h>

void test_function(WallClock::Value old) {
  // Reads the current time like this:
  WallClock::Value t = WallClock::Now();

  // Clock values can be treated as regular integers for comparison.
  if (old < t) {
    // old is smaller than t.
  }

  // Easy to log the value.
  LOG(INFO) << t;

  // Easy to convert to Monotonic clock value.
  MonotonicClock::Value m = WallClock::ToMonotonicClock(t);
}
using funapi;

public void TestFunction(System.DateTime old)
{
  // Reads the current time like this:
  System.DateTime dt = WallClock.Now;

  // Clock values can be treated as regular integers for comparison.
  if (old < t) {
    // old is smaller than t.
  }

  // Easy to convert to Monotonic clock value.
  Int64 m = WallClock.ToMonotonicClock (dt);
}

17.2.2. Converting to and from timestamps

You can change the wall clock to an integer timestamp as follows.

// Getting the current timestamp.
int64_t ts = WallClock::GetTimestampInSec();

// Conversion from WallClock::Value into timestamp.
WallClock::Value t = WallClock::Now();
ts = WallClock::GetTimestampInSec(t);

// Conversion from timestamp into WallClock::Value.
t = WallClock::FromTimestampInSec(ts);

// Msec is supported.
ts = WallClock::GetTimestampInMsec();
ts = WallClock::GetTimestampInMsec(t);
t = WallClock::FromTimestampInMsec(ts);
// Getting the current timestamp.
Int64 ts = WallClock.GetTimestampInSec ();

// Conversion from System.DateTime into timestamp.
System.DateTime dateTime = WallClock.Now;
ts = WallClock.GetTimestampInSec (dateTime);

// Conversion from timestamp into System.DateTime.
dateTime = WallClock.FromTimestampInSec (ts);

// Msec is suported.
ts = WallClock.GetTimestampInMsec ();
ts = WallClock.GetTimestampInMsec (dateTime);
dateTime = WallClock.FromTimestampInMsec (ts);

17.2.3. Expressing time as text strings

You can change the wall clock to a text string as follows.

Note

Text strings for time are expressed in ISO-extended format. E.g., 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 이 아니면 실패합니다.
}

17.3. Timer

Most game server processing is cyclic, and a repeating timer called a “tick” is required for this. iFun Engine supports repeating timers and one-time timers. The iFun Engine timer registers handlers to invoke when the timer expires. An example is given below.

17.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() {
  // Use ExpireAt if you want to fire a timer at a specific time.
  // Example below triggers a timer at current time + 500msec.
  Timer::ExpireAt(WallClock::Now() + boost::posix_time::millisec(500), handler1);

  // If want to expire a timer firing in a specific time, use ExpireAfter.
  Timer::ExpireAfter(boost::posix_time::millisec(500), handler2);

  // Repetitive timer takes both an interval and a handler.
  // Example below sets a timer firing every 500msec.
  Timer::ExpireRepeatedly(boost::posix_time::millisec(500), handler3);
}

Important

Timer.ExpireAt(), Timer.ExpireAfter(), Timer.ExpireRepeatedly(), and Timer::Cancel() detect unexpected rollbacks when used together with ORM. If you are using the iFun Engine’s ORM feature, please refer to Transaction for details.

using funapi;

public void TestFunction()
{
  // Use ExpireAt if you want to fire a timer at a specific time.
  // Example below triggers a timer at current time + 1500msec.
  Timer.ExpireAt (
      WallClock.Now + WallClock.FromMsec(1500), TimerHandler1);


  // You can use C# delegate.
  Timer.Handler timer_handler2 = (
      /*UInt64 tid*/ tid,
      /*System.DateTime*/ value) => {
    Log.Info ("timer_handler_2");
  };

  // Repetitive timer takes both an interval and a handler.
  // Example below sets a timer firing every 500msec.
  // We are using C# delegate here.
  Timer.ExpireRepeatedly (WallClock.FromSec (1), timer_handler2);

  // If want to expire a timer firing in a specific time, use ExpireAfter.
  // Below example triggers an event in 500 msec.
  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(), and Timer::Cancel() detect unexpected rollbacks when used together with ORM. If you are using the iFun Engine’s ORM feature, please refer to Transaction for details.