gitoriaLog in with ident

antcolony

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Main branchmain3a4d0324antcolony#37: a too-long report gets up to 3 fix tries, finished work is never thrown away for lengthmremain/plugins/time/server.hl

4.7 KB

  1. \* hl:time — the clock, and TIME AS AN EVENT SOURCE. Native realm wrapper (see
  2. server.js for the twin the JavaScript target and the BROWSER both run).
  3. THE CLOCK is three calls, deliberately: a wall clock you can store, a
  4. human/interchange rendering of it, and a monotonic counter you can subtract.
  5. Everything else (formatting, timezones, arithmetic) is a userland concern.
  6. TIME AS AN EVENT SOURCE is `every`, `after` and `until`. Each hands back a
  7. `Timer` you subscribe the way you subscribe a spawned process:
  8. let interval = every(360) \\ every six minutes, forever
  9. on interval.tick(t) { … }
  10. interval.stop() \\ …until you stop it
  11. let timeout = after(6) \\ once, six seconds from now
  12. on timeout.tick() { … } \\ and then the program can end
  13. let deadline = until(t) \\ once, at that epoch-ms instant
  14. on deadline.tick() { … }
  15. SECONDS, EVERYWHERE, FRACTIONS ALLOWED. `every(0.25)` is four times a second
  16. and `after(0.5)` is half a second. One unit for every duration in this
  17. package, because mixing them is a defect this project has already had to
  18. diagnose in someone else's code — a SECONDS-valued lifetime compared against
  19. millisecond deltas turned a year into 8¾ hours. `now()` stays milliseconds
  20. because it is an INSTANT, not a duration.
  21. THE FLOOR IS 4 ms, IN BOTH REALMS, and it is measured rather than quoted.
  22. Server-side a timerfd carries nanoseconds and the event loop blocks on it, so
  23. there is no floor at all; the BROWSER has one — HTML clamps a nested
  24. `setTimeout(…, 0)` to 4ms and a 1ms `setInterval` delivers ~4ms periods
  25. (measured by `tests/browser/tests/66-timers.mjs`, which prints its number on
  26. every run). The same `on t.tick()` handler is meant to run in both realms, so
  27. a value below the floor is RAISED to it on both sides rather than meaning two
  28. different things depending on where it ran.
  29. ONE EVENT NAME. All three fire `tick` — see Timer.hl for why.
  30. `until()` TAKES A TIMESTAMP, not a string. `until('23:30')` needs date
  31. parsing with timezone and format rules, which is its own piece of work and
  32. not part of the clock; write `until(someEpochMs)` and build the epoch-ms with
  33. whatever calendar logic your app already has.
  34. `sleep(n)` BLOCKS — that is what it is for. It is a basic tool for tests and
  35. scripts, and it stops the whole thread: called inside a request handler, no
  36. other request is served until it returns. Inside a server you want `after()`,
  37. which stops nothing. *\
  38. \* Wall clock, milliseconds since the Unix epoch (UTC). Integer-valued. *\
  39. now() {
  40. return __native("time.now")
  41. }
  42. \* ISO-8601 UTC string, always `YYYY-MM-DDTHH:MM:SS.mmmZ` (24 chars).
  43. With no argument it renders `now()`. With an epoch-ms argument it renders
  44. THAT instant — which is what makes it testable: `timestamp(0)` is always
  45. "1970-01-01T00:00:00.000Z". *\
  46. timestamp(ms) {
  47. return __native("time.timestamp", ms)
  48. }
  49. \* Monotonic counter in nanoseconds. Never goes backwards, unaffected by
  50. clock adjustments; the origin is arbitrary, so only DIFFERENCES mean
  51. anything. Use it to measure, never to timestamp. *\
  52. monotonic() {
  53. return __native("time.monotonic")
  54. }
  55. \* A REPEATING timer: `on t.tick()` fires every `seconds` until `t.stop()`, and
  56. a program whose only source is an interval runs forever — that is the point.
  57. Scheduled from the ORIGIN, not from each fire, so the handler's own runtime
  58. never accumulates into the next round. *\
  59. every(
  60. Number seconds \\ the period, in seconds; fractions allowed, floor 0.004
  61. ) {
  62. return new Timer().start(seconds, true)
  63. }
  64. \* A ONE-SHOT: `on t.tick()` fires once, `seconds` from now, and the timer then
  65. RETIRES ITS SOURCE — so a program whose only source was an `after()` ends
  66. after it fires instead of hanging forever. *\
  67. after(
  68. Number seconds \\ the delay, in seconds; fractions allowed, floor 0.004
  69. ) {
  70. return new Timer().start(seconds, false)
  71. }
  72. \* A ONE-SHOT AT AN INSTANT: `until(t)` is `after((t - now()) / 1000)`. `t` is
  73. epoch MILLISECONDS — the same thing `now()` answers and `timestamp(ms)`
  74. renders — because that is this package's instant. An instant already past
  75. fires immediately. *\
  76. until(
  77. Number epochMs \\ the instant, in epoch milliseconds (what now() returns)
  78. ) {
  79. let delay = (epochMs - __native("time.now")) / 1000
  80. if (delay < 0) {
  81. delay = 0
  82. }
  83. return new Timer().start(delay, false)
  84. }
  85. \* BLOCK this thread for `seconds`. The creator's ruling: a basic tool for tests
  86. and debugging. It blocks the event loop too, so nothing else is served while
  87. it runs — inside a server, use `after()`. Returns true when it slept. *\
  88. sleep(
  89. Number seconds \\ how long to block, in seconds; fractions allowed
  90. ) {
  91. return __native("time.sleep", seconds)
  92. }

Branches

Latest commits

  • 3a4d0324antcolony#37: a too-long report gets up to 3 fix tries, finished work is never thrown away for lengthmre
  • a6af7883tracker: worker box sees calendar.worldapi.org (login to copy)mre
  • c613d26btemplates: bridges to external components (login.js for ident's selector) are allowed (creator 2026-09-27)mre
  • 9062978ctracker: worker box sees /media/STORAGE/projects/old-tracker read-only (tracker#2 source data)mre
  • 7f9660eeState of 2026-09-27, before the move to gitoriamre