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
104
105
106
// The clock: `every "250ms"`, `every frame`, `after "2s"`.
//
// **There is no callback here, and that is the point.** Every function in
// this file takes a number and returns a *read function* — the same read
// function `signal()` returns — so what the emitter writes for a clock
// declaration is one `const` and nothing else. The scheduler's callback
// exists, but it is closed over inside this file and no program can reach
// it: all it does is put a number in a cell. Everything downstream is the
// `derived` and the bindings the language already had.
//
// That is the whole answer to "how does a timer enter a dataflow graph
// without an escape hatch": it does not enter as control flow at all. It
// is a source, like a text box, and the browser is its writer.
//
// **Its own module, for the reason `list.js` and `markup.js` are.** The
// null-program size gate (`zdc-bench/tests/scaling.rs`) keeps a 2 kB
// reserve against Swift's number, and the fix when a runtime addition eats
// into it is to ship the addition only to the programs that use it rather
// than to move the ceiling. A program with no clock links nothing here.
import from './signal.js';
/** `every "<duration>"` — milliseconds elapsed, written every `ms`.
*
* The value is *elapsed time*, not a tick count, and not the wall clock.
*
* - Not a count, because a count answers "how many" and almost every use
* wants "how long": a progress bar, a countdown and a carousel are all
* arithmetic on a duration, and a count makes each of them divide by the
* interval to get back what the timer already knew.
* - Not `Date.now()`, because a signal holding the wall clock changes
* every time it is written whether or not anything moved, and because
* `static` and `server` placements are refused anyway — so the one
* question a wall clock answers ("what time is it") is the prelude's
* `clock`, which is where it already lives.
*
* Measured from the same base every time rather than accumulated, so a
* late tick does not shift every later one: `setInterval` drifts, and a
* clock whose drift compounds is one that visibly disagrees with a second
* clock beside it after a minute. */
export
/** `every frame` — milliseconds elapsed, written once per repaint.
*
* The base is the *first* frame's timestamp rather than the time this was
* called: `requestAnimationFrame` hands the callback a
* `DOMHighResTimeStamp` measured from the document's time origin, so
* subtracting a `stamp()` taken during module evaluation would start the
* signal at however long the page took to load. Subtracting the first
* frame starts it at zero, which is what an animation wants and what makes
* two frame signals declared at different moments comparable.
*
* **A cancelled frame loop must not schedule another one.** `cancel`
* cancels the frame already booked; the `live` flag is what stops the
* callback that is *mid-flight* — one already dequeued by the browser —
* from booking its successor after the dispose ran. Without it a disposed
* loop survives roughly half the time, which is exactly the kind of leak
* that never reproduces on the machine it is reported from. */
export
/** `after "<duration>"` — `false` until `ms` have passed, then `true`.
*
* One-shot, so there is nothing to keep alive afterwards: the timer clears
* itself by firing. The `onCleanup` covers the other case — a view thrown
* away before the delay elapses, where the write would land in a cell
* nothing reads and the browser would hold the closure until it did. */
export
/** A monotonic-ish millisecond reading.
*
* `performance.now()` where there is one, because it does not jump when
* the system clock is corrected — an NTP step or a user changing the time
* would otherwise make an interval signal go backwards, and every
* subtraction downstream of it negative. `Date.now()` is the fallback and
* not the default. */