1 module more.time; 2 3 import std.typecons : Flag, Yes, No; 4 5 version(Windows) 6 { 7 import core.sys.windows.windows : 8 GetLastError, 9 LARGE_INTEGER, 10 QueryPerformanceFrequency, QueryPerformanceCounter; 11 private immutable long performanceFrequency; 12 static this() 13 { 14 { 15 LARGE_INTEGER temp; 16 if (!QueryPerformanceFrequency(&temp)) 17 { 18 import std.format : format; 19 throw new Exception(format("QueryPeformanceFrequency failed (e=%d)", GetLastError())); 20 } 21 performanceFrequency = temp.QuadPart; 22 } 23 } 24 } 25 26 version (Windows) 27 { 28 pragma(inline) auto ticksPerSecond() { return performanceFrequency; } 29 } 30 else version (Posix) 31 { 32 // FOR NOW: we are just going to represent posix ticks as microseconds 33 enum ticksPerSecond = 1000000; // assume nanosecond for now 34 } 35 36 enum TimeUnit : byte 37 { 38 seconds = 0, // 10 ^ 0 39 deciseconds = -1, // 10 ^ -1 40 centiseconds = -2, // 10 ^ -2 41 milliseconds = -3, // 10 ^ -3 42 microseconds = -6, // 10 ^ -6 43 nanoseconds = -9, // 10 ^ -9 44 picoseconds = -12, // 10 ^ -12 45 } 46 string inverseMultiplierMixin(TimeUnit unit) pure 47 { 48 final switch(unit) 49 { 50 case TimeUnit.seconds: return "1"; 51 case TimeUnit.deciseconds: return "10"; 52 case TimeUnit.centiseconds: return "100"; 53 case TimeUnit.milliseconds: return "1000"; 54 case TimeUnit.microseconds: return "1000000"; 55 case TimeUnit.nanoseconds: return "1000000000"; 56 case TimeUnit.picoseconds: return "1000000000000"; 57 } 58 } 59 60 //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 61 //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 62 // TODO: MOVE THESE MIXIN TEMPLATES TO A DIFFERENT MODULE 63 // maybe something like more.typecons? 64 //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 65 //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 66 mixin template OpUnary(string wrappedFieldName) 67 { 68 mixin(` 69 auto opUnary(string op)() inout 70 { 71 return inout typeof(this)(mixin(op ~ "` ~ wrappedFieldName ~ `")); 72 } 73 `); 74 } 75 mixin template OpBinary(string wrappedFieldName, Flag!"includeWrappedType" includeWrappedType) 76 { 77 mixin(` 78 auto opBinary(string op)(const typeof(this) rhs) inout 79 { 80 return inout typeof(this)(mixin("this.` ~ wrappedFieldName ~ ` " ~ op ~ " rhs.` ~ wrappedFieldName ~ `")); 81 } 82 `); 83 static if (includeWrappedType) mixin(` 84 auto opBinary(string op)(const typeof(` ~ wrappedFieldName ~ `) rhs) inout 85 { 86 return inout typeof(this)(mixin("this.` ~ wrappedFieldName ~ ` " ~ op ~ " rhs")); 87 } 88 `); 89 } 90 mixin template OpCmp(string wrappedFieldName, Flag!"includeWrappedType" includeWrappedType) 91 { 92 mixin(` 93 int opCmp(const typeof(this) rhs) const 94 { 95 return this.` ~ wrappedFieldName ~ `.opCmp(rhs.` ~ wrappedFieldName ~ `); 96 } 97 `); 98 static if (includeWrappedType) mixin(` 99 int opCmp(const typeof(` ~ wrappedFieldName ~ `) rhs) const 100 { 101 return this.` ~ wrappedFieldName ~ `.opCmp(rhs); 102 } 103 `); 104 } 105 mixin template OpCmpIntegral(string wrappedFieldName, Flag!"includeWrappedType" includeWrappedType) 106 { 107 mixin(` 108 int opCmp(const typeof(this) rhs) const 109 { 110 const result = this.` ~ wrappedFieldName ~ ` - rhs.` ~ wrappedFieldName ~ `; 111 if (result < 0) return -1; 112 if (result > 0) return 1; 113 return 0; 114 } 115 `); 116 static if (includeWrappedType) mixin(` 117 int opCmp(const typeof(` ~ wrappedFieldName ~ `) rhs) const 118 { 119 const result = this.` ~ wrappedFieldName ~ ` - rhs; 120 if (result < 0) return -1; 121 if (result > 0) return 1; 122 return 0; 123 } 124 `); 125 } 126 127 128 struct Duration(TimeUnit timeUnit, T) 129 { 130 private T value; 131 void reset() { this.value = cast(T)0; } 132 mixin OpUnary!("value"); 133 mixin OpBinary!("value", No.includeWrappedType); 134 mixin OpCmpIntegral!("value", No.includeWrappedType); 135 auto to(TimeUnit unit)() const 136 { 137 assert(0, "not implemented"); 138 //return value * mixin(unit.inverseMultiplierMixin) / ticksPerSecond; 139 } 140 T to(TimeUnit unit, T)() const 141 { 142 assert(0, "not implemented"); 143 /+ 144 const result = durationTicks * mixin(unit.inverseMultiplierMixin) / performanceFrequency; 145 static if (T.sizeof < result.sizeof) 146 { 147 if ((cast(typeof(result))T.max) < result) 148 { 149 import std.format : format; 150 throw new Exception(format("time value %s is too large to be represented as type %s", result, T.stringof)); 151 } 152 } 153 return cast(T)result; 154 +/ 155 } 156 } 157 158 template TimeTemplate(Policy) 159 { 160 // TODO: make this configurable through the policy somehow 161 alias TimestampInteger = long; 162 163 /** 164 Contains code related to ticks. 165 */ 166 struct Ticks 167 { 168 /** 169 Return the number of units per 1 tick. 170 */ 171 static DurationTicks unitsPerTick(TimeUnit unit)() pure 172 { 173 return DurationTicks(ticksPerSecond * mixin(unit.inverseMultiplierMixin)); 174 } 175 /** 176 Return the number ticks for 1 unit. 177 */ 178 static DurationTicks per(TimeUnit unit)() pure 179 { 180 return DurationTicks(ticksPerSecond / mixin(unit.inverseMultiplierMixin)); 181 } 182 } 183 184 /** 185 A `Timestamp` is an integer value that can roll. Timestamps are compared 186 by finding the shorter distance between 2 values. 187 */ 188 struct TimestampTicks 189 { 190 private TimestampInteger ticks; 191 192 TimestampTicks offsetWith(const DurationTicks duration) const 193 { 194 return TimestampTicks(this.ticks + duration.durationTicks); 195 } 196 DurationTicks diff(const TimestampTicks rhs) const 197 { 198 return DurationTicks(this.ticks - rhs.ticks); 199 } 200 int opCmp(const TimestampTicks rhs) const 201 { 202 const result = this.ticks - rhs.ticks; 203 if (result < 0) return -1; 204 if (result > 0) return 1; 205 return 0; 206 } 207 /** 208 Get a timestamp for the current time right `now`. 209 */ 210 static TimestampTicks now() 211 { 212 version (Windows) 213 { 214 static assert(is (TimestampInteger == long)); 215 LARGE_INTEGER ticksInteger; 216 if (!QueryPerformanceCounter(&ticksInteger)) 217 { 218 import std.format : format; 219 throw new Exception(format("QueryPerformanceCounter failed (e=%d)", GetLastError())); 220 } 221 return TimestampTicks(ticksInteger.QuadPart); 222 } 223 else version (Posix) 224 { 225 import core.stdc.errno : errno; 226 import core.sys.posix.time : clock_gettime, timespec; 227 import core.sys.linux.time : CLOCK_MONOTONIC_RAW; 228 timespec now; 229 if (0 != clock_gettime(CLOCK_MONOTONIC_RAW, &now)) 230 { 231 import std.format : format; 232 throw new Exception(format("clock_gettime with CLOCK_MONOTONIC_RAW failed (e=%d)", errno)); 233 } 234 // TODO: detect overflow 235 return TimestampTicks((cast(TimestampInteger)now.tv_sec * ticksPerSecond) + 236 (now.tv_nsec * ticksPerSecond / 1000000000)); 237 } 238 } 239 } 240 unittest 241 { 242 static void testAt(TimestampInteger value) 243 { 244 const valueTimestamp = TimestampTicks(value); 245 assert(valueTimestamp == valueTimestamp); 246 assert(valueTimestamp <= valueTimestamp); 247 assert(valueTimestamp >= valueTimestamp); 248 assert(valueTimestamp < TimestampTicks(value + 1)); 249 assert(valueTimestamp > TimestampTicks(value - 1)); 250 assert(valueTimestamp < TimestampTicks(value + (TimestampInteger.max / 6))); 251 assert(valueTimestamp > TimestampTicks(value - (TimestampInteger.max / 6))); 252 } 253 // TODO: only do these ones if TimestampInteger is signed 254 { 255 testAt(TimestampInteger.min); 256 testAt(TimestampInteger.min / 2); 257 testAt(-1); 258 } 259 testAt(0); 260 testAt(1); 261 testAt(TimestampInteger.max / 2); 262 testAt(TimestampInteger.max); 263 } 264 265 /* 266 TODO: probably just be an alias to Duration!(TimeUnit.ticks, TimestampInteger). 267 */ 268 /** 269 TODO: we may want a signed/unsigned duration. 270 */ 271 struct DurationTicks 272 { 273 TimestampInteger durationTicks; 274 275 mixin OpUnary!("durationTicks"); 276 mixin OpBinary!("durationTicks", Yes.includeWrappedType); 277 mixin OpCmpIntegral!("durationTicks", Yes.includeWrappedType); 278 auto to(TimeUnit unit)() const 279 { 280 return durationTicks * mixin(unit.inverseMultiplierMixin) / ticksPerSecond; 281 } 282 T to(TimeUnit unit, T)() const 283 { 284 const result = durationTicks * mixin(unit.inverseMultiplierMixin) / ticksPerSecond; 285 static if (T.sizeof < result.sizeof) 286 { 287 if ((cast(typeof(result))T.max) < result) 288 { 289 import std.format : format; 290 throw new Exception(format("time value %s is too large to be represented as type %s", result, T.stringof)); 291 } 292 } 293 return cast(T)result; 294 } 295 } 296 unittest 297 { 298 static void testAt(DurationTicks value) 299 { 300 assert(value == value); 301 assert(value <= value); 302 assert(value >= value); 303 assert(value < value + 1); 304 assert(value > value - 1); 305 assert(value < value + (TimestampInteger.max / 6)); 306 assert(value > value - (TimestampInteger.max / 6)); 307 } 308 // TODO: only do these ones if TimestampInteger is signed 309 { 310 testAt(DurationTicks(TimestampInteger.min)); 311 testAt(DurationTicks(TimestampInteger.min / 2)); 312 testAt(DurationTicks(-1)); 313 } 314 testAt(DurationTicks(0)); 315 testAt(DurationTicks(1)); 316 testAt(DurationTicks(TimestampInteger.max / 2)); 317 testAt(DurationTicks(TimestampInteger.max)); 318 } 319 320 /** 321 Used to track "laps" of time, where each call to `lap` will return the time 322 since the last call to `lap`. 323 */ 324 struct LapTimerTicks 325 { 326 import more.types : unconst; 327 328 private TimestampTicks startLapTime; 329 330 /** 331 Get the current lap start time. 332 */ 333 TimestampTicks getStartLapTime() const 334 { 335 return startLapTime.unconst; 336 } 337 /** 338 Set a custom value for the lap start time. 339 */ 340 void setStartLapTime(TimestampTicks startLapTime) 341 { 342 this.startLapTime = startLapTime; 343 } 344 /** 345 Change the lap start time. 346 */ 347 void moveLapStartTime(DurationTicks duration) 348 { 349 this.startLapTime = this.startLapTime.offsetWith(duration); 350 } 351 /** 352 Start the next lap. This will get the current time and set it so that the next 353 call to lap will take the difference between the new time with the time this was called. 354 */ 355 void start() 356 { 357 startLapTime = TimestampTicks.now(); 358 } 359 /** 360 Return the duration of time since the last call to `lap` (or `start`). 361 */ 362 DurationTicks lap(Flag!"enforceNonNegative" enforceNonNegative = Yes.enforceNonNegative)() 363 { 364 const now = TimestampTicks.now(); 365 auto duration = now.diff(startLapTime); 366 static if (enforceNonNegative) 367 { 368 if (duration < 0) 369 { 370 import std.format : format; 371 assert(0, format("LapTimer.lap has a negative duration (startLapTime %s, now %s, diff %s)", 372 startLapTime, now, duration)); 373 } 374 } 375 this.startLapTime = now; 376 return duration; 377 } 378 /** 379 Check the current lap time without starting a new lap. 380 */ 381 DurationTicks peek(Flag!"enforceNonNegative" enforceNonNegative = Yes.enforceNonNegative)() const 382 { 383 const now = TimestampTicks.now(); 384 auto duration = now.diff(startLapTime); 385 static if (enforceNonNegative) 386 { 387 if (duration < 0) 388 { 389 import std.format : format; 390 assert(0, format("LapTimer.lap peek has a negative duration (startLapTime %s, now %s, diff %s)", 391 startLapTime, now, duration)); 392 } 393 } 394 return duration; 395 } 396 } 397 /** 398 A timer that can be started/stopped and tracks the total duration of time while running. 399 */ 400 struct StopWatchTicks 401 { 402 private DurationTicks _totalDuration; 403 private TimestampTicks _timeStarted; 404 private bool _running; 405 406 @property bool running() const { return _running; } 407 @property DurationTicks totalDuration() const { return _totalDuration; } 408 409 void start() 410 in { assert(!running); } do 411 { 412 _running = true; 413 _timeStarted = TimestampTicks.now(); 414 } 415 DurationTicks stop() 416 in { assert(running); } do 417 { 418 auto duration = TimestampTicks.now().diff(_timeStarted); 419 assert(duration >= 0); 420 _totalDuration = _totalDuration + duration; 421 _running = false; 422 return duration; 423 } 424 } 425 } 426 427 unittest 428 { 429 struct TimePolicy { } 430 alias TimestampTicks = TimeTemplate!TimePolicy.TimestampTicks; 431 }