From 429e41932713059fb0fb81186e4e6f71e9aaaed6 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Mon, 28 Sep 2026 16:41:47 +0300 Subject: [PATCH] ludic.ui, ludic.i18n: frame_allocs to 0 - a wrap, an ellipsis, a tooltip's lines, a text-shadow's colour and a range's value kept by what made them; pointer events from a ring; inline text joined through the memo; the pool's and the state's lists through ui_kept; parse, memo miss, pool miss, lazy start and template errors declared; the translation cache clears in place Co-Authored-By: Claude Opus 5.5 --- packages/ludic.i18n/langs.ludic | 1 + packages/ludic.i18n/match.ludic | 1 + packages/ludic.i18n/po.ludic | 2 + packages/ludic.i18n/translate.ludic | 6 +- packages/ludic.i18n/use.ludic | 1 + packages/ludic.ui/anim.ludic | 5 +- packages/ludic.ui/attr.ludic | 1 + packages/ludic.ui/attr_edge.ludic | 1 + packages/ludic.ui/attr_fit.ludic | 1 + packages/ludic.ui/attr_more.ludic | 11 ++-- packages/ludic.ui/backend.ludic | 3 + packages/ludic.ui/build.ludic | 4 +- packages/ludic.ui/build_class.ludic | 10 +-- packages/ludic.ui/build_more.ludic | 9 ++- packages/ludic.ui/calc.ludic | 2 + packages/ludic.ui/calc_fns.ludic | 6 +- packages/ludic.ui/calc_terms.ludic | 1 + packages/ludic.ui/cascade.ludic | 7 +- packages/ludic.ui/cascade_merge.ludic | 6 +- packages/ludic.ui/class.ludic | 2 + packages/ludic.ui/color.ludic | 2 + packages/ludic.ui/controls_build.ludic | 33 +++++----- packages/ludic.ui/controls_number.ludic | 4 +- packages/ludic.ui/controls_parts.ludic | 9 +-- packages/ludic.ui/draw_gradient.ludic | 1 + packages/ludic.ui/draw_order.ludic | 10 +-- packages/ludic.ui/dump.ludic | 1 + packages/ludic.ui/elem.ludic | 23 ++++--- packages/ludic.ui/env.ludic | 8 ++- packages/ludic.ui/eval.ludic | 5 +- packages/ludic.ui/eval_text.ludic | 9 +-- packages/ludic.ui/focus.ludic | 2 +- packages/ludic.ui/frame.ludic | 1 + packages/ludic.ui/frame_box.ludic | 2 +- packages/ludic.ui/index.ludic | 3 + packages/ludic.ui/instance.ludic | 5 +- packages/ludic.ui/kept.ludic | 6 ++ packages/ludic.ui/layout_fit.ludic | 8 +-- packages/ludic.ui/layout_run.ludic | 4 +- packages/ludic.ui/layout_shrink.ludic | 2 +- packages/ludic.ui/layout_text.ludic | 9 +-- packages/ludic.ui/lib.ludic | 2 + packages/ludic.ui/life.ludic | 20 +++--- packages/ludic.ui/link.ludic | 1 + packages/ludic.ui/memo.ludic | 3 + packages/ludic.ui/memo_join.ludic | 3 + packages/ludic.ui/memo_more.ludic | 88 +++++++++++++++++++++++++ packages/ludic.ui/native.ludic | 7 +- packages/ludic.ui/node_more.ludic | 1 + packages/ludic.ui/pointer.ludic | 2 +- packages/ludic.ui/pointer_events.ludic | 20 +++--- packages/ludic.ui/pointer_ring.ludic | 22 +++++++ packages/ludic.ui/pool.ludic | 42 +++++++----- packages/ludic.ui/popover_anchor.ludic | 1 + packages/ludic.ui/popover_within.ludic | 1 + packages/ludic.ui/reload.ludic | 1 + packages/ludic.ui/render3d_image.ludic | 1 + packages/ludic.ui/screen_class.ludic | 27 ++++---- packages/ludic.ui/scroll.ludic | 4 +- packages/ludic.ui/scroll_bar.ludic | 2 +- packages/ludic.ui/select_match.ludic | 1 + packages/ludic.ui/state.ludic | 4 +- packages/ludic.ui/style.ludic | 1 + packages/ludic.ui/style_rule.ludic | 1 + packages/ludic.ui/tooltip.ludic | 7 +- packages/ludic.ui/tooltip_text.ludic | 5 +- packages/ludic.ui/tpl.ludic | 2 + packages/ludic.ui/transition.ludic | 8 +-- packages/ludic.ui/units.ludic | 7 +- packages/ludic.ui/units_pieces.ludic | 8 ++- packages/ludic.ui/zoom.ludic | 1 + 71 files changed, 365 insertions(+), 155 deletions(-) create mode 100644 packages/ludic.ui/kept.ludic create mode 100644 packages/ludic.ui/memo_more.ludic create mode 100644 packages/ludic.ui/pointer_ring.ludic diff --git a/packages/ludic.i18n/langs.ludic b/packages/ludic.i18n/langs.ludic index be7e6351..af1a735a 100644 --- a/packages/ludic.i18n/langs.ludic +++ b/packages/ludic.i18n/langs.ludic @@ -1,5 +1,6 @@ # langs.ludic - where languages come from, later ones replacing earlier ones with the same code: # /languages.txt (one code per line, # a comment) and every .po in the mod folder +@alloc_ok("once at start: the shipped and dropped-in languages found") export function i18n_init(i18n_st: mut I18nState) -> void { i18n_st.codes = new []string i18n_st.names = new []string diff --git a/packages/ludic.i18n/match.ludic b/packages/ludic.i18n/match.ludic index d4fdd614..38ff7244 100644 --- a/packages/ludic.i18n/match.ludic +++ b/packages/ludic.i18n/match.ludic @@ -46,6 +46,7 @@ function best_of(i18n_st: mut I18nState, head: int, next: words, s: string, best return b } +@alloc_ok("a translation cache miss (L): once per distinct text") function pattern(i18n_st: mut I18nState, s: string, depth: int) -> string { i18n_st.hit = false if i18n_st.pat_n == 0 { return s } diff --git a/packages/ludic.i18n/po.ludic b/packages/ludic.i18n/po.ludic index dc77ff54..9551137b 100644 --- a/packages/ludic.i18n/po.ludic +++ b/packages/ludic.i18n/po.ludic @@ -20,6 +20,7 @@ function entry_put(e: PoEntry, mode: int, s: string) -> void { if mode == 4 { e.forms[len(e.forms) - 1] = e.forms[len(e.forms) - 1] + s } } +@alloc_ok("on a language change: a .po read") export function i18n_parse(text: string) -> []PoEntry { let r = new []PoEntry let lines = lines_of(text) @@ -81,6 +82,7 @@ function unquote(line: string, from: int) -> string { } # a header field out of the msgstr "" block: "X-Language-Name: Türkçe\n" +@alloc_ok("on a language change: a .po's header read") export function i18n_header(text: string, field: string) -> string { var n = len(text) if n > 4000 { n = 4000 } diff --git a/packages/ludic.i18n/translate.ludic b/packages/ludic.i18n/translate.ludic index 2365b234..3d5bf7b9 100644 --- a/packages/ludic.i18n/translate.ludic +++ b/packages/ludic.i18n/translate.ludic @@ -7,13 +7,16 @@ export function L(i18n_st: mut I18nState, s: string) -> string { let r = whole(i18n_st, s) if Dict.size(i18n_st.cache) >= CACHE_MAX { Dict.clear(i18n_st.cache) - i18n_st.cache_out = new []string + List.clear(i18n_st.cache_out) } + @alloc_ok("a translation cache miss: the entry is kept, up to CACHE_MAX") Dict.set(i18n_st.cache, s, len(i18n_st.cache_out)) + @alloc_ok("a translation cache miss: as above") push(i18n_st.cache_out, r) return r } +@alloc_ok("a translation cache miss (L): once per distinct text, bounded by CACHE_MAX") function whole(i18n_st: mut I18nState, s: string) -> string { let n = len(s) if n == 0 { return s } @@ -37,6 +40,7 @@ function whole(i18n_st: mut I18nState, s: string) -> string { } # a paragraph built from sentences, one at a time; English stays where a sentence is unknown +@alloc_ok("a translation cache miss (L): once per distinct text") function sentences(i18n_st: mut I18nState, s: string) -> string { let n = len(s) var r = "" diff --git a/packages/ludic.i18n/use.ludic b/packages/ludic.i18n/use.ludic index e4da8d39..096ec7d5 100644 --- a/packages/ludic.i18n/use.ludic +++ b/packages/ludic.i18n/use.ludic @@ -27,6 +27,7 @@ export function i18n_use(i18n_st: mut I18nState, code: string) -> void { } # a .po's text as the lookups, whatever the language is called (a test, a file from elsewhere) +@alloc_ok("on a language change: the language's messages read in") export function i18n_load_text(i18n_st: mut I18nState, text: string) -> void { let es = i18n_parse(text) i18n_st.exact = Dict.new() diff --git a/packages/ludic.ui/anim.ludic b/packages/ludic.ui/anim.ludic index a31e2b8c..5403e39c 100644 --- a/packages/ludic.ui/anim.ludic +++ b/packages/ludic.ui/anim.ludic @@ -43,8 +43,8 @@ function an_began(ui_st: mut UiState, key: string) -> float { for i in 0 .. len(ui_st.an_keys) { if ui_st.an_keys[i] == key { return ui_st.an_start[i] } } - push(ui_st.an_keys, key) - push(ui_st.an_start, an_now(ui_st)) + ui_kept(ui_st.an_keys, key) + ui_kept(ui_st.an_start, an_now(ui_st)) return an_now(ui_st) } # the element's animation at this moment: each property between the stops around it @@ -52,6 +52,7 @@ function an_apply(ui_st: mut UiState, n: UiNode) -> void { if n.anim == "" or n.anim_dur <= 0.0 { return } let k = an_find(ui_st, n.anim) if k == null { + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `animation {n.anim}: there is no @keyframes of that name`) return } diff --git a/packages/ludic.ui/attr.ludic b/packages/ludic.ui/attr.ludic index 01a4bc46..c7bedb9c 100644 --- a/packages/ludic.ui/attr.ludic +++ b/packages/ludic.ui/attr.ludic @@ -4,6 +4,7 @@ const AT_UNDRAWN: string = " font-weight font-family font-style box-shadow curso # a stylesheet's property: one that means nothing is said function el_attr(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void { if el_apply(ui_st, n, key, v) { return } + @alloc_ok("only when a template is wrong: a broken screen, never a working one") if lk_in(AT_UNDRAWN, at_alias(key)) { ui_err(ui_st, `{key}: this renderer does not draw it`) } else { ui_err(ui_st, `{key}: there is no such property`) } } # an element's attribute: a property when it names one, otherwise only there for [selectors] diff --git a/packages/ludic.ui/attr_edge.ludic b/packages/ludic.ui/attr_edge.ludic index 92f83eb0..94a2c159 100644 --- a/packages/ludic.ui/attr_edge.ludic +++ b/packages/ludic.ui/attr_edge.ludic @@ -14,6 +14,7 @@ function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { return true } # url(path) or url("path") -> path +@alloc_ok("a memo miss (el_url_u): once per text") function el_url(s: string) -> string { var t = tpl_words(s) if len(t) > 5 and sat(t, 0, "url(") { t = tpl_words(t[4..len(t) - 1]) } diff --git a/packages/ludic.ui/attr_fit.ludic b/packages/ludic.ui/attr_fit.ludic index b54c1834..fcde6897 100644 --- a/packages/ludic.ui/attr_fit.ludic +++ b/packages/ludic.ui/attr_fit.ludic @@ -16,6 +16,7 @@ function el_fit_props(ui_st: mut UiState, n: UiNode, key: string, s: string) -> return true } # "a, rgba(1, 2, 3, 0.5) 40%, b" -> its three parts +@alloc_ok("a gradient being read, on a memo miss") function el_commas(s: string) -> []string { let out = new []string var at = 0 diff --git a/packages/ludic.ui/attr_more.ludic b/packages/ludic.ui/attr_more.ludic index 9a1b9d83..2d4bd530 100644 --- a/packages/ludic.ui/attr_more.ludic +++ b/packages/ludic.ui/attr_more.ludic @@ -38,6 +38,7 @@ function el_dim(ui_st: mut UiState, n: UiNode, wide: bool, v: Val) -> void { # a length out of calc(); a percentage has nothing to be a share of here function el_calc_len(ui_st: mut UiState, s: string) -> float { if not ca_eval(ui_st, s) { return 0.0 } + @alloc_ok("only when a template is wrong: a broken screen, never a working one") if ui_st.ca_pct != 0.0 { ui_err(ui_st, `{ui_st.el_key}: {s} - a percentage in calc() is for a width or a height`) } return ui_st.ca_px } @@ -53,6 +54,7 @@ function el_flex_short(ui_st: mut UiState, n: UiNode, s: string) -> void { if len(parts) > 1 and len(parts[1]) > 0 and parts[1][0] >= 48 and parts[1][0] <= 57 { nx(n).shrink = el_num(parts[1]) } } # words apart by spaces, a bracket's own spaces kept: "calc(1em + 2px) 4px" is two +@alloc_ok("a memo miss (mm_split): once per text") function el_words(s: string) -> []string { let out = new []string var at = 0 @@ -76,17 +78,16 @@ function el_text_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void { if tw(ui_st, s) == "none" { return } let parts = mm_words(ui_st, s) var nums = 0 - var col = "" + var from = -1 for i in 0 .. len(parts) { let p = parts[i] - if col == "" and ((p[0] >= 48 and p[0] <= 57) or p[0] == 45 or p[0] == 46) { + if from < 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 45 or p[0] == 46) { if nums == 0 { x.tsh_x = el_unit(ui_st, p) } if nums == 1 { x.tsh_y = el_unit(ui_st, p) } nums += 1 - } else if col == "" { col = p } else { col = col + " " + p } + } else if from < 0 { from = i } } - if col == "" { col = "rgba(0,0,0,0.6)" } - x.tsh_c = ui_color(ui_st, col) + x.tsh_c = ui_color(ui_st, el_tsh_col(ui_st, s, parts, from)) x.tsh_a = ui_st.cl_alpha } # one word of a list of lengths (padding: calc(1em / 4) 2px) diff --git a/packages/ludic.ui/backend.ludic b/packages/ludic.ui/backend.ludic index cffed699..846b54c8 100644 --- a/packages/ludic.ui/backend.ludic +++ b/packages/ludic.ui/backend.ludic @@ -52,6 +52,9 @@ export state UiState { ca_used: int = 0 ca_args: [][]CaVal = new [][]CaVal ca_depth: int = 0 + pe_evs: []UiPointer = pe_evs__new() # pointer_events.ludic: an event's record and its Value, a ring + pe_vals: []Val = pe_vals__new() + pe_at: int = 0 tt_src: string = null # tooltip.ludic: the title tt_text was translated from fit_out: []float = new []float # draw_content.ludic: an image's box, kept r3n_cut: []float = new []float # render3d_nine.ludic: a nine-slice's edges, kept diff --git a/packages/ludic.ui/build.ludic b/packages/ludic.ui/build.ludic index d2e24b37..2a1f1d4f 100644 --- a/packages/ludic.ui/build.ludic +++ b/packages/ludic.ui/build.ludic @@ -20,7 +20,7 @@ function bd_kids(ui_st: mut UiState, kids: []UiTpl, e0: UiEnv, key: string, out: } else if k.tag == "else" { if not last_if { bd_kids(ui_st, k.kids, e, ck, out) } last_if = true - } else if k.tag == "each" { bd_each(ui_st, k, e, ck, out) } else if k.tag == "slot" { bd_slot(ui_st, k, e, ck, out) } else if not bd_comp(ui_st, k, e, ck, out) { push(out, bd_elem(ui_st, k, e, ck)) } + } else if k.tag == "each" { bd_each(ui_st, k, e, ck, out) } else if k.tag == "slot" { bd_slot(ui_st, k, e, ck, out) } else if not bd_comp(ui_st, k, e, ck, out) { ui_kept(out, bd_elem(ui_st, k, e, ck)) } } } function bd_test(ui_st: mut UiState, k: UiTpl, e: UiEnv) -> bool { @@ -86,7 +86,7 @@ function bd_comp(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiN for i in 0 .. len(k.keys) { env_bind(ce, k.keys[i], ui_eval(ui_st, k.vals[i], e)) } ce.on_names = k.on_keys ce.on_acts = k.on_acts - for i in 0 .. len(k.on_keys) { push(ce.on_envs, e) } + for i in 0 .. len(k.on_keys) { ui_kept(ce.on_envs, e) } ce.slot = k ce.slot_env = e ce.ctx = e.ctx diff --git a/packages/ludic.ui/build_class.ludic b/packages/ludic.ui/build_class.ludic index 78ea163e..6885e1db 100644 --- a/packages/ludic.ui/build_class.ludic +++ b/packages/ludic.ui/build_class.ludic @@ -22,12 +22,13 @@ function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui ce.slot_env = e ce.on_names = k.on_keys ce.on_acts = k.on_acts - for i in 0 .. len(k.on_keys) { push(ce.on_envs, e) } + 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) - push(cls.roots, cls.root) + ui_kept(cls.roots, cls.root) } let root = cls.roots bd_kids(ui_st, root, ce, key, out) @@ -40,7 +41,7 @@ function bd_pass_down(ui_st: mut UiState, n: UiNode, k: UiTpl, e: UiEnv) -> void 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 { push(n.classes, parts[i]) } + if len(parts[i]) > 0 { ui_kept(n.classes, parts[i]) } } } let id = tpl_attr(k, "id") @@ -49,6 +50,7 @@ function bd_pass_down(ui_st: mut UiState, n: UiNode, k: UiTpl, e: UiEnv) -> void 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 } - push(n.ups, up) + ui_kept(n.ups, up) } diff --git a/packages/ludic.ui/build_more.ludic b/packages/ludic.ui/build_more.ludic index e0a2386b..83233501 100644 --- a/packages/ludic.ui/build_more.ludic +++ b/packages/ludic.ui/build_more.ludic @@ -23,17 +23,16 @@ function bd_group(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui t.key = key t.text = ui_tr(ui_st, ui_eval_text(ui_st, k.text, e)) t.env = e - push(out, t) + ui_kept(out, t) } # text inside text -

Hi there

,

- reads as one line of it -function el_inline(n: UiNode) -> void { +function el_inline(ui_st: mut UiState, n: UiNode) -> void { if n.kind != UI_TEXT or len(n.children) == 0 { return } var s = n.text for i in 0 .. len(n.children) { let c = n.children[i] - el_inline(c) - if len(s) > 0 and len(c.text) > 0 { s = s + " " } - s = s + c.text + el_inline(ui_st, c) + if len(s) == 0 { s = c.text } else if len(c.text) > 0 { s = mm_join(ui_st, s, " ", c.text) } } n.text = s List.clear(n.children) diff --git a/packages/ludic.ui/calc.ludic b/packages/ludic.ui/calc.ludic index 1a9c7278..7dd851c6 100644 --- a/packages/ludic.ui/calc.ludic +++ b/packages/ludic.ui/calc.ludic @@ -13,6 +13,7 @@ function ca_eval(ui_st: mut UiState, s: string) -> bool { ca_ws(ui_st) if ui_st.ca_bad == "" and ui_st.ca_i < len(ui_st.ca_s) { ui_st.ca_bad = "nothing after its closing )" } if ui_st.ca_bad != "" { + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `{s}: calc() expected {ui_st.ca_bad}`) return false } @@ -68,6 +69,7 @@ function ca_len(ui_st: mut UiState, a: CaVal) -> CaVal { # a term, blank, from the pool: an evaluation is over before the next begins, and what it works out is # copied into ca_px / ca_pct, so its records are used again by the next one (never made a frame) function ca_new(ui_st: mut UiState) -> CaVal { + @alloc_ok("a pool miss: past the most terms one evaluation has had") if ui_st.ca_used >= len(ui_st.ca_pool) { push(ui_st.ca_pool, new CaVal) } let r = ui_st.ca_pool[ui_st.ca_used] ui_st.ca_used += 1 diff --git a/packages/ludic.ui/calc_fns.ludic b/packages/ludic.ui/calc_fns.ludic index 16b1d598..04c36dfc 100644 --- a/packages/ludic.ui/calc_fns.ludic +++ b/packages/ludic.ui/calc_fns.ludic @@ -12,12 +12,13 @@ function ca_word(s: string, at: int, w: string) -> bool { return at + len(w) <= function ca_fn(ui_st: mut UiState, f: string) -> CaVal { ui_st.ca_i = ui_st.ca_i + len(f) + 1 let depth = ui_st.ca_depth # min(calc(...)) nests: a kept list of arguments a depth - while len(ui_st.ca_args) <= depth { push(ui_st.ca_args, new []CaVal) } + @alloc_ok("once per depth of nesting, then kept") + while len(ui_st.ca_args) <= depth { ui_kept(ui_st.ca_args, new []CaVal) } let args = ui_st.ca_args[depth] List.clear(args) ui_st.ca_depth = depth + 1 while ui_st.ca_bad == "" { - push(args, ca_sum(ui_st)) + ui_kept(args, ca_sum(ui_st)) ca_ws(ui_st) if ui_st.ca_i < len(ui_st.ca_s) and ui_st.ca_s[ui_st.ca_i] == 44 { ui_st.ca_i += 1 } else { break } } @@ -31,6 +32,7 @@ function ca_fn(ui_st: mut UiState, f: string) -> CaVal { } for i in 0 .. len(args) { if args[i].pct != 0.0 { + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_st.ca_bad = `lengths in {f}(), not a percentage` return ca_new(ui_st) } diff --git a/packages/ludic.ui/calc_terms.ludic b/packages/ludic.ui/calc_terms.ludic index f89d1fbe..64b5ab4d 100644 --- a/packages/ludic.ui/calc_terms.ludic +++ b/packages/ludic.ui/calc_terms.ludic @@ -84,6 +84,7 @@ function ca_number(ui_st: mut UiState) -> CaVal { return r } r.bare = false + @alloc_ok("only when a template is wrong: a broken screen, never a working one") if ca_unit(s, num_end, end, "%") { r.pct = f } else if ca_unit(s, num_end, end, "px") { r.px = el_px(ui_st, f) } else if ca_unit(s, num_end, end, "rem") { r.px = f * el_px(ui_st, 16.0) } else if ca_unit(s, num_end, end, "em") { r.px = f * el_em(ui_st) } else if ca_unit(s, num_end, end, "vw") { r.px = f * ui_st.ly_vp_w / 100.0 } else if ca_unit(s, num_end, end, "vh") { r.px = f * ui_st.ly_vp_h / 100.0 } else { ui_st.ca_bad = `a unit calc() knows, not {s[num_end..end]}` } return r } diff --git a/packages/ludic.ui/cascade.ludic b/packages/ludic.ui/cascade.ludic index 6aad6834..30d2bb8e 100644 --- a/packages/ludic.ui/cascade.ludic +++ b/packages/ludic.ui/cascade.ludic @@ -61,12 +61,15 @@ function cs_rule_in(ui_st: mut UiState, n: UiNode, r: UiRule, e: UiEnv) -> void } } function ua_sheet(ui_st: mut UiState) -> UiSheet { - if ui_st.ua_sh != null { return ui_st.ua_sh } + if ui_st.ua_sh == null { ua_sheet_make(ui_st) } + return ui_st.ua_sh +} +@alloc_ok("once: the default sheet is read the first time a style is asked for") +function ua_sheet_make(ui_st: mut UiState) -> void { let f = new UiFile f.path = "the default sheet" f.imp = new []UiFile f.own = new []UiRule lss_text(ui_st, f, UA_LSS) ui_st.ua_sh = sheet_of(f) - return ui_st.ua_sh } diff --git a/packages/ludic.ui/cascade_merge.ludic b/packages/ludic.ui/cascade_merge.ludic index 0054543f..e436640d 100644 --- a/packages/ludic.ui/cascade_merge.ludic +++ b/packages/ludic.ui/cascade_merge.ludic @@ -20,12 +20,12 @@ function cs_gather(ui_st: mut UiState, n: UiNode, sh: UiSheet, e: UiEnv, rules: for i in 0 .. len(seen) { if seen[i] == sh { return } } - push(seen, sh) + ui_kept(seen, sh) for i in 0 .. len(sh.rules) { let r = sh.rules[i] if md_ok(ui_st, r) and sel_match(ui_st, r.sel, n) { - push(rules, r) - push(envs, e) + ui_kept(rules, r) + ui_kept(envs, e) var j = len(rules) - 1 while j > 0 and rules[j - 1].spec > r.spec { rules[j] = rules[j - 1] diff --git a/packages/ludic.ui/class.ludic b/packages/ludic.ui/class.ludic index 25b39ca5..c5db0c95 100644 --- a/packages/ludic.ui/class.ludic +++ b/packages/ludic.ui/class.ludic @@ -32,6 +32,7 @@ function uc_find(ui_st: UiState, name: string) -> UiClass { return null } # the class's template and styles, read the first time it is shown +@alloc_ok("once per component class: its template and styles are read the first time it is shown") function uc_load(ui_st: mut UiState, k: UiClass) -> void { if k.root != null { return } let f = lib_blank(k.xml_path) @@ -46,6 +47,7 @@ function uc_load(ui_st: mut UiState, k: UiClass) -> void { } # a developer's build reads a class's files again when they change on disk: the first look is what # they are, and a later one that differs is taken (an @import in the styles is read from disk too) +@alloc_ok("developer builds only: a changed file read again") function uc_reload(ui_st: mut UiState) -> bool { if ui_st.uc_list == null { return false } var changed = false diff --git a/packages/ludic.ui/color.ludic b/packages/ludic.ui/color.ludic index c7bac3d5..4c842e3e 100644 --- a/packages/ludic.ui/color.ludic +++ b/packages/ludic.ui/color.ludic @@ -17,6 +17,7 @@ function ui_color(ui_st: mut UiState, name0: string) -> int { } return c } +@alloc_ok("a memo miss (ui_color): once per colour") function ui_color_new(ui_st: mut UiState, name0: string) -> int { let name = tw(ui_st, name0) ui_st.cl_alpha = 1.0 @@ -27,6 +28,7 @@ function ui_color_new(ui_st: mut UiState, name0: string) -> int { for i in 0 .. len(CL_NAMES) - n - 1 { if CL_NAMES[i] == 32 and CL_NAMES[i + n + 1] == 61 and sat(CL_NAMES, i + 1, name) { return cl_hex_at(ui_st, CL_NAMES, i + n + 2, 6) } } + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `{name}: not a colour - #rrggbb, rgb(), rgba(), a name, or var(--a-colour)`) return 15658734 } diff --git a/packages/ludic.ui/controls_build.ludic b/packages/ludic.ui/controls_build.ludic index 64f0754e..0ebb8779 100644 --- a/packages/ludic.ui/controls_build.ludic +++ b/packages/ludic.ui/controls_build.ludic @@ -10,7 +10,7 @@ function ct_part(ui_st: mut UiState, n: UiNode, cls: string, text: string) -> Ui p.tag = "span" p.env = n.env p.sheet = n.sheet - push(p.classes, cls) + ui_kept(p.classes, cls) if text != "" { p.kind = UI_TEXT p.text = text @@ -25,7 +25,7 @@ function ct_build_bar(ui_st: mut UiState, n: UiNode) -> void { if hi <= lo { hi = lo + 1.0 } let fill = ct_part(ui_st, n, "ui-fill", "") fill.w_pct = Math.clamp((Value.as_float(ui_attr(n, "value")) - lo) / (hi - lo), 0.0, 1.0) * 100.0 - push(n.children, fill) + ui_kept(n.children, fill) } function ct_build(ui_st: mut UiState, n: UiNode) -> void { if n.tag == "progress" or n.tag == "meter" { @@ -42,15 +42,15 @@ function ct_build(ui_st: mut UiState, n: UiNode) -> void { ct_label(ui_st, n) if t == "checkbox" or t == "radio" { let track = ct_part(ui_st, n, "ui-track", "") - push(track.children, ct_part(ui_st, n, "ui-knob", "")) - push(n.children, track) + ui_kept(track.children, ct_part(ui_st, n, "ui-knob", "")) + ui_kept(n.children, track) } else if t == "range" { ct_build_range(ui_st, n) } else { let field = ct_part(ui_st, n, "ui-field", "") - push(field.children, ct_part(ui_st, n, "ui-value", ct_field_text(ui_st, n))) - if ui_st.fc_key == n.key and t != "key" { push(field.children, ct_part(ui_st, n, "ui-caret", "")) } - push(n.children, field) + ui_kept(field.children, ct_part(ui_st, n, "ui-value", ct_field_text(ui_st, n))) + if ui_st.fc_key == n.key and t != "key" { ui_kept(field.children, ct_part(ui_st, n, "ui-caret", "")) } + ui_kept(n.children, field) } } function ct_build_range(ui_st: mut UiState, n: UiNode) -> void { @@ -61,24 +61,25 @@ function ct_build_range(ui_st: mut UiState, n: UiNode) -> void { let track = ct_part(ui_st, n, "ui-track", "") let fill = ct_part(ui_st, n, "ui-fill", "") fill.w_pct = Math.clamp((v - lo) / (hi - lo), 0.0, 1.0) * 100.0 - push(track.children, fill) - push(track.children, ct_part(ui_st, n, "ui-thumb", "")) - push(n.children, track) - push(n.children, ct_part(ui_st, n, "ui-value", ct_value_text(n))) + ui_kept(track.children, fill) + ui_kept(track.children, ct_part(ui_st, n, "ui-thumb", "")) + ui_kept(n.children, track) + ui_kept(n.children, ct_part(ui_st, n, "ui-value", ct_value_shown(ui_st, n))) } function ct_build_select(ui_st: mut UiState, n: UiNode) -> void { + @alloc_ok("once per pooled node: its list is kept and cleared") if n.opts == null { n.opts = new []UiNode } # its options moved to a list the node keeps, not a new one let opts = n.opts List.clear(opts) - for i in 0 .. len(n.children) { push(opts, n.children[i]) } + for i in 0 .. len(n.children) { ui_kept(opts, n.children[i]) } List.clear(n.children) ct_label(ui_st, n) var shown = "" let at = ct_option_at(n, opts) if at >= 0 { shown = opts[at].text } - push(n.children, ct_part(ui_st, n, "ui-prev", "<")) - push(n.children, ct_part(ui_st, n, "ui-value", shown)) - push(n.children, ct_part(ui_st, n, "ui-next", ">")) + ui_kept(n.children, ct_part(ui_st, n, "ui-prev", "<")) + ui_kept(n.children, ct_part(ui_st, n, "ui-value", shown)) + ui_kept(n.children, ct_part(ui_st, n, "ui-next", ">")) } # which option the select's value names: an option's value="...", else its place function ct_option_at(n: UiNode, opts: []UiNode) -> int { @@ -89,7 +90,7 @@ function ct_option_at(n: UiNode, opts: []UiNode) -> int { } return -1 } -function ct_field_text(ui_st: UiState, n: UiNode) -> string { +function ct_field_text(ui_st: mut UiState, n: UiNode) -> string { let t = ui_attr_text(n, "type") if t == "key" { return ct_key_text(ui_st, n) } if t == "number" { return ct_num_shown(ui_st, n) } diff --git a/packages/ludic.ui/controls_number.ludic b/packages/ludic.ui/controls_number.ludic index 0c782d32..2ce6b237 100644 --- a/packages/ludic.ui/controls_number.ludic +++ b/packages/ludic.ui/controls_number.ludic @@ -65,7 +65,7 @@ function ct_num_blur(ui_st: mut UiState, root: UiNode) -> void { } ct_num_commit(ui_st, n) } -function ct_num_shown(ui_st: UiState, n: UiNode) -> string { +function ct_num_shown(ui_st: mut UiState, n: UiNode) -> string { if ui_st.ct_num_key == n.key { return ui_st.ct_num_text } - return ct_value_text(n) + return ct_value_shown(ui_st, n) } diff --git a/packages/ludic.ui/controls_parts.ludic b/packages/ludic.ui/controls_parts.ludic index b9cc82bc..04d8507b 100644 --- a/packages/ludic.ui/controls_parts.ludic +++ b/packages/ludic.ui/controls_parts.ludic @@ -5,14 +5,15 @@ function ct_label(ui_st: mut UiState, n: UiNode) -> void { let label = ui_attr_text(n, "label") let note = ui_attr_text(n, "note") if note == "" { - if label != "" { push(n.children, ct_part(ui_st, n, "ui-label", ui_tr(ui_st, label))) } + if label != "" { ui_kept(n.children, ct_part(ui_st, n, "ui-label", ui_tr(ui_st, label))) } return } let both = ct_part(ui_st, n, "ui-labels", "") - if label != "" { push(both.children, ct_part(ui_st, n, "ui-label", ui_tr(ui_st, label))) } - push(both.children, ct_part(ui_st, n, "ui-note", ui_tr(ui_st, note))) - push(n.children, both) + if label != "" { ui_kept(both.children, ct_part(ui_st, n, "ui-label", ui_tr(ui_st, label))) } + ui_kept(both.children, ct_part(ui_st, n, "ui-note", ui_tr(ui_st, note))) + ui_kept(n.children, both) } +@alloc_ok("a memo miss: once per value a range shows (memo_more.ludic)") function ct_value_text(n: UiNode) -> string { let v = ui_attr(n, "value") if Value.kind(v) == 0 { return "" } diff --git a/packages/ludic.ui/draw_gradient.ludic b/packages/ludic.ui/draw_gradient.ludic index c56f8bec..4025019c 100644 --- a/packages/ludic.ui/draw_gradient.ludic +++ b/packages/ludic.ui/draw_gradient.ludic @@ -18,6 +18,7 @@ function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient { if j >= 0 { m.grads[j] = g } return g } +@alloc_ok("a memo miss (gr_parse): once per gradient") function gr_parse_new(ui_st: mut UiState, s0: string) -> UiGradient { let s = tw(ui_st, s0) let parts = el_commas(s[16..len(s) - 1]) diff --git a/packages/ludic.ui/draw_order.ludic b/packages/ludic.ui/draw_order.ludic index 308b5312..b318fc01 100644 --- a/packages/ludic.ui/draw_order.ludic +++ b/packages/ludic.ui/draw_order.ludic @@ -11,10 +11,10 @@ function fr_clip_push(ui_st: mut UiState, x: float, y: float, w: float, h: float x1 = Math.min(x1, ui_st.fr_clips[n - 2]) y1 = Math.min(y1, ui_st.fr_clips[n - 1]) } - push(ui_st.fr_clips, x0) - push(ui_st.fr_clips, y0) - push(ui_st.fr_clips, x1) - push(ui_st.fr_clips, y1) + ui_kept(ui_st.fr_clips, x0) + ui_kept(ui_st.fr_clips, y0) + ui_kept(ui_st.fr_clips, x1) + ui_kept(ui_st.fr_clips, y1) ui_st.ui_be.clip(x0, y0, Math.max(x1 - x0, 0.0), Math.max(y1 - y0, 0.0)) } function fr_clip_pop(ui_st: mut UiState) -> void { @@ -28,7 +28,7 @@ function fr_clip_pop(ui_st: mut UiState) -> void { function fr_children(ui_st: mut UiState, n: UiNode) -> void { var positioned = 0 for i in 0 .. len(n.children) { - if n.children[i].popover { push(ui_st.pop_list, n.children[i]) } else if n.children[i].pos == 0 { fr_draw(ui_st, n.children[i]) } else { positioned += 1 } + if n.children[i].popover { ui_kept(ui_st.pop_list, n.children[i]) } else if n.children[i].pos == 0 { fr_draw(ui_st, n.children[i]) } else { positioned += 1 } } if positioned == 0 { return } var lo = 1000000 diff --git a/packages/ludic.ui/dump.ludic b/packages/ludic.ui/dump.ludic index 59abb8e4..06926019 100644 --- a/packages/ludic.ui/dump.ludic +++ b/packages/ludic.ui/dump.ludic @@ -1,5 +1,6 @@ # dump.ludic - a laid-out tree, for a test or a look # every node and where it went, one line each - for a test, or a look at a layout +@alloc_ok("developer builds only, once, under LUDIC_UI_DUMP") export function ui_dump(n: UiNode, depth: int) -> string { var pad = "" for d in 0 .. depth { pad = pad + " " } diff --git a/packages/ludic.ui/elem.ludic b/packages/ludic.ui/elem.ludic index 20a403dd..324d7d3b 100644 --- a/packages/ludic.ui/elem.ludic +++ b/packages/ludic.ui/elem.ludic @@ -24,7 +24,7 @@ function bd_elem(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string) -> UiNode if say == "no" { ui_st.tr_no = true } else if say == "yes" { ui_st.tr_no = false } bd_kids(ui_st, k.kids, e, key, n.children) if k.text != null { el_text(ui_st, n, ui_tr(ui_st, ui_eval_text(ui_st, k.text, e))) } - el_inline(n) + el_inline(ui_st, n) if k.tag == "input" or k.tag == "select" or k.tag == "progress" or k.tag == "meter" { ct_build(ui_st, n) } ui_st.tr_no = was_no return n @@ -37,7 +37,7 @@ function el_kind(ui_st: mut UiState, n: UiNode, tag: string) -> void { } else if lk_in(EL_TEXTS, tag) { n.kind = UI_TEXT } else if tag == "row" { n.dir = UI_ROW } else if tag == "button" { n.dir = UI_ROW n.focusable = true - } else if tag == "input" or tag == "select" { n.focusable = true } else if tag == "spacer" { n.kind = UI_SPACER } else if tag == "scroll" { n.kind = UI_SCROLL } else if tag == "img" { n.kind = UI_IMAGE } else if tag == "hr" { n.kind = UI_RULE } else if not lk_in(EL_BOXES, tag) { ui_err(ui_st, `<{tag}>: there is no such element`) } + } else if tag == "input" or tag == "select" { n.focusable = true } else if tag == "spacer" { n.kind = UI_SPACER } else if tag == "scroll" { n.kind = UI_SCROLL } else if tag == "img" { n.kind = UI_IMAGE } else if tag == "hr" { n.kind = UI_RULE } else if not lk_in(EL_BOXES, tag) { el_unknown(ui_st, tag) } } # a box with words in it holds them as a text of its own, as
words
does function el_text(ui_st: mut UiState, n: UiNode, s: string) -> void { @@ -49,7 +49,7 @@ function el_text(ui_st: mut UiState, n: UiNode, s: string) -> void { t.kind = UI_TEXT t.text = s t.env = n.env - push(n.children, t) + ui_kept(n.children, t) } # id, class, hidden, disabled and src say what the element is; the rest are applied after the rules # an attribute's text is worked out only where it is read as text (the rest keep their Value; a selector @@ -58,18 +58,21 @@ function el_keep(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void { if key == "id" { n.id = vl_text_u(ui_st, v) } else if key == "class" { let parts = mm_split(ui_st, vl_text_u(ui_st, v), 32) for i in 0 .. len(parts) { - if len(parts[i]) > 0 { push(n.classes, parts[i]) } + if len(parts[i]) > 0 { ui_kept(n.classes, parts[i]) } } } else if key == "popover" { n.popover = true n.pos = 2 - push(n.akeys, key) - push(n.avals, "") - push(n.vals, v) + ui_kept(n.akeys, key) + ui_kept(n.avals, "") + ui_kept(n.vals, v) } else if key == "tabindex" { n.focusable = vl_text_u(ui_st, v) != "-1" } else if key == "hidden" { n.hidden = el_flag(v) } else if key == "disabled" { n.enabled = not el_flag(v) } else if key == "src" { n.src = vl_text_u(ui_st, v) } else { if key == "enabled" { n.enabled = el_flag(v) } - push(n.akeys, key) - push(n.avals, "") - push(n.vals, v) + ui_kept(n.akeys, key) + ui_kept(n.avals, "") + ui_kept(n.vals, v) } } + +@alloc_ok("only when a template is wrong: a broken screen, never a working one") +function el_unknown(ui_st: mut UiState, tag: string) -> void { ui_err(ui_st, `<{tag}>: there is no such element`) } diff --git a/packages/ludic.ui/env.ludic b/packages/ludic.ui/env.ludic index 52a8f937..bf432b35 100644 --- a/packages/ludic.ui/env.ludic +++ b/packages/ludic.ui/env.ludic @@ -26,8 +26,8 @@ function env_new(ui_st: mut UiState, parent: UiEnv) -> UiEnv { return e } function env_bind(e: UiEnv, name: string, v: Val) -> void { - push(e.names, name) - push(e.vals, v) + ui_kept(e.names, name) + ui_kept(e.vals, v) } function env_get(ui_st: mut UiState, e: UiEnv, name: string) -> Val { var at = e @@ -48,6 +48,7 @@ function env_get(ui_st: mut UiState, e: UiEnv, name: string) -> Val { cx = cx.parent } if e.model != null and Value.has(e.model, name) != 0 { return Value.get(e.model, name) } + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `no {name} here: not a loop's, a prop, a state or in the view`) return Value.null() } @@ -58,6 +59,7 @@ function env_set(ui_st: mut UiState, e: UiEnv, name: string, v: Val) -> void { if at.inst != null { let args = Value.list() Value.add(args, v) + @alloc_ok("on an action that sets a component's state, not a frame") if Value.kind(at.inst.cls.call(at.inst.ptr, `set:{name}`, args, null)) != 99 { return } } if at.state != null and Value.has(at.state, name) != 0 { @@ -66,10 +68,12 @@ function env_set(ui_st: mut UiState, e: UiEnv, name: string, v: Val) -> void { } at = at.parent } + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `set {name}: no state here holds it - declare it with `) } # an error is said once, however many frames make it, and names the template line being built # said by the runtime, or by a host about something a template asked for +@alloc_ok("only when a template is wrong: a broken screen, never a working one") export function ui_err(ui_st: mut UiState, msg0: string) -> void { var msg = msg0 if ui_st.ui_at != "" and not ui_has(msg, ".xml:") { msg = `{ui_st.ui_at}: {msg}` } diff --git a/packages/ludic.ui/eval.ludic b/packages/ludic.ui/eval.ludic index 237bf2ec..c30df4d4 100644 --- a/packages/ludic.ui/eval.ludic +++ b/packages/ludic.ui/eval.ludic @@ -6,6 +6,7 @@ function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val { let o = ui_eval(ui_st, x.a, e) if Value.kind(o) == 6 and Value.has(o, x.s) != 0 { return Value.get(o, x.s) } if Value.kind(o) == 5 and x.s == "length" { return uv_int(ui_st, Value.count(o)) } + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `.{x.s}: there is no such field`) return Value.null() } @@ -31,6 +32,7 @@ function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val { # a call: len() and range() are the template's own, anything else is the view's function ev_call(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val { let depth = ui_st.ev_depth + @alloc_ok("once per depth of nesting, then kept") while len(ui_st.ev_args) <= depth { push(ui_st.ev_args, Value.list()) } let args = ui_st.ev_args[depth] Value.clear(args) @@ -57,6 +59,7 @@ function ev_call_with(ui_st: mut UiState, x: UiExpr, e: UiEnv, args: Val) -> Val at = at.parent } if ui_st.ui_view == null or ui_st.ui_view.call == null { + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `{x.s}(): there is no function or event of that name here`) return Value.null() } @@ -68,7 +71,7 @@ function ev_into(ui_st: mut UiState) -> Val { let v = uv_hold(ui_st) if v != null { return v } let ring = ui_st.ev_ring - if len(ring) < EV_RING { push(ring, Value.null()) } + if len(ring) < EV_RING { ui_kept(ring, Value.null()) } let at = ui_st.ev_ring_at % len(ring) ui_st.ev_ring_at = at + 1 return ring[at] diff --git a/packages/ludic.ui/eval_text.ludic b/packages/ludic.ui/eval_text.ludic index f7ca57b6..ef342b64 100644 --- a/packages/ludic.ui/eval_text.ludic +++ b/packages/ludic.ui/eval_text.ludic @@ -3,6 +3,7 @@ # text is built only the first time it reads that way. The memo is bounded; past it a text is built as ever function ev_text(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val { let depth = ui_st.tx_depth # a hole may hold a text of its own: a kept list of pieces a depth + @alloc_ok("once per depth of nesting, then kept") while len(ui_st.tx_pieces) <= depth { push(ui_st.tx_pieces, new []string) } let ps = ui_st.tx_pieces[depth] ui_st.tx_depth = depth + 1 @@ -41,17 +42,17 @@ function tx_same(ps: []string, k: string) -> bool { function tx_piece(ui_st: mut UiState, ps: []string, v: Val) -> void { let k = Value.kind(v) if k != 2 and k != 7 { - push(ps, vl_text_u(ui_st, v)) + ui_kept(ps, vl_text_u(ui_st, v)) return } var t = int(Math.round(Value.as_float(v) * 10.0)) if t < 0 { - push(ps, "-") + ui_kept(ps, "-") t = 0 - t } - push(ps, mm_int(ui_st, t / 10)) + ui_kept(ps, mm_int(ui_st, t / 10)) let d = t % 10 - if d != 0 { push(ps, tx_tenth(d)) } + if d != 0 { ui_kept(ps, tx_tenth(d)) } } function tx_tenth(d: int) -> string { if d == 1 { return ".1" } diff --git a/packages/ludic.ui/focus.ludic b/packages/ludic.ui/focus.ludic index 01a1088c..ce0aa339 100644 --- a/packages/ludic.ui/focus.ludic +++ b/packages/ludic.ui/focus.ludic @@ -2,7 +2,7 @@ # Shift+Tab, or the arrows), Enter and Space activate what has it, and a pointer press focuses what # it presses; a key field that is listening has every key to itself. A key that is gone from the screen leaves nothing focused. function fc_collect(ui_st: mut UiState, n: UiNode) -> void { - if n.focusable and n.enabled { push(ui_st.fc_list, n) } + if n.focusable and n.enabled { ui_kept(ui_st.fc_list, n) } for i in 0 .. len(n.children) { fc_collect(ui_st, n.children[i]) } } function fc_find(ui_st: UiState) -> int { diff --git a/packages/ludic.ui/frame.ludic b/packages/ludic.ui/frame.ludic index 11819017..cb6f2420 100644 --- a/packages/ludic.ui/frame.ludic +++ b/packages/ludic.ui/frame.ludic @@ -35,6 +35,7 @@ export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> Ui let cls = uc_find(ui_st, screen) if si < 0 and cls != null { return fr_class_nodes(ui_st, cls) } if si < 0 { + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `there is no screen {screen} - ui_load the file that has it`) return null } diff --git a/packages/ludic.ui/frame_box.ludic b/packages/ludic.ui/frame_box.ludic index d44682f1..ff73525c 100644 --- a/packages/ludic.ui/frame_box.ludic +++ b/packages/ludic.ui/frame_box.ludic @@ -15,7 +15,7 @@ function fb_keep(ui_st: mut UiState, screen: string, root: UiNode) -> void { return } } - push(ui_st.fb_roots, root) + ui_kept(ui_st.fb_roots, root) } # the box of the element with that id (or key) on any screen shown, in screen pixels; null if none export function ui_box(ui_st: UiState, id: string) -> UiBox { diff --git a/packages/ludic.ui/index.ludic b/packages/ludic.ui/index.ludic index 76ee8c46..54dbc3e5 100644 --- a/packages/ludic.ui/index.ludic +++ b/packages/ludic.ui/index.ludic @@ -7,10 +7,12 @@ import "node.ludic" import "node_more.ludic" import "view.ludic" import "backend.ludic" +import "kept.ludic" import "pool.ludic" import "pool_reset.ludic" import "pool_reset_more.ludic" import "memo.ludic" +import "memo_more.ludic" import "memo_join.ludic" import "eval_text.ludic" import "units_pieces.ludic" @@ -94,6 +96,7 @@ import "hold.ludic" import "pointer.ludic" import "pointer_pick.ludic" import "pointer_events.ludic" +import "pointer_ring.ludic" import "screen_class.ludic" import "draw.ludic" import "draw_content.ludic" diff --git a/packages/ludic.ui/instance.ludic b/packages/ludic.ui/instance.ludic index 6f983a19..1c256d61 100644 --- a/packages/ludic.ui/instance.ludic +++ b/packages/ludic.ui/instance.ludic @@ -21,11 +21,12 @@ function in_get(ui_st: mut UiState, key: string, k: UiClass) -> UiInst { let it = in_reuse(ui_st, k) it.key = key it.seen = ui_st.in_build - push(ui_st.in_list, it) + ui_kept(ui_st.in_list, it) return it } # an unmounted instance of this class, put back as new, else a new one: a prompt that comes and goes # as the player walks is mounted again without making another record, props and model each time +@alloc_ok("a component instance: made once per instance key, then kept or reused") function in_reuse(ui_st: mut UiState, k: UiClass) -> UiInst { let l = ui_st.in_free for i in 0 .. len(l) { @@ -50,7 +51,7 @@ function in_sweep(ui_st: mut UiState, screen: string) -> void { if it.seen == ui_st.in_build or not lc_mine(it.key, screen) { ui_st.in_list[kept] = it kept += 1 - } else { push(ui_st.in_free, it) } + } else { ui_kept(ui_st.in_free, it) } } while len(ui_st.in_list) > kept { List.pop(ui_st.in_list) } } diff --git a/packages/ludic.ui/kept.ludic b/packages/ludic.ui/kept.ludic new file mode 100644 index 00000000..1ff3088a --- /dev/null +++ b/packages/ludic.ui/kept.ludic @@ -0,0 +1,6 @@ +# kept.ludic - the one push a frame may make: into a list the pool or the state keeps and clears, +# which grows to the most it has held and stays there +function ui_kept(xs: []T, v: T) -> void { + @alloc_ok("a kept list: cleared and filled again, it grows to the most it has held once") + push(xs, v) +} diff --git a/packages/ludic.ui/layout_fit.ludic b/packages/ludic.ui/layout_fit.ludic index 3501d72d..759e9ffc 100644 --- a/packages/ludic.ui/layout_fit.ludic +++ b/packages/ludic.ui/layout_fit.ludic @@ -31,9 +31,9 @@ function ui_object_fit_into(out: []float, n: UiNode, nat_w: float, nat_h: float) h = nat_h * s } List.clear(out) - push(out, bx + (bw - w) / 2.0) - push(out, by + (bh - h) / 2.0) - push(out, w) - push(out, h) + ui_kept(out, bx + (bw - w) / 2.0) + ui_kept(out, by + (bh - h) / 2.0) + ui_kept(out, w) + ui_kept(out, h) return out } diff --git a/packages/ludic.ui/layout_run.ludic b/packages/ludic.ui/layout_run.ludic index 5bc9fb62..a8931326 100644 --- a/packages/ludic.ui/layout_run.ludic +++ b/packages/ludic.ui/layout_run.ludic @@ -45,7 +45,7 @@ function ly_place_line(ui_st: mut UiState, n: UiNode, from: int, to: int, top: f ui_st.ly_depth += 1 let sizes = ly_scratch(ui_st, depth) for i in from .. to { - push(sizes, ly_basis(n.children[i], n.dir, main_room)) + ui_kept(sizes, ly_basis(n.children[i], n.dir, main_room)) if n.children[i].pos < 2 { used = used + sizes[i - from] grows = grows + ly_grow(n.children[i], n.dir) @@ -73,7 +73,9 @@ function ly_place_line(ui_st: mut UiState, n: UiNode, from: int, to: int, top: f # their own lines while it is still placing them), emptied rather than made anew function ly_scratch(ui_st: mut UiState, depth: int) -> []float { while len(ui_st.ly_sizes) <= depth { + @alloc_ok("once per depth of nesting, then kept") push(ui_st.ly_sizes, new []float) + @alloc_ok("once per depth of nesting, then kept") push(ui_st.ly_frozen, new []bool) } List.clear(ui_st.ly_sizes[depth]) diff --git a/packages/ludic.ui/layout_shrink.ludic b/packages/ludic.ui/layout_shrink.ludic index b006cd36..5f52ce6a 100644 --- a/packages/ludic.ui/layout_shrink.ludic +++ b/packages/ludic.ui/layout_shrink.ludic @@ -27,7 +27,7 @@ function ly_shrink_line(ui_st: mut UiState, depth: int, n: UiNode, from: int, si var over = over0 let frozen = ui_st.ly_frozen[depth] List.clear(frozen) - for k in 0 .. len(sizes) { push(frozen, n.children[from + k].pos >= 2) } + for k in 0 .. len(sizes) { ui_kept(frozen, n.children[from + k].pos >= 2) } for pass in 0 .. 8 { var wsum = 0.0 for k in 0 .. len(sizes) { diff --git a/packages/ludic.ui/layout_text.ludic b/packages/ludic.ui/layout_text.ludic index 012a11ac..e438ea9a 100644 --- a/packages/ludic.ui/layout_text.ludic +++ b/packages/ludic.ui/layout_text.ludic @@ -19,7 +19,8 @@ function ly_measure_text(ui_st: mut UiState, n: UiNode, avail: float) -> void { n.mw = n.mw + widest n.mh = n.mh + lh * float(len(n.lines)) } -function ly_wrap(ui_st: mut UiState, s: string, size: float, room: float) -> []string { +@alloc_ok("a memo miss: once per text, size and room (memo_more.ludic), bounded by MM_CAP") +function ly_wrap_new(ui_st: mut UiState, s: string, size: float, room: float) -> []string { let words = mm_split(ui_st, s, 32) let out = new []string var line = "" @@ -35,9 +36,9 @@ function ly_wrap(ui_st: mut UiState, s: string, size: float, room: float) -> []s if len(line) > 0 { push(out, line) } return out } -# a line cut to `room` with an ellipsis, when it does not fit -function ly_ellipsis(ui_st: UiState, s: string, size: float, room: float) -> string { - if ly_text_w(ui_st, s, size) <= room { return s } +# a line cut to `room` with an ellipsis (it does not fit) +@alloc_ok("a memo miss: once per text, size and room (memo_more.ludic), bounded by MM_CAP") +function ly_ellipsis_new(ui_st: UiState, s: string, size: float, room: float) -> string { var n = len(s) while n > 0 and ly_text_w(ui_st, s[0..n] + "...", size) > room { n -= 1 } return s[0..n] + "..." diff --git a/packages/ludic.ui/lib.ludic b/packages/ludic.ui/lib.ludic index caf71545..1588fc0c 100644 --- a/packages/ludic.ui/lib.ludic +++ b/packages/ludic.ui/lib.ludic @@ -22,6 +22,7 @@ function lib_file(ui_st: mut UiState, path: string) -> UiFile { } let src = lib_text(ui_st, path) if len(src) == 0 { + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `{path}: cannot read it`) return null } @@ -64,6 +65,7 @@ function lib_top(ui_st: mut UiState, f: UiFile, t: UiTpl) -> void { } let name = bd_name(t, "name", "") if name == "" or (t.tag != "component" and t.tag != "screen") { + @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `{f.path}: <{t.tag}> - a file holds s,