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:
parent
9c24d707bc
commit
a299117858
15 changed files with 71088 additions and 69253 deletions
16
examples/lang/value_list_regrow.ludic
Normal file
16
examples/lang/value_list_regrow.ludic
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# value_list_regrow.ludic - a model's list filled in place that shrinks and grows back, frame after
|
||||
# frame, makes nothing: the items cut off are the list's spares, handed out again as it grows (each
|
||||
# regrowth once made a new Value per item, and the failing fence stopped it at the warm-up's cap):
|
||||
# bad 0 xs 2
|
||||
program ValueListRegrow {
|
||||
entry {
|
||||
let o = Value.object()
|
||||
let long = ["a", "b", "c", "d", "e", "f"]
|
||||
let short = ["x", "y"]
|
||||
for f in 0 .. 3500 {
|
||||
if f % 2 == 0 { value_set_strs(o, "xs", long) } else { value_set_strs(o, "xs", short) }
|
||||
Mem.frame()
|
||||
}
|
||||
print(`bad {Mem.bad_frames()} xs {Value.count(Value.get(o, "xs"))}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,8 @@ export state UiState {
|
|||
ev_ring_at: int = 0
|
||||
nt_evs: []Val = nt_evs_new() # native.ludic: fired events' objects, NT_EVS of them, made with the state
|
||||
nt_ev_at: int = 0
|
||||
nt_nums: []Val = nt_nums_new() # fired.ludic: the numbers a scroll or a slider reports, a ring
|
||||
nt_num_at: int = 0
|
||||
hd_ev: Val = hd_ev_new() # hold.ludic: the held control's event, filled in place
|
||||
cv_texts: []string = new []string # units_pieces.ludic: a value's var() pieces, kept
|
||||
tx_pieces: [][]string = new [][]string # eval_text.ludic: a text's pieces, a list a depth
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ function ct_range_set(ui_st: mut UiState, n: UiNode, v0: float) -> void {
|
|||
let lo = Value.as_float(ui_attr(n, "min"))
|
||||
let hi = Value.as_float(ui_attr(n, "max"))
|
||||
let v = Math.clamp(v0, lo, Math.max(hi, lo))
|
||||
if v != Value.as_float(ui_attr(n, "value")) { ui_fire(ui_st, n, "change", Value.float(v)) }
|
||||
if v != Value.as_float(ui_attr(n, "value")) { ui_fire_float(ui_st, n, "change", v) }
|
||||
}
|
||||
# a range's track pressed or dragged: the value where the pointer is
|
||||
function ct_press(ui_st: mut UiState, n: UiNode, x: float, y: float) -> void {
|
||||
|
|
|
|||
16
packages/ludic.ui/fired.ludic
Normal file
16
packages/ludic.ui/fired.ludic
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# fired.ludic - the number a control reports on each frame it moves (a box scrolled, a slider dragged),
|
||||
# from a ring of Values kept with the state: a fresh Value.float a frame was kept by nothing. The
|
||||
# queue runs within the frame, long before NT_NUMS more come round to one
|
||||
const NT_NUMS: int = 64
|
||||
function nt_nums_new() -> []Val {
|
||||
let l = new []Val
|
||||
for i in 0 .. NT_NUMS { push(l, Value.float(0.0)) }
|
||||
return l
|
||||
}
|
||||
export function ui_fire_float(ui_st: mut UiState, n: UiNode, name: string, x: float) -> void {
|
||||
let at = ui_st.nt_num_at % NT_NUMS
|
||||
ui_st.nt_num_at = at + 1
|
||||
let v = ui_st.nt_nums[at]
|
||||
Value.be_float(v, x)
|
||||
ui_fire(ui_st, n, name, v)
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import "env.ludic"
|
|||
import "eval.ludic"
|
||||
import "act.ludic"
|
||||
import "native.ludic"
|
||||
import "fired.ludic"
|
||||
import "native_room.ludic"
|
||||
import "controls.ludic"
|
||||
import "controls_build.ludic"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ function ly_ratio(n: UiNode, fixed_w: float, avail: float) -> void {
|
|||
}
|
||||
# x, y, w, h: a `nat_w` x `nat_h` picture in the node's content box by its object-fit
|
||||
export function ui_object_fit(n: UiNode, nat_w: float, nat_h: float) -> []float { return ui_object_fit_into(new []float, n, nat_w, nat_h) }
|
||||
function ui_object_fit_into(out: []float, n: UiNode, nat_w: float, nat_h: float) -> []float {
|
||||
# ... into the caller's kept list (a draw every frame should: ui_object_fit makes one a call)
|
||||
export function ui_object_fit_into(out: []float, n: UiNode, nat_w: float, nat_h: float) -> []float {
|
||||
let bx = n.x + n.pl + n.bl
|
||||
let by = n.y + n.pt + n.bt
|
||||
let bw = n.cw - ly_hin(n)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function sc_walk(ui_st: mut UiState, n: UiNode, live: bool) -> void {
|
|||
var off = held
|
||||
if live { off = sc_input(ui_st, n, off, room, most) }
|
||||
off = Math.clamp(off, 0.0, most)
|
||||
if live and off != Math.clamp(held, 0.0, most) { ui_fire(ui_st, n, "scroll", Value.float(off / ui_px(ui_st, 1.0))) }
|
||||
if live and off != Math.clamp(held, 0.0, most) { ui_fire_float(ui_st, n, "scroll", off / ui_px(ui_st, 1.0)) }
|
||||
sc_put(ui_st, n.key, off)
|
||||
n.scroll_y = off
|
||||
if off != 0.0 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,12 @@ function cm_set(c: int, ty: pointer, k: pointer, x: pointer, head: Node) -> poin
|
|||
if (ty == "[]float") { return ` value_set_floats(o, "{k}", {x})\n` }
|
||||
if (ty == "[]string") { return ` value_set_strs(o, "{k}", {x})\n` }
|
||||
if (ty == "[]bool") { return ` value_set_bools(o, "{k}", {x})\n` }
|
||||
return ` value_put(o, "{k}", {cm_to(c, ty, x, head)})\n`
|
||||
let line = vw_set(0, ty, "o", k, x, head) # a record or a list of them: filled in place (view_fill)
|
||||
if line == null {
|
||||
cm_err(c, `a {ty} is something a template cannot read`)
|
||||
return ""
|
||||
}
|
||||
return line
|
||||
}
|
||||
function cm_to(c: int, ty: pointer, x: pointer, head: Node) -> pointer {
|
||||
if (ty == "Val") { return x }
|
||||
|
|
|
|||
54
selfhost/frontend/view_fill.ludic
Normal file
54
selfhost/frontend/view_fill.ludic
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# view_fill.ludic - a component's field as a Value FILLED IN PLACE: a record is the object under its
|
||||
# key with each field set, a list of records is the list under its key with each item filled, so a
|
||||
# model built every frame makes Values only the first time (value_put of a fresh conversion kept a
|
||||
# whole new tree each frame - the HUD's notifications were one). What cannot be filled converts.
|
||||
function vw_set(v: int, ty: pointer, o: pointer, k: pointer, x: pointer, head: Node) -> pointer {
|
||||
if (ty == "int") { return ` value_set_int({o}, "{k}", {x})\n` }
|
||||
if (ty == "float") { return ` value_set_float({o}, "{k}", {x})\n` }
|
||||
if (ty == "string") { return ` value_set_str({o}, "{k}", {x})\n` }
|
||||
if (ty == "bool") { return ` value_set_bool({o}, "{k}", {x})\n` }
|
||||
if (ty == "[]int") { return ` value_set_ints({o}, "{k}", {x})\n` }
|
||||
if (ty == "[]float") { return ` value_set_floats({o}, "{k}", {x})\n` }
|
||||
if (ty == "[]string") { return ` value_set_strs({o}, "{k}", {x})\n` }
|
||||
if (ty == "[]bool") { return ` value_set_bools({o}, "{k}", {x})\n` }
|
||||
if (ty == "Val") { return ` value_put({o}, "{k}", {x})\n` }
|
||||
if len(ty) > 2 and ty[0] == '[' and ty[1] == ']' {
|
||||
let et: pointer = ty[2..len(ty)]
|
||||
if find_comp(et) != null {
|
||||
vw_fill_fn(v, et, head)
|
||||
let name = `view_set_list_{vw_mangle(et)}`
|
||||
vw_helper(`function {name}(o: Val, k: string, xs: {ty}) -> void {{\n let l = value_list_fit(o, k, len(xs))\n var i = 0\n while i < len(xs) {{\n let d = value_item(l, i, 6)\n if xs[i] == null {{ value_become(d, 0) }} else {{ view_fill_{vw_mangle(et)}(d, xs[i]) }}\n i += 1\n }}\n}}\n`, name, head)
|
||||
return ` {name}({o}, "{k}", {x})\n`
|
||||
}
|
||||
}
|
||||
if find_comp(ty) != null {
|
||||
vw_fill_fn(v, ty, head)
|
||||
let m = vw_mangle(ty)
|
||||
let name = `view_set_{m}`
|
||||
vw_helper(`function {name}(o: Val, k: string, r: {ty}) -> void {{\n if r == null {{\n value_slot(o, k, 0)\n return\n }}\n view_fill_{m}(value_slot(o, k, 6), r)\n}}\n`, name, head)
|
||||
return ` {name}({o}, "{k}", {x})\n`
|
||||
}
|
||||
let c = vw_conv(v, ty, x, head)
|
||||
if c == null { return null }
|
||||
return ` value_put({o}, "{k}", {c})\n`
|
||||
}
|
||||
|
||||
# view_fill_T(d, r): every field of r a template could read, set into the object d in place. Its name
|
||||
# is taken before its body is written, so a record that holds its own kind fills through itself
|
||||
function vw_fill_fn(v: int, ty: pointer, head: Node) -> void {
|
||||
let name = `view_fill_{vw_mangle(ty)}`
|
||||
if vw_have(name) { return }
|
||||
push(g_vw_helpers, name)
|
||||
let comp = find_comp(ty)
|
||||
var src = `function {name}(d: Val, r: {ty}) -> void {{\n`
|
||||
var i = 0
|
||||
while i < len(comp.kids) {
|
||||
let f = comp.kids[i]
|
||||
if f.ty != null {
|
||||
let line = vw_set(v, f.ty, "d", f.s, `r.{f.s}`, head)
|
||||
if line != null { src = src + line }
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
vw_parse(src + "}\n", head.file, head.line, 1)
|
||||
}
|
||||
68433
selfhost/ludicc.seed.ll
68433
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -35,6 +35,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/frontend/defaults.ludic")
|
||||
push(f, "selfhost/frontend/view.ludic")
|
||||
push(f, "selfhost/frontend/view_conv.ludic")
|
||||
push(f, "selfhost/frontend/view_fill.ludic")
|
||||
push(f, "selfhost/frontend/view_finish.ludic")
|
||||
push(f, "selfhost/frontend/component.ludic")
|
||||
push(f, "selfhost/frontend/component_rw.ludic")
|
||||
|
|
|
|||
|
|
@ -1182,6 +1182,7 @@ function cmd_dev_test() -> int {
|
|||
reject_case("rejected/long_into_int", "4294967295 does not fit one; it is a long", "a long literal given to an int is refused, not wrapped")
|
||||
feat_case("lang/runtime_temps", "", "1 2 3 4 5 grew 0 files grew 0", "runtime_temps.ludic (Log, DateTime.format, Path, Mime, Text, Os and Fs keep nothing a call does not hand back)")
|
||||
feat_case("lang/string_temps", "", "1 2 3 4 5 6 7 grew 0", "string_temps.ludic (a concatenation's pieces, a template's holes and a compared side are freed once used; a kept one is not)")
|
||||
feat_case("lang/value_list_regrow", "", "bad 0 xs 2", "value_list_regrow.ludic (a model list filled in place that shrinks and grows back keeps its cut-off items as spares: nothing kept, the fence lets it through)")
|
||||
feat_case("lang/json_saves", "", "1 2 3 saves grew 0", "json_saves.ludic (Json.write_file saves through a kept buffer: the text Json.encode gives, 1000 saves keep nothing)")
|
||||
feat_case("lang/alloc_fence", "", "frames 300 kept 0 bad 0", "alloc_fence.ludic (25.1: a frame that makes only what it frees or reuses keeps nothing, and the fence passes it)")
|
||||
feat_case("lang/alloc_fence_declared", "", "bad 0 kept 0", "alloc_fence_declared.ludic (25.1: @alloc_ok on a function, a statement and a generic's statement is declared at run time; the failing fence lets it through)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue