Repository navigation
Expand file tree
/
Copy pathJavaScriptEventLoop+YieldInterval.swift
More file actions
103 lines (90 loc) · 3.16 KB
/
Copy pathJavaScriptEventLoop+YieldInterval.swift
File metadata and controls
103 lines (90 loc) · 3.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// Clock and host task scheduling for `JavaScriptEventLoop.yieldInterval`.
import JavaScriptKit
import _Concurrency
#if compiler(>=5.5)
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
extension JavaScriptEventLoop {
#if hasFeature(Embedded)
// `ContinuousClock` is unavailable in Embedded Swift; use `performance.now()` in milliseconds.
typealias DrainInstant = Double
func drainClockNow() -> DrainInstant {
if performanceNow == nil {
let performance = JSObject.global.performance.object!
performanceNow = (performance, performance.now.function!)
}
return performanceNow!.now(this: performanceNow!.performance).number!
}
func drainElapsed(since start: DrainInstant) -> Duration {
.milliseconds(drainClockNow() - start)
}
#else
typealias DrainInstant = ContinuousClock.Instant
func drainClockNow() -> DrainInstant {
ContinuousClock.now
}
func drainElapsed(since start: DrainInstant) -> Duration {
start.duration(to: .now)
}
#endif
/// Runs `body` in a new JavaScript task via `setImmediate`, `MessageChannel`, or `setTimeout`.
func queueHostTask(_ body: @escaping () -> Void) {
if hostTaskScheduler == nil {
hostTaskScheduler = Self.makeHostTaskScheduler()
}
hostTaskScheduler!(body)
}
private static func makeHostTaskScheduler() -> (@escaping () -> Void) -> Void {
let global = JSObject.global
if let setImmediate = global.setImmediate.function {
return { body in
_ = setImmediate(
JSOneshotClosure { _ in
body()
return .undefined
}
)
}
}
if let messageChannel = global.MessageChannel.function {
// Unlike nested `setTimeout` calls, messages are not clamped to 4 ms.
let channel = messageChannel.new(arguments: [JSValue]())
let sender = channel.port1.object!
let postMessage = sender.postMessage.function!
let pending = PendingHostTasks()
let receiver = JSClosure { _ in
pending.runAll()
return .undefined
}
channel.port2.object!.onmessage = .object(receiver)
return { body in
pending.bodies.append(body)
// Keep `receiver` alive as long as the scheduler.
withExtendedLifetime(receiver) {
_ = postMessage(this: sender, JSValue.undefined)
}
}
}
let setTimeout = global.setTimeout.function!
return { body in
_ = setTimeout(
JSOneshotClosure { _ in
body()
return .undefined
},
0
)
}
}
}
/// Bodies waiting for the next `MessageChannel` message.
private final class PendingHostTasks {
var bodies: [() -> Void] = []
func runAll() {
let bodies = self.bodies
self.bodies = []
for body in bodies {
body()
}
}
}
#endif