ludic/packages/ludic.ui/build_class.ludic
Orkuncakilkaya 2958539514 ludic.ui: a new component instance's first build is declared - a first show mid-play is not a frame's keep
Master's windowed fog profile failed the fence at walk t 35 s: +512 B from value_new / value_put /
value_slot / value_lists under bd_class and cmp_keycap_model - a KeyCap first shown mid-walk.

It is a first build, bounded, not a per-show leak: an unmounted instance goes to in_free and the next
show of its class reuses it (in_reuse, `renew`), its props and model objects kept and filled in place.
Only a NEW instance makes them - bounded by how many of that class are up at once, and the ones
unmounted less than two builds ago. in_reuse already declared the record; the props and model objects
and their first fill, made afterwards in bd_class, were not. They are now: in_reuse marks a new record
`fresh`, and bd_class's first fill of it goes through bd_first_fill (@alloc_ok) - a reused instance's
fill stays judged, so a real per-show leak would still fail.

Golden ui_first_show: a component first shown at frame 700, once the fence is judging, then hidden and
shown twice more: bad 0 (the toolchain before this fails frame 701: +464 B value_new from value_slot
under cmp_cell_model, value_put grow - the profile's failure). The 37 ui examples pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:18:22 +03:00

67 lines
2.9 KiB
Text

# build_class.ludic - a Ludic component's instance built: the parent's attributes are its props (id,
# class and style go on its root element instead, styled by the parent's sheet as well as its own),
# its model is its props, state and fields, and its template reads nothing else
function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void {
let cls = k.cls
uc_load(ui_st, cls)
let it = in_get(ui_st, key, cls)
let ce = env_new(ui_st, null)
if it.fresh { ce.model = bd_first_fill(ui_st, it, k, e) } else { ce.model = bd_fill(ui_st, it, k, e) }
ce.inst = it
ce.ctx = e.ctx
ce.slot = k
ce.slot_env = e
ce.on_names = k.on_keys
ce.on_acts = k.on_acts
for i in 0 .. len(k.on_keys) { ui_kept(ce.on_envs, e) }
lc_note(ui_st, key, k.on_keys, k.on_acts, e)
let at = len(out)
if cls.roots == null or len(cls.roots) != 1 or cls.roots[0] != cls.root {
@alloc_ok("once per class, again after a reload")
cls.roots = new []UiTpl # its template as a list of one, made once (again after a reload)
ui_kept(cls.roots, cls.root)
}
let root = cls.roots
bd_kids(ui_st, root, ce, key, out)
if len(out) > at { bd_pass_down(ui_st, out[at], k, e) }
}
# an instance's props and model, filled in place into the objects it keeps (a reused one's are kept)
function bd_fill(ui_st: mut UiState, it: UiInst, k: UiTpl, e: UiEnv) -> Val {
if it.props == null { it.props = Value.object() }
let props = it.props
Value.clear(props)
for i in 0 .. len(k.keys) {
let a = k.keys[i]
if a != "class" and a != "style" and a != "id" { Value.put(props, a, ui_eval(ui_st, k.vals[i], e)) }
}
it.cls.props(it.ptr, props)
if it.model == null { it.model = Value.object() }
return it.cls.model(it.ptr, it.model)
}
# a new instance's first fill makes its props and model objects and every Value in them - once per
# instance, which in_reuse bounds (a prompt's key hint first shown mid-walk was judged as a frame's keep)
@alloc_ok("a new component instance's first build: its props and model, then kept or reused")
function bd_first_fill(ui_st: mut UiState, it: UiInst, k: UiTpl, e: UiEnv) -> Val {
it.fresh = false
return bd_fill(ui_st, it, k, e)
}
# <Card class="wide" style="margin: 4px" id="c"> puts those on the card's own root element; when that
# root is itself a component, each user in turn adds theirs (the outermost's id wins)
function bd_pass_down(ui_st: mut UiState, n: UiNode, k: UiTpl, e: UiEnv) -> void {
let c = tpl_attr(k, "class")
if c != null {
let parts = mm_split(ui_st, ui_eval_text(ui_st, c, e), 32)
for i in 0 .. len(parts) {
if len(parts[i]) > 0 { ui_kept(n.classes, parts[i]) }
}
}
let id = tpl_attr(k, "id")
if id != null { n.id = ui_eval_text(ui_st, id, e) }
let up = up_take(ui_st)
up.sheet = k.sheet
up.style = k.style
up.env = e
@alloc_ok("once per pooled node: its list is kept and cleared")
if n.ups == null { n.ups = new []UiPass }
ui_kept(n.ups, up)
}