Skip to main content

CLOCK_JS

Constant CLOCK_JS 

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

The clock: every "250ms", every frame and after "2s".

Its own module for the same reason as the modules above, and the size gate is the reason it is not in signal.js: a null program links signal.js, so anything put there is shipped to every program forever. It imports signal.js and nothing else — a clock writes a cell and touches no DOM.