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 }