Ported from the author's Clojure watch.el. Three of its decisions survive unchanged — the program decides what is shown, the request is async, and the paint is replace-buffer-contents so point survives every tick. The transport is the part that had to turn round. An eval here compiles a module and dlopens it, so the planned fix of compiling the render thunk once and re-invoking it per tick was still a poll, and a poll has a defect caching does not touch: a thunk runs at a frame boundary and a stopped program has no more of those. So the program writes into a table in flan_dev.c from its own loop and Emacs reads the table, which is memory. The values are then as fresh as the last frame whatever the repaint interval is, and they are still there while the program is stopped. The frame thread's constraints decide the storage: no allocation, so names are fixed arrays rather than strdup'd; no lock, because the reader is the listener thread; and not the result buffer, which is written once per C-x C-e and would be overwritten sixty times a second by watch traffic. One seqlock per slot rather than one for the table, so a reader retries one slot instead of having to catch the gap between two frames' writes; a snapshot from adjacent frames is what a HUD looks like anyway. Sixty-four slots, and past that a name is dropped rather than fatal — killing the program because somebody watched a 65th value is the diagnostic shooting the patient. Reported as a flag and not a count: the only number the write path could keep is of write attempts, which at frame rate says "3847 names" about one name. Nothing writes the table until a watch buffer is open, so a watch call in a program nobody is debugging is a load and a branch that is not taken — the same number in a release build, since flan_dev.c is linked into both. Scalars work today through declare-c against four runtime entry points, which is why this needed no compiler change. A struct or a slice needs a walk over its type, which is one arm in check.ml beside print; BUILT.md writes it out rather than reaching into a file another lane holds. Ghost text turns out to be gated on the same arm, for a different reason: nothing in the table carries a source location, and a hand-written declare-c call cannot supply one that does not drift when the line moves.
39 lines
1.6 KiB
Plaintext
39 lines
1.6 KiB
Plaintext
;;;; A program that pushes values into the watch table from its own loop.
|
|
;;;;
|
|
;;;; The point of the case is that this needs no compiler change: the watch
|
|
;;;; entry points are ordinary C functions, so a program reaches them through
|
|
;;;; [declare-c] the same way it reaches anything else in the runtime. That is
|
|
;;;; deliberate — a [(watch "hp" hp)] form would be an arm in the checker, and
|
|
;;;; a scalar does not need one.
|
|
;;;;
|
|
;;;; The values are written every iteration and are *not* read back from here.
|
|
;;;; What reads them is the daemon's [watch] op, over the agent, while this
|
|
;;;; program is still running — which is the whole design: the program pushes
|
|
;;;; at frame rate and the editor reads memory.
|
|
(import agent "vendor:agent")
|
|
|
|
(declare-c watch-i64 [name string x i64] i32 "flan_dev_watch_i64")
|
|
(declare-c watch-f64 [name string x f64] i32 "flan_dev_watch_f64")
|
|
(declare-c watch-str [name string s string] i32 "flan_dev_watch_str")
|
|
|
|
(defvar ticks i64)
|
|
|
|
(defn step [] i64
|
|
(set ticks (+ ticks 1))
|
|
;; Three types, because the table stores *rendered text* and the rendering
|
|
;; is per type: an i64 and an f64 do not print the same way, and a string is
|
|
;; quoted and escaped so that a newline in one cannot become a second row.
|
|
(watch-i64 "ticks" ticks)
|
|
(watch-f64 "half" (/ (f64 ticks) 2.0))
|
|
(watch-str "label" "sand")
|
|
ticks)
|
|
|
|
(defn main [] i32
|
|
(agent/start "/tmp/flan-dev-watch-fallback.sock")
|
|
;; A watch call costs a load and a not-taken branch until somebody opens a
|
|
;; watch buffer, so spinning here writes nothing until the test arms it.
|
|
(while (= (agent/wait 20) 0) (step))
|
|
(step)
|
|
(while (= (agent/wait 20) 0) (step))
|
|
0)
|