frame keeps: a model's records filled in place, kept event numbers, list spares, Json.read_file

From the fence's kept frames in 22 minutes of play:

- A component field that is a record or a list of records was `value_put(o, k, view_val_T(x))`,
  a whole new tree every frame (HudPrompt's notifications). view_fill.ludic generates view_set_T /
  view_set_list_T / view_fill_T that fill the object and list under the key in place.
- value_list_fit dropped the items it cut off and value_item made new ones as the list grew back,
  a Value per item per regrowth (value_item / value_set_strs); the cut-off items are now the
  list's spares (Val.spare), and an item of another kind is turned rather than replaced.
- ludic.ui: a scroll box's "scroll" and a slider's "change" fired a fresh Value.float a frame
  (sc_walk, scroll.ludic:36); ui_fire_float takes one from a ring kept with the state (fired.ludic).
  ui_object_fit_into is exported, for a draw that keeps its list.
- Json.read_file(path): read, parsed, and the file's text given back - Json.parse(Fs.read_text())
  kept the whole file on every read (Maroon Lake's settings peeks).

Golden value_list_regrow: a list alternating 6 and 2 items every frame keeps nothing (the toolchain
before this fails it: +128 B new Val from value_item). Game compiles; ludic.i18n/settings/hints/
base tests pass (ludic.ui has none); arena and fence goldens unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 12:22:07 +03:00
parent 9c24d707bc
commit a299117858
15 changed files with 71088 additions and 69253 deletions

View file

@ -490,6 +490,7 @@ namespace Json {
alias encode(value) = json_encode
alias write_file(value, path) = json_write_file # encode straight to a file, keeping nothing per call
alias parse(text) = json_parse
alias read_file(path) = json_read_file # parsed, and the file's text given back
alias free(value) = json_free
alias free_all(value) = json_free_all
}

View file

@ -22,6 +22,7 @@ property Val {
txt: pointer = null
keys: []pointer
kids: []Val
spare: []Val = null # a list's items cut off when it last shrank, handed out again as it grows
}
# a number, a string or a null is one record; only a list or an object carries lists (made when first
@ -112,20 +113,30 @@ function value_set_bool(o: Val, key: pointer, b: bool) -> void {
function value_list_fit(o: Val, key: pointer, n: int) -> Val {
let l = value_slot(o, key, 5)
value_lists(l)
while len(l.kids) > n { List.pop(l.kids) }
while len(l.kids) > n { value_spare_put(l, List.pop(l.kids)) }
return l
}
# a list's item i as kind `tag`: its own Value turned when it is another kind, a spare from when the
# list was last longer, and made only past the most it has held (a list that shrank and grew back
# made a new Value per item every time)
function value_item(l: Val, i: int, tag: int) -> Val {
if i < len(l.kids) {
if l.kids[i].tag == tag { return l.kids[i] }
let v = value_new(tag)
l.kids[i] = v
return v
return value_become(l.kids[i], tag)
}
if l.spare != null and len(l.spare) > 0 {
let s = value_become(List.pop(l.spare), tag)
push(l.kids, s)
return s
}
let v = value_new(tag)
push(l.kids, v)
return v
}
function value_spare_put(l: Val, v: Val) -> void {
if l.spare == null { l.spare = new []Val }
push(l.spare, v)
}
function value_set_ints(o: Val, key: pointer, xs: []int) -> void {
let l = value_list_fit(o, key, len(xs))
for i in 0 .. len(xs) {
@ -488,6 +499,16 @@ function jp_skip_ws(p: JP) -> void {
}
}
# a JSON file read and parsed, and its text given back: the tree's strings are the parser's own, so
# a reader of Json.parse(Fs.read_text(path)) kept the whole file for good, on every read
function json_read_file(path: string) -> Val {
let s = Fs.read_text(path)
if s == null { return null }
let v = json_parse(s)
free(s)
return v
}
function json_parse(s: pointer) -> Val {
let p = new JP
p.s = s; p.i = 0; p.n = len(s)