diff --git a/bc/CMakeLists.txt b/bc/CMakeLists.txt index e79e12c..3aea658 100644 --- a/bc/CMakeLists.txt +++ b/bc/CMakeLists.txt @@ -1,7 +1,8 @@ file(GLOB BC_SOURCES "*.cpp" "lock/*.cpp" - "system/**.cpp" + "time/*.cpp" + "system/*.cpp" ) add_library(bc STATIC diff --git a/bc/Time.hpp b/bc/Time.hpp new file mode 100644 index 0000000..97b1c7a --- /dev/null +++ b/bc/Time.hpp @@ -0,0 +1,6 @@ +#ifndef BC_TIME_HPP +#define BC_TIME_HPP + +#include "bc/time/Time.hpp" + +#endif diff --git a/bc/system/System_Time.cpp b/bc/system/System_Time.cpp new file mode 100644 index 0000000..7f301aa --- /dev/null +++ b/bc/system/System_Time.cpp @@ -0,0 +1,177 @@ +#include "bc/system/System_Time.hpp" +#include "bc/time/Time.hpp" +#include "bc/time/TimeConst.hpp" + +#if defined(WHOA_SYSTEM_MAC) +#include +#endif + +#if defined(WHOA_SYSTEM_WIN) +#include +#endif + +#if defined(WHOA_SYSTEM_LINUX) +#include +#include +#endif + +#include "bc/Debug.hpp" + +namespace Blizzard { +namespace System_Time { + +// Globals + +// Stores the earliest TSC value +static uint64_t s_absBegin = 0; + +// Stores the number of nanoseconds since Jan 1, 2000 00:00 GMT at the raw clock moment of s_absBegin. +static Time::Timestamp s_gmBegin = 0; + +// timeScales can be multiplied against number of ticks since s_absBegin to get +// meaningful durations in the corresponding format +double timeScaleNanoseconds = 0.0; +double timeScaleMicroseconds = 0.0; +double timeScaleMilliseconds = 0.0; +double timeScaleSeconds = 0.0; + +// Functions + +bool ReadTSC(uint64_t& counter) { +#if defined(WHOA_SYSTEM_WIN) + LARGE_INTEGER li; + auto ok = QueryPerformanceCounter(&li); + + if (ok) { + counter = static_cast(li.QuadPart); + return true; + } + + return false; +#elif defined(WHOA_SYSTEM_MAC) + counter = mach_absolute_time(); + return true; +#elif defined(WHOA_SYSTEM_LINUX) + struct timespec ts; + auto status = clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + if (status == 0) { + counter = (static_cast(ts.tv_sec) * TimeConst::TimestampsPerSecond) + ts.tv_nsec; + return true; + } + return false; +#endif +} + +// Returns a clock moment, relative to s_absBegin; +uint64_t QueryClockMoment() { + uint64_t counter = 0; + ReadTSC(counter); + + return counter - s_absBegin; +} + +// Returns Y2K-GMT nanosecond time. +Time::Timestamp Now() { + CheckInit(); + + // Record clock moment + auto moment = QueryClockMoment(); + + // Add moment to GMT + return s_gmBegin + static_cast(timeScaleNanoseconds * static_cast(moment)); +} + +// this func is run on first use +void TimeInit() { + // Record absolute clock beginning moment in raw CPU time + ReadTSC(s_absBegin); + BLIZZARD_ASSERT(s_absBegin != 0); + + // Look at system clock's GMT/UTC time as nanoseconds + // This associates a point in GMT with the more precise measurements obtained from reading the timestamp counter +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + // Unix system clock + struct timeval tv; + gettimeofday(&tv, nullptr); + + s_gmBegin = Time::FromUnixTime(tv.tv_sec) + (tv.tv_usec * 1000ULL); +#elif defined(WHOA_SYSTEM_WIN) + // Read Win32 system time + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + // Convert time into Blizzard timestamp + ULARGE_INTEGER ul = {}; + ul.HighPart = ft.dwHighDateTime; + ul.LowPart = ft.dwLowDateTime; + s_gmBegin = Time::FromWinFiletime(ul.QuadPart); +#endif + + // Attempt to figure out the scale of TSC durations in real-time +#if defined(WHOA_SYSTEM_WIN) + // Read frequency with Win32 API + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + + auto ticksPerSecond = static_cast(freq.QuadPart); + auto ticksPerNanosecond = static_cast(ticksPerSecond) / static_cast(TimeConst::TimestampsPerSecond); + + timeScaleNanoseconds = 1.0 / ticksPerNanosecond; +#elif defined(WHOA_SYSTEM_MAC) + // Ask Mach what the base time parameters are + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + + timeScaleNanoseconds = static_cast(timebase.numer) / static_cast(timebase.denom); +#elif defined(WHOA_SYSTEM_LINUX) + // clock_gettime is already attuned to timestamp counter frequency + timeScaleNanoseconds = 1.0; +#endif + timeScaleMicroseconds = timeScaleNanoseconds / 1000.0; + timeScaleMilliseconds = timeScaleNanoseconds / 1000000.0; + timeScaleSeconds = timeScaleNanoseconds / 1000000000.0; +} + +void CheckInit() { + if (s_absBegin == 0) { + TimeInit(); + } +} + +// Wall clock functions. The values returned are of an arbitrary epoch. +// The only guarantee is that the values returned will increase monotonically. + +// Get wall clock time in nanoseconds +uint64_t Nanoseconds() { + CheckInit(); + uint64_t tsc; + ReadTSC(tsc); + return static_cast(static_cast(tsc) * timeScaleNanoseconds); +} + +// Get wall clock time in microseconds +uint64_t Microseconds() { + CheckInit(); + uint64_t tsc; + ReadTSC(tsc); + return static_cast(static_cast(tsc) * timeScaleMicroseconds); +} + +// Get wall clock time in milliseconds +uint64_t Milliseconds() { + CheckInit(); + uint64_t tsc; + ReadTSC(tsc); + return static_cast(static_cast(tsc) * timeScaleMilliseconds); +} + +// Get wall clock time in seconds +uint64_t Seconds() { + CheckInit(); + uint64_t tsc; + ReadTSC(tsc); + return static_cast(static_cast(tsc) * timeScaleSeconds); +} + +} // namespace System_Time +} // namespace Blizzard diff --git a/bc/system/System_Time.hpp b/bc/system/System_Time.hpp new file mode 100644 index 0000000..3cf59b2 --- /dev/null +++ b/bc/system/System_Time.hpp @@ -0,0 +1,25 @@ +#ifndef BC_SYSTEM_TIME_HPP +#define BC_SYSTEM_TIME_HPP + +#include + +#include "bc/time/Types.hpp" + +namespace Blizzard { +namespace System_Time { + +Time::Timestamp Now(); + +void CheckInit(); + +uint64_t QueryClockMoment(); + +uint64_t Nanoseconds(); +uint64_t Microseconds(); +uint64_t Milliseconds(); +uint64_t Seconds(); + +} // namespace System_Time +} // namespace Blizzard + +#endif diff --git a/bc/time/Time.cpp b/bc/time/Time.cpp new file mode 100644 index 0000000..ee733f5 --- /dev/null +++ b/bc/time/Time.cpp @@ -0,0 +1,229 @@ +#include "bc/time/Time.hpp" +#include "bc/time/TimeConst.hpp" +#include "bc/system/System_Time.hpp" + +#if defined(WHOA_SYSTEM_WIN) +#include +#endif + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) +#include +#endif + +#include + +namespace Blizzard { +namespace Time { + +// Global variables + +// Jan = [1], +// Feb = [2] and so on +static uint32_t s_monthDays[14] = { + 0x41F00000, // Invalid? + 0, + 31, + 59, + 90, + 120, + 151, + 182, + 212, + 243, + 273, + 304, + 334, + 365 +}; + +// Functions + +// Convert Blizzard timestamp to UNIX seconds +int32_t ToUnixTime(Timestamp timestamp) { + // Can't return time prior to 1901 + // Return minimum time (1901) in case of underflow + if (timestamp < -3094168447999999999LL) { + return std::numeric_limits::min(); + } + + // 32-bit UNIX sec time suffers from the Year 2038 problem + // Return maximum time (2038) in case of overflow + if (timestamp >= 1200798847000000000LL) { + return std::numeric_limits::max(); + } + + // Go back 30 years + auto y1970 = timestamp + 946684800000000000LL; + // Convert nanoseconds to seconds + return static_cast(y1970 / TimeConst::TimestampsPerSecond); +} + +// TODO: look into making this Y2038-aware. +// Convert UNIX seconds into Blizzard timestamp +Timestamp FromUnixTime(int32_t unixTime) { + // Convert seconds to nanoseconds + auto unixnano = int64_t(unixTime) * TimeConst::TimestampsPerSecond; + // Move forward 30 years + auto y2k = unixnano - 946684800000000000LL; + + return static_cast(y2k); +} + +// Win32 FILETIME to y2k +Timestamp FromWinFiletime(uint64_t winTime) { + if (winTime < 33677863631452242ULL) { + return std::numeric_limits::min(); + } + + if (winTime >= 218145301729837567ULL) { + return std::numeric_limits::max();; + } + + // 1601 (Gregorian) 100-nsec + auto gregorian = static_cast(winTime); + // Convert filetime from 1601 epoch to 2000 epoch. + auto y2k = gregorian - TimeConst::WinFiletimeY2kDifference; + // Convert 100-nsec intervals into nsec intervals + return static_cast(y2k * 100LL); +} + +uint64_t ToWinFiletime(Timestamp y2k) { + return (y2k + TimeConst::WinFiletimeY2kDifference) / 100ULL; +} + +int32_t GetTimeElapsed(uint32_t start, uint32_t end) { + if (end < start) { + return ~start + end; + } + return end - start; +} + +Timestamp GetTimestamp() { + return System_Time::Now(); +} + +uint64_t Nanoseconds() { + return System_Time::Nanoseconds(); +} + +uint64_t Microseconds() { + return System_Time::Microseconds(); +} + +uint32_t Milliseconds() { + return System_Time::Milliseconds(); +} + +uint32_t Seconds() { + return System_Time::Seconds(); +} + +Timestamp MakeTime(const TimeRec& date) { +#if defined(WHOA_SYSTEM_WIN) + // Win32 implementation + FILETIME fileTime = {}; + SYSTEMTIME systemTime = {}; + + systemTime.wYear = static_cast(date.year); + systemTime.wMonth = static_cast(date.month); + systemTime.wDay = static_cast(date.day); + systemTime.wHour = static_cast(date.hour); + systemTime.wMinute = static_cast(date.min); + systemTime.wSecond = static_cast(date.sec); + systemTime.wMilliseconds = 0; + + ::SystemTimeToFileTime(&systemTime, &fileTime); + + ULARGE_INTEGER ul = {}; + ul.HighPart = fileTime.dwHighDateTime; + ul.LowPart = fileTime.dwLowDateTime; + + auto timestamp = FromWinFiletime(ul.QuadPart); + if (timestamp == std::numeric_limits::min() + || timestamp == std::numeric_limits::max()) { + return timestamp; + } + + timestamp += date.nsec; + return timestamp; +#endif + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + // UNIX implementation + struct tm t; + + t.tm_year = date.year - 1900; + t.tm_mon = date.month - 1; + t.tm_mday = date.day; + t.tm_hour = date.hour; + t.tm_min = date.min; + t.tm_sec = date.sec; + + // Convert date into UNIX timestamp + auto unixTime = ::timegm(&t); + auto timestamp = FromUnixTime(unixTime); + if (timestamp == std::numeric_limits< || timestamp == 2147483647L) { + return timestamp; + } + + timestamp += date.nsec; + return timestamp; +#endif +} + +void BreakTime(Timestamp timestamp, TimeRec& date) { + auto nsec = timestamp % TimeConst::TimestampsPerSecond; + + if (nsec < 0) { + nsec += TimeConst::TimestampsPerSecond; + } + +#if defined(WHOA_SYSTEM_WIN) + // Win32 implementation + ULARGE_INTEGER ul = {}; + ul.QuadPart = ToWinFiletime(timestamp); + + FILETIME fileTime = {}; + fileTime.dwHighDateTime = ul.HighPart; + fileTime.dwLowDateTime = ul.LowPart; + SYSTEMTIME systemTime = {}; + ::FileTimeToSystemTime(&fileTime, &systemTime); + + date.day = static_cast(systemTime.wDay); + date.hour = static_cast(systemTime.wHour); + date.min = static_cast(systemTime.wMinute); + date.sec = static_cast(systemTime.wSecond); + date.wday = static_cast(systemTime.wDayOfWeek); + date.year = static_cast(systemTime.wYear); + date.nsec = nsec; + + bool leapYear = (date.year % 400 == 0) || (date.year % 100 != 0 && ((systemTime.wYear & 3) == 0)); + + auto yearDay = s_monthDays[date.month] + -1 + static_cast(systemTime.wDay); + date.yday = yearDay; + if (leapYear && date.month > 2) { + date.yday++; + } +#endif + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + // UNIX implementation + auto unixTime = static_cast(ToUnixTime(timestamp)); + + struct tm t; + ::gmtime_r(&unixTime, &t); + + date.year = t.tm_year + 1900; + date.month = t.tm_mon + 1; + date.day = t.tm_mday; + date.hour = t.tm_hour; + date.min = t.tm_min; + date.sec = t.tm_sec; + date.nsec = nsec; + date.wday = t.tm_wday; + date.yday = t.tm_yday; +#endif +} + +} // namespace Time +} // namespace Blizzard diff --git a/bc/time/Time.hpp b/bc/time/Time.hpp new file mode 100644 index 0000000..04be7e1 --- /dev/null +++ b/bc/time/Time.hpp @@ -0,0 +1,39 @@ +#ifndef BC_TIME_TIME_HPP +#define BC_TIME_TIME_HPP + +#include "bc/time/Types.hpp" + +#include + +namespace Blizzard { +namespace Time { + +int32_t ToUnixTime(Timestamp timestamp); + +Timestamp FromUnixTime(int32_t unixTime); + +// Win32 FILETIME to y2k +Timestamp FromWinFiletime(uint64_t winTime); + +uint64_t ToWinFiletime(Timestamp y2k); + +Timestamp GetTimestamp(); + +int32_t GetTimeElapsed(uint32_t start, uint32_t end); + +Timestamp MakeTime(const TimeRec& date); + +void BreakTime(Timestamp timestamp, TimeRec& date); + +uint64_t Nanoseconds(); + +uint64_t Microseconds(); + +uint32_t Milliseconds(); + +uint32_t Seconds(); + +} // namespace Time +} // namespace Blizzard + +#endif diff --git a/bc/time/TimeConst.hpp b/bc/time/TimeConst.hpp new file mode 100644 index 0000000..a972223 --- /dev/null +++ b/bc/time/TimeConst.hpp @@ -0,0 +1,19 @@ +#ifndef BC_TIME_TIME_CONST_HPP +#define BC_TIME_TIME_CONST_HPP + +#include + +namespace TimeConst { + +// The number of nanoseconds in a second +constexpr int64_t TimestampsPerSecond = 1000000000ULL; + +// amount of win32 filetime units in a second +constexpr int64_t WinUnitsPerSecond = (TimestampsPerSecond / 100ULL); + +// the FILETIME value needed to move from 1601 epoch to the Year 2000 epoch that Blizzard prefers +constexpr int64_t WinFiletimeY2kDifference = 125911584000000000ULL; + +} // namespace TimeConst + +#endif diff --git a/bc/time/Types.hpp b/bc/time/Types.hpp new file mode 100644 index 0000000..e631b09 --- /dev/null +++ b/bc/time/Types.hpp @@ -0,0 +1,28 @@ +#ifndef BC_TIME_TYPES_HPP +#define BC_TIME_TYPES_HPP + +#include + +namespace Blizzard { +namespace Time { + +// Timestamp - nanoseconds starting from 0 == January 1 2000 00:00:00 GMT. +typedef int64_t Timestamp; + +class TimeRec { + public: + int32_t year; + uint32_t month; + uint32_t day; + uint32_t hour; + uint32_t min; + uint32_t sec; + uint32_t nsec; + uint32_t wday; + uint32_t yday; +}; + +} // namespace Time +} // namespace Blizzard + +#endif