@@ -34,57 +34,154 @@ import Swift
3434public protocol Clock < Duration> : Sendable {
3535 associatedtype Duration
3636 associatedtype Instant : InstantProtocol where Instant. Duration == Duration
37+ associatedtype CanonicalClock : Clock = Self
3738
3839 var now : Instant { get }
3940 var minimumResolution : Instant . Duration { get }
4041
4142#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
4243 func sleep( until deadline: Instant , tolerance: Instant . Duration ? ) async throws
43- #endif
4444
45- /// The traits associated with this clock instance.
45+ /// Run the given job on an unspecified executor at some point
46+ /// after the given instant.
47+ ///
48+ /// Parameters:
49+ ///
50+ /// - job: The job we wish to run
51+ /// - at instant: The time at which we would like it to run.
52+ /// - tolerance: The ideal maximum delay we are willing to tolerate.
53+ ///
4654 @available ( StdlibDeploymentTarget 6 . 2 , * )
47- var traits : ClockTraits { get }
55+ func run( _ job: consuming ExecutorJob ,
56+ at instant: Instant , tolerance: Duration ? )
4857
49- /// Convert a Clock-specific Duration to a Swift Duration
58+ /// Enqueue the given job on the specified executor at some point after the
59+ /// given instant.
5060 ///
51- /// Some clocks may define `C.Duration` to be something other than a
52- /// `Swift.Duration`, but that makes it tricky to convert timestamps
53- /// between clocks, which is something we want to be able to support.
54- /// This method will convert whatever `C.Duration` is to a `Swift.Duration`.
61+ /// The default implementation uses the `run` method to trigger a job that
62+ /// does `executor.enqueue(job)`. If a particular `Clock` knows that the
63+ /// executor it has been asked to use is the same one that it will run jobs
64+ /// on, it can short-circuit this behaviour and directly use `run` with
65+ /// the original job.
5566 ///
5667 /// Parameters:
5768 ///
58- /// - from duration: The `Duration` to convert
69+ /// - job: The job we wish to run
70+ /// - on executor: The executor on which we would like it to run.
71+ /// - at instant: The time at which we would like it to run.
72+ /// - tolerance: The ideal maximum delay we are willing to tolerate.
5973 ///
60- /// Returns: A `Swift.Duration` representing the equivalent duration, or
61- /// `nil` if this function is not supported.
6274 @available ( StdlibDeploymentTarget 6 . 2 , * )
63- func convert( from duration: Duration ) -> Swift . Duration ?
75+ func enqueue( _ job: consuming ExecutorJob ,
76+ on executor: some Executor ,
77+ at instant: Instant , tolerance: Duration ? )
78+ #endif
6479
65- /// Convert a Swift Duration to a Clock-specific Duration
80+ /// Obtain the equivalent, canonical clock, or `nil` if this clock
81+ /// is already canonical.
82+ ///
83+ /// A non-canonical clock is a clock with some relationship to a base,
84+ /// canonical, clock, to which it can be converted.
85+ @available ( StdlibDeploymentTarget 6 . 2 , * )
86+ var canonicalClock : CanonicalClock ? { get }
87+
88+ /// Convert an Instant to the canonical clock's equivalent Instant.
6689 ///
6790 /// Parameters:
6891 ///
69- /// - from duration: The `Swift.Duration ` to convert.
92+ /// - instant: The `Instant ` to convert.
7093 ///
71- /// Returns: A `Duration` representing the equivalent duration, or
72- /// `nil` if this function is not supported.
73- @ available ( StdlibDeploymentTarget 6 . 2 , * )
74- func convert ( from duration : Swift . Duration ) -> Duration ?
94+ /// Returns:
95+ ///
96+ /// The equivalent `CanonicalClock.Instant`.
97+ func convertToCanonical ( instant : Instant ) -> CanonicalClock . Instant
7598
76- /// Convert an `Instant` from some other clock's `Instant`
99+ /// Convert a Duration to the canonical clock's equivalent Duration.
77100 ///
78101 /// Parameters:
79102 ///
80- /// - instant: The instant to convert.
81- // - from clock: The clock to convert from.
103+ /// - duration: The `Duration` to convert.
104+ ///
105+ /// Returns:
106+ ///
107+ /// The equivalent `CanonicalClock.Duration`.
108+ func convertToCanonical( duration: Duration ) -> CanonicalClock . Duration
109+
110+ /// Convert an Instant to the canonical clock's equivalent Instant.
111+ ///
112+ /// Parameters:
113+ ///
114+ /// - instant: The `Instant` to convert, or `nil`.
115+ ///
116+ /// Returns:
117+ ///
118+ /// The equivalent `CanonicalClock.Instant`, or `nil` if `instant` was `nil`.
119+ func maybeConvertToCanonical( instant: Instant ? ) -> CanonicalClock . Instant ?
120+
121+ /// Convert a Duration to the canonical clock's equivalent Duration.
122+ ///
123+ /// Parameters:
82124 ///
83- /// Returns: An `Instant` representing the equivalent instant, or
84- /// `nil` if this function is not supported.
125+ /// - duration: The `Duration` to convert, or `nil`.
126+ ///
127+ /// Returns:
128+ ///
129+ /// The equivalent `CanonicalClock.Duration`, or `nil` if `duration` was `nil`.
130+ func maybeConvertToCanonical( duration: Duration ? ) -> CanonicalClock . Duration ?
131+ }
132+
133+ extension Clock {
134+ // The default implementation works by creating a trampoline and calling
135+ // the run() method.
85136 @available ( StdlibDeploymentTarget 6 . 2 , * )
86- func convert< OtherClock: Clock > ( instant: OtherClock . Instant ,
87- from clock: OtherClock ) -> Instant ?
137+ public func enqueue( _ job: consuming ExecutorJob ,
138+ on executor: some Executor ,
139+ at instant: Instant , tolerance: Duration ? ) {
140+ let trampoline = job. createTrampoline ( to: executor)
141+ run ( trampoline, at: instant, tolerance: tolerance)
142+ }
143+
144+ // Clocks that do not implement run will fatalError() if you try to use
145+ // them with an executor that does not understand them.
146+ @available ( StdlibDeploymentTarget 6 . 2 , * )
147+ public func run( _ job: consuming ExecutorJob ,
148+ at instant: Instant , tolerance: Duration ? ) {
149+ fatalError ( " \( Self . self) does not implement run(_:at:tolerance:). " )
150+ }
151+ }
152+
153+ // Default implementations for canonicalization support
154+ extension Clock where CanonicalClock == Self {
155+ public var canonicalClock : CanonicalClock ? { return nil }
156+
157+ public func convertToCanonical( duration: Duration ) -> CanonicalClock . Duration {
158+ return duration
159+ }
160+
161+ public func convertToCanonical( instant: Instant ) -> CanonicalClock . Instant {
162+ return instant
163+ }
164+ }
165+
166+ // nil-propagating versions of convertToCanonical()
167+ extension Clock {
168+ public func maybeConvertToCanonical( duration: Duration ? )
169+ -> CanonicalClock . Duration ?
170+ {
171+ if let duration {
172+ return convertToCanonical ( duration: duration)
173+ }
174+ return nil
175+ }
176+
177+ public func maybeConvertToCanonical( instant: Instant ? )
178+ -> CanonicalClock . Instant ?
179+ {
180+ if let instant {
181+ return convertToCanonical ( instant: instant)
182+ }
183+ return nil
184+ }
88185}
89186
90187@available ( StdlibDeploymentTarget 5 . 7 , * )
@@ -140,44 +237,6 @@ extension Clock {
140237 }
141238}
142239
143- @available ( StdlibDeploymentTarget 6 . 2 , * )
144- extension Clock {
145- // For compatibility, return `nil` if this is not implemented
146- public func convert( from duration: Duration ) -> Swift . Duration ? {
147- return nil
148- }
149-
150- public func convert( from duration: Swift . Duration ) -> Duration ? {
151- return nil
152- }
153-
154- public func convert< OtherClock: Clock > ( instant: OtherClock . Instant ,
155- from clock: OtherClock ) -> Instant ? {
156- let ourNow = now
157- let otherNow = clock. now
158- let otherDuration = otherNow. duration ( to: instant)
159-
160- // Convert to `Swift.Duration`
161- guard let duration = clock. convert ( from: otherDuration) else {
162- return nil
163- }
164-
165- // Convert from `Swift.Duration`
166- guard let ourDuration = convert ( from: duration) else {
167- return nil
168- }
169-
170- return ourNow. advanced ( by: ourDuration)
171- }
172- }
173-
174- @available ( StdlibDeploymentTarget 6 . 2 , * )
175- extension Clock where Duration == Swift . Duration {
176- public func convert( from duration: Duration ) -> Duration ? {
177- return duration
178- }
179- }
180-
181240#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
182241@available ( StdlibDeploymentTarget 5 . 7 , * )
183242extension Clock {
@@ -196,44 +255,6 @@ extension Clock {
196255}
197256#endif
198257
199- /// Represents traits of a particular Clock implementation.
200- ///
201- /// Clocks may be of a number of different varieties; executors will likely
202- /// have specific clocks that they can use to schedule jobs, and will
203- /// therefore need to be able to convert timestamps to an appropriate clock
204- /// when asked to enqueue a job with a delay or deadline.
205- ///
206- /// Choosing a clock in general requires the ability to tell which of their
207- /// clocks best matches the clock that the user is trying to specify a
208- /// time or delay in. Executors are expected to do this on a best effort
209- /// basis.
210- @available ( StdlibDeploymentTarget 6 . 2 , * )
211- public struct ClockTraits : OptionSet {
212- public let rawValue : UInt32
213-
214- public init ( rawValue: UInt32 ) {
215- self . rawValue = rawValue
216- }
217-
218- /// Clocks with this trait continue running while the machine is asleep.
219- public static let continuous = ClockTraits ( rawValue: 1 << 0 )
220-
221- /// Indicates that a clock's time will only ever increase.
222- public static let monotonic = ClockTraits ( rawValue: 1 << 1 )
223-
224- /// Clocks with this trait are tied to "wall time".
225- public static let wallTime = ClockTraits ( rawValue: 1 << 2 )
226- }
227-
228- @available ( StdlibDeploymentTarget 6 . 2 , * )
229- extension Clock {
230- /// The traits associated with this clock instance.
231- @available ( StdlibDeploymentTarget 6 . 2 , * )
232- public var traits : ClockTraits {
233- return [ ]
234- }
235- }
236-
237258enum _ClockID : Int32 {
238259 case continuous = 1
239260 case suspending = 2
0 commit comments