@@ -55,7 +55,7 @@ and solves the problem of specifying its unit.
55
55
While ` measureTime ` function is convenient to measure time of executing a block of code, there are cases when
56
56
the beginning and end of a time interval measurement cannot be placed in the scope of a function. These cases require
57
57
to notch the moment of the beginning, store it somewhere and then calculate the elapsed time from that moment.
58
- Also the elapsed time can be noted not only once, as it is with ` measureTime ` , but multiple times.
58
+ Also, the elapsed time can be noted not only once, as it is with ` measureTime ` , but multiple times.
59
59
60
60
The interface ` TimeSource ` is a basic building block both for implementing ` measureTime ` and for covering the case above.
61
61
It allows to obtain a ` TimeMark ` that captures the current instant of the time source. That ` TimeMark ` can be queried later
@@ -72,6 +72,34 @@ timeout is not expired, and positive values if it is. For convenience, instead o
72
72
a negative duration one can use ` hasPassedNow ` /` hasNotPassedNow ` functions of the ` TimeMark ` .
73
73
This way timeout can be represented by a single ` TimeMark ` instead of a ` TimeMark ` and a ` Duration ` .
74
74
75
+ #### Noting time between two time marks
76
+
77
+ When measuring elapsed time from a single origin point, it's enough to get a ` TimeMark ` at the origin point
78
+ and then use its ` elapsedNow ` function.
79
+
80
+ However, if a use case requires measuring at some point several elapsed time values from several origin points,
81
+ it is hard to do consistently using only the ` elapsedNow ` function because each its call may return slightly different
82
+ value depending on the * current* time in the time source produced these origin time marks.
83
+
84
+ ``` kotlin
85
+ val elapsed1 = originMark1.elapsedNow()
86
+ val elapsed2 = originMark2.elapsedNow()
87
+ // the difference between elapsed1 and elapsed2 depends on the order of calls and
88
+ // on the unpredictable delay between these two calls
89
+ ```
90
+
91
+ When, for example, doing several animation calculations, it's important to measure time elapsed from the start
92
+ of each animation with regard to the frame rendering time moment consistently, and such unpredictable difference may
93
+ result in unwanted visual artifacts.
94
+
95
+ To support this case, a time source can return time marks comparable and subtractable with each other, so it is possible
96
+ to mark the time moment of a frame once and then calculate all animation elapsed times consistently:
97
+
98
+ ``` kotlin
99
+ val markNow = timeSource.markNow()
100
+ val elapsed1 = markNow - originMark1
101
+ val elapsed2 = markNow - originMark2
102
+ ```
75
103
76
104
## Similar API review
77
105
@@ -111,6 +139,11 @@ Another approach that was considered is introducing the class `TimeStamp` and th
111
139
return ` Duration ` . However, it was concluded to be error-prone because it would be too easy to mix two timestamps taken
112
140
from unrelated time sources in a single expression and get nonsense in the result.
113
141
142
+ ** Update:** after considering use cases (see [ Noting time between two time marks] ( #noting-time-between-two-time-marks ) ),
143
+ we decided to introduce a subtype of ` TimeMark ` , ` ComparableTimeMark ` , that allows arithmetic operations
144
+ (subtraction, comparison) on time marks obtained from the same time source,
145
+ even though mixing time marks from different time sources would lead to a runtime exception.
146
+
114
147
## API details
115
148
116
149
### Duration
@@ -282,9 +315,64 @@ if (expirationMark.hasPassedNow()) {
282
315
Instances of ` TimeMark ` are usually not serializable because it isn't possible to restore the captured time point upon deserialization
283
316
in a meaningful way.
284
317
318
+ ### Comparable time marks
319
+
320
+ ` ComparableTimeMark ` interface extends the ` TimeMark ` interface with the functions to compare two time marks with each other
321
+ and to calculate time elapsed between them.
322
+
323
+ ``` kotlin
324
+ interface ComparableTimeMark : TimeMark , Comparable <ComparableTimeMark > {
325
+ abstract override operator fun plus (duration : Duration ): ComparableTimeMark
326
+ open override operator fun minus (duration : Duration ): ComparableTimeMark = plus(- duration)
327
+ operator fun minus (other : ComparableTimeMark ): Duration
328
+ override operator fun compareTo (other : ComparableTimeMark ): Int = (this - other) compareTo (Duration .ZERO )
329
+
330
+ override fun equals (other : Any? ): Boolean
331
+ override fun hashCode (): Int
332
+ }
333
+ ```
334
+
335
+ In order to represent a time source from which comparable time marks can be obtained, a specialized time source interface is introduced:
336
+
337
+ ``` kotlin
338
+ interface TimeSource {
339
+ interface WithComparableMarks : TimeSource {
340
+ override fun markNow (): ComparableTimeMark
341
+ }
342
+ }
343
+ ```
344
+
345
+ The comparison ` timeMark1 < timeMark2 ` returns true if ` timeMark1 ` represents the moment earlier than ` timeMark2 ` .
346
+ If the time marks were obtained from different time sources, both comparison and the ` minus ` operator throw an ` IllegalArgumentException ` .
347
+
348
+ Comparable time marks also implement structural equality contract with the ` equals ` and ` hashCode ` functions consistent with
349
+ the ` compareTo ` operator, so that if ` timeMark1 == timeMark2 ` , then ` timeMark1 compareTo timeMark2 == 0 ` .
350
+ However, the equality operator doesn't throw an exception when time marks are from different time sources, it just returns ` false ` .
351
+
352
+ #### Alternatives considered
353
+
354
+ - Instead of introducing separate interfaces for comparable time marks and time sources returning them, introduce functions
355
+ in the ` TimeMark ` base interface.
356
+ - Comparing time marks is not always needed, but supporting it in the base interface would complicate all ` TimeSource `
357
+ implementations.
358
+
359
+ - Instead of introducing specialized time source for comparable time marks, parametrize the base ` TimeSource ` interface
360
+ with a generic time mark type, then ` TimeSource<*> ` , ` TimeSource<TimeMark> ` , and ` TimeSource<ComparableTimeMark> ` types
361
+ can be used.
362
+
363
+ ``` kotlin
364
+ interface TimeSource <out M : TimeMark > {
365
+ fun markNow (): M
366
+ }
367
+ ```
368
+ - We do not expect many different parametrizations of ` TimeSource ` interface, so dealing with pesky ` <> ` brackets in
369
+ common use cases would be tedious.
370
+ - Parametrization only affects the return type of one function, so it doesn't bring much value compared to covariant
371
+ override in a more specialized interface.
372
+
285
373
### Monotonic TimeSource
286
374
287
- ` TimeSource ` has the nested object ` Monotonic ` that implements ` TimeSource ` and provides the default source of monotonic
375
+ ` TimeSource ` has the nested object ` Monotonic ` that implements ` TimeSource.WithComparableMarks ` and provides the default source of monotonic
288
376
time in the platform.
289
377
290
378
Different platforms provide different sources of monotonic time:
@@ -325,17 +413,20 @@ This allows to make such time mark an inline value class:
325
413
326
414
``` kotlin
327
415
public interface TimeSource {
328
- public object Monotonic {
416
+ public object Monotonic : TimeSource.WithComparableMarks {
329
417
330
418
override fun markNow (): ValueTimeMark = .. .
331
419
332
420
333
- public value class ValueTimeMark internal constructor(internal val reading : ValueTimeMarkReading ) : TimeMark {
421
+ public value class ValueTimeMark internal constructor(internal val reading : ValueTimeMarkReading ) : ComparableTimeMark {
334
422
override fun elapsedNow (): Duration = .. .
335
423
override fun plus (duration : Duration ): ValueTimeMark = .. .
336
424
override fun minus (duration : Duration ): ValueTimeMark = .. .
337
425
override fun hasPassedNow (): Boolean = ! elapsedNow().isNegative()
338
426
override fun hasNotPassedNow (): Boolean = elapsedNow().isNegative()
427
+
428
+ override fun minus (other : ValueTimeMark ): Duration = .. .
429
+ operator fun compareTo (other : ValueTimeMark ): Int = .. .
339
430
}
340
431
}
341
432
}
@@ -353,16 +444,18 @@ and thus working with `TimeSource.Monotonic` through its `TimeSource` interface
353
444
The function ` measureTime ` without a ` TimeSource ` also benefits from that, as it obtains a time mark from the default monotonic
354
445
time source.
355
446
356
- ### AbstractLongTimeSource/AbstractDoubleTimeSource
447
+ ` ValueTimeMark ` is a ` ComparableTimeMark ` , thus it allows comparing it with other ` ValueTimeMark ` values.
448
+
449
+ ### AbstractLongTimeSource
357
450
358
- These two abstract classes are provided to make it easy implementing own ` TimeSource `
359
- from a source that returns the current timestamp as a number.
451
+ This abstract class is provided to make it easy implementing own ` TimeSource `
452
+ from a source that returns the current timestamp as an integer number.
360
453
361
454
``` kotlin
362
- public abstract class AbstractLongTimeSource (protected val unit : DurationUnit ) : TimeSource {
455
+ public abstract class AbstractLongTimeSource (protected val unit : DurationUnit ) : TimeSource.WithComparableMarks {
363
456
protected abstract fun read (): Long
364
457
365
- override fun markNow (): TimeMark = .. .
458
+ override fun markNow (): ComparableTimeMark = .. .
366
459
}
367
460
```
368
461
0 commit comments