The author edited (def colors [4 u32] [...]) in his running game, pressed C-c C-c, and the colours did not change — the same complaint def was built to answer, one step further in. The reading behind it was that a re-evaluated def is a promise about the next re-run, so the session republished the lifted global/<n> and stopped; nothing called it. That is wrong for the reason the form is named after: def is Common Lisp's defparameter, and evaluating a defparameter assigns. The difference from defvar is not "one takes effect at restart", it is "one takes effect, the other does not touch the value at all". So a def now does both. The storage takes the new value at the next frame boundary, carried by the thunk a redefinition module already has — one Set per re-evaluated def, in the same flan_reload_call the class registrations use, run after the bodies are published and on the game thread. And the lifted initialiser is still republished, so the next re-run runs the edited one; dev-rerun.flan pins that half unchanged. A brand-new def gets its initialiser run too, which needed one thing from each backend: a lifted global/<n> asked for by name is neither a sibling nor one of the target's own lifted clauses, so it had no cell, and a dev call goes through a cell. Both now give an unknown one a slot of the module's own, filled from the registry by the installer. An initialiser that signals leaves the old value alone — the value is computed whole before it is stored — and offers abandon-evaluation like any other thunk. A retype is refused first, by the pass that names both types. defonce is untouched, which is its whole contract; defconst was already the immediate one, through consts.
86 lines
3.6 KiB
Plaintext
86 lines
3.6 KiB
Plaintext
;;;; What evaluating a [def] does to a program that is running right now.
|
|
;;;;
|
|
;;;; dev-rerun.flan is the other half of the same form: it asks what the
|
|
;;;; *next run* sees. This asks what *this* run sees, and the answer is that
|
|
;;;; a [def] is Common Lisp's [defparameter] — evaluating one assigns. The
|
|
;;;; author's report is the first global here, to the letter: a palette in a
|
|
;;;; native array, edited in a live game, with the colours expected to change
|
|
;;;; on the next frame and not on the next restart.
|
|
;;;;
|
|
;;;; One global per shape the store has to reach: a native array, a scalar, a
|
|
;;;; struct, a dyn, and one whose initialiser is a call rather than a literal.
|
|
;;;; [kept] is the control — a [defonce] whose whole contract is that
|
|
;;;; evaluating it again leaves the value alone.
|
|
;;;;
|
|
;;;; main runs for as long as any of this takes and does nothing else. Every
|
|
;;;; assertion is read back out of the program's own storage by the daemon,
|
|
;;;; because a printed line is what a run computed and the question here is
|
|
;;;; what the storage holds *now*.
|
|
(import agent "vendor:agent")
|
|
|
|
(defstruct Tuning [gain i32 bias i32])
|
|
|
|
(defstruct Boom [why i32])
|
|
|
|
;; A call, so that [computed]'s initialiser is lifted for a reason other than
|
|
;; the form — and so a brand-new def evaluated into this program has
|
|
;; something to call that the process was built with.
|
|
(defn twice [] i64 10)
|
|
|
|
;; What a failed initialiser is made of. The condition goes unhandled, so a
|
|
;; def whose initialiser calls this stops the program in a break loop rather
|
|
;; than storing anything.
|
|
(defn blow [] i64
|
|
(error (Boom {.why 1}))
|
|
0)
|
|
|
|
(def colors [4 u32] [0xE6B800FF 0xFF0000FF 0xA83232FF 0xCC6B1FFF])
|
|
|
|
(def speed i64 3)
|
|
|
|
(def knobs Tuning {.gain 1 .bias 2})
|
|
|
|
;; A map and not a dyn number: a number is an immediate word and would read
|
|
;; back the same whether or not the store reached the heap.
|
|
(def label dyn {:n 1})
|
|
|
|
(def computed i64 (twice))
|
|
|
|
(defonce kept i64 7)
|
|
|
|
;; A constant nothing consumes at compile time, so it is just bytes in the
|
|
;; program's memory and a re-evaluation can publish a new value into it. It
|
|
;; was the immediate one before [def] was, which is the fact this pins.
|
|
;;
|
|
;; A float rather than an integer, and that is the whole reason it is
|
|
;; written this way: the checker folds integer constants on the way in —
|
|
;; an array length has to be a number before any type resolves — and a
|
|
;; folded one is in the shape of the running program, where no store can
|
|
;; reach it. That one is refused by name, which is a different claim.
|
|
(defconst limit f64 40.0)
|
|
|
|
;; The one [def] spelling with nothing to run: [uninit] lifts no initialiser,
|
|
;; so re-evaluating this form stores nothing, and the byte main wrote stays.
|
|
(def scratch [4 u8] uninit)
|
|
|
|
;; For the one case where the thunk's two halves have to be in order. A
|
|
;; module carries one [flan_reload_call], and it holds both the class
|
|
;; registrations and the def stores; a def whose initialiser constructs an
|
|
;; instance of a class the same form redefined has to see the slot list the
|
|
;; registry now holds, so the registrations go first.
|
|
(defclass point [x y])
|
|
|
|
(defn main [] i32
|
|
(agent/start "/tmp/flan-dev-defstore-fallback.sock")
|
|
;; A byte of [scratch] with a value somebody chose, so that "the uninit
|
|
;; form stored nothing" is a claim about a number and not about whatever
|
|
;; the bytes happened to be.
|
|
(set (at scratch 0) (u8 77))
|
|
;; Long enough that the whole block below runs against a program that is
|
|
;; still running: every assertion here is about what a *running* program
|
|
;; holds, and a fixture that parked half way through would answer the
|
|
;; second half of them from the park instead.
|
|
(dotimes [i 120000]
|
|
(agent/wait 5))
|
|
0)
|