From f8825dc55a3cd1697adc95f5bfea540f23334a08 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Mon, 28 Sep 2026 16:46:00 +0300 Subject: [PATCH] ludic.ui, ludic.i18n: frame_keeps and birth_leaks to 0 - :nth-child's a and b read once per argument, a border-image's miss its own function; typing, the dev dump, parsing, the pointer ring's making, Ln's counted line and the plural header declared Co-Authored-By: Claude Opus 5.5 --- packages/ludic.i18n/plural.ludic | 2 + packages/ludic.ui/anim.ludic | 1 + packages/ludic.ui/attr_edge.ludic | 21 +++++++---- packages/ludic.ui/backend.ludic | 2 + packages/ludic.ui/controls.ludic | 1 + packages/ludic.ui/controls_number.ludic | 1 + packages/ludic.ui/controls_parts.ludic | 1 + packages/ludic.ui/dump.ludic | 2 + packages/ludic.ui/frame_box.ludic | 1 + packages/ludic.ui/link.ludic | 1 + packages/ludic.ui/pointer_ring.ludic | 2 + packages/ludic.ui/select_match.ludic | 2 +- packages/ludic.ui/select_nth.ludic | 50 ++++++++++++++++++------- packages/ludic.ui/select_parts.ludic | 1 + packages/ludic.ui/style.ludic | 1 + packages/ludic.ui/tpl.ludic | 1 + 16 files changed, 67 insertions(+), 23 deletions(-) diff --git a/packages/ludic.i18n/plural.ludic b/packages/ludic.i18n/plural.ludic index 4c9021dc..165551fb 100644 --- a/packages/ludic.i18n/plural.ludic +++ b/packages/ludic.i18n/plural.ludic @@ -5,6 +5,7 @@ export const PL_ABOVE_ONE: int = 1 # French, Portuguese (0 and 1 are singular) export const PL_ONE_FORM: int = 2 # Japanese, Chinese export const PL_SLAVIC: int = 3 # Russian, Ukrainian: one, few, many +@alloc_ok("on a language change: its plural rule read from the header") function plural_rule_of(h: string) -> int { let k = i18n_find(h, len(h), 0, "plural=", 7) if k < 0 { return PL_NOT_ONE } @@ -56,6 +57,7 @@ export function Ln(i18n_st: I18nState, one: string, many: string, n: int) -> str return count_in(t, n) } +@alloc_ok("a counted line (Ln) made when the game asks for one, outside a frame; its caller keeps it") function count_in(t: string, n: int) -> string { let m = len(t) var r = "" diff --git a/packages/ludic.ui/anim.ludic b/packages/ludic.ui/anim.ludic index 5403e39c..505c1d2e 100644 --- a/packages/ludic.ui/anim.ludic +++ b/packages/ludic.ui/anim.ludic @@ -12,6 +12,7 @@ function an_now(ui_st: UiState) -> float { return float(ui_st.an_frame) / 60.0 } # @keyframes NAME { from {...} 50% {...} to {...} } +@alloc_ok("a stylesheet being read: once per component class (uc_load)") function an_read(ui_st: mut UiState, name: string, body: string, err: []string) -> void { let k = new UiKeyframes k.name = tpl_words(name) diff --git a/packages/ludic.ui/attr_edge.ludic b/packages/ludic.ui/attr_edge.ludic index 94a2c159..f89a8b45 100644 --- a/packages/ludic.ui/attr_edge.ludic +++ b/packages/ludic.ui/attr_edge.ludic @@ -26,14 +26,8 @@ function el_url(s: string) -> string { function el_border_image(ui_st: mut UiState, n: UiNode, s: string) -> void { let m = mm_of(ui_st) var at = mm_find(m, MM_BIMG, s) - if at < 0 { - var close = 0 - while close < len(s) and s[close] != 41 { close += 1 } - at = mm_add(m, MM_BIMG, s) - if at < 0 { return } - m.texts[at] = el_url(s[0..Math.min(close + 1, len(s))]) - m.lists[at] = lss_split(s[Math.min(close + 1, len(s))..len(s)], 47) - } + if at < 0 { at = el_bimg_read(m, s) } + if at < 0 { return } n.bi_src = m.texts[at] let rest = m.lists[at] n.bi_slice = el_num(tw(ui_st, rest[0])) @@ -95,3 +89,14 @@ function el_secs(ui_st: mut UiState, s: string) -> float { } return 0.0 } + +@alloc_ok("a memo miss: once per border-image value, bounded by MM_CAP") +function el_bimg_read(m: UiMemo, s: string) -> int { + var close = 0 + while close < len(s) and s[close] != 41 { close += 1 } + let at = mm_add(m, MM_BIMG, s) + if at < 0 { return -1 } + m.texts[at] = el_url(s[0..Math.min(close + 1, len(s))]) + m.lists[at] = lss_split(s[Math.min(close + 1, len(s))..len(s)], 47) + return at +} diff --git a/packages/ludic.ui/backend.ludic b/packages/ludic.ui/backend.ludic index 846b54c8..71e557d3 100644 --- a/packages/ludic.ui/backend.ludic +++ b/packages/ludic.ui/backend.ludic @@ -55,6 +55,8 @@ export state UiState { 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 + nth_a: int = 0 # select_nth.ludic: the a and b just read + nth_b: 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/controls.ludic b/packages/ludic.ui/controls.ludic index 37a447f4..fcea9b18 100644 --- a/packages/ludic.ui/controls.ludic +++ b/packages/ludic.ui/controls.ludic @@ -68,6 +68,7 @@ function ct_drag(ui_st: mut UiState, root: UiNode, key: string, x: float, y: flo if n != null { ct_press(ui_st, n, x, y) } } # typing into a focused text field, or the key a key field is waiting for +@alloc_ok("on a key typed into a field, not a frame") function ct_typed(ui_st: mut UiState, n: UiNode) -> void { let t = ct_type(n) if t == "key" { ct_key_typed(ui_st, n) } diff --git a/packages/ludic.ui/controls_number.ludic b/packages/ludic.ui/controls_number.ludic index 2ce6b237..e702e137 100644 --- a/packages/ludic.ui/controls_number.ludic +++ b/packages/ludic.ui/controls_number.ludic @@ -37,6 +37,7 @@ function ct_num_step(ui_st: mut UiState, n: UiNode, d: int) -> void { ct_num_set(ui_st, n, base + ct_num_step_of(n) * float(d)) } # what was typed this frame, the characters a number can hold; Backspace takes one back +@alloc_ok("on a key typed into a number field, not a frame") function ct_num_typed(ui_st: mut UiState, n: UiNode) -> void { if ui_st.fc_key != n.key or (ui_st.in_now.typed == "" and not ui_st.in_now.backspace) { return } if ui_st.ct_num_key != n.key { diff --git a/packages/ludic.ui/controls_parts.ludic b/packages/ludic.ui/controls_parts.ludic index 04d8507b..291fb27a 100644 --- a/packages/ludic.ui/controls_parts.ludic +++ b/packages/ludic.ui/controls_parts.ludic @@ -31,6 +31,7 @@ function ct_value_text(n: UiNode) -> string { return s + ui_attr_text(n, "unit") } # a number with `places` digits after the point, rounded +@alloc_ok("a memo miss (ct_value_shown): once per value a range shows") function ct_fixed(f: float, places: int) -> string { var k = 1 for i in 0 .. Math.max(places, 0) { k = k * 10 } diff --git a/packages/ludic.ui/dump.ludic b/packages/ludic.ui/dump.ludic index 06926019..c13344de 100644 --- a/packages/ludic.ui/dump.ludic +++ b/packages/ludic.ui/dump.ludic @@ -11,6 +11,7 @@ export function ui_dump(n: UiNode, depth: int) -> string { return out } # tag#id.class.class, or the kind of a node the template did not name +@alloc_ok("developer builds only, once, under LUDIC_UI_DUMP") function dp_name(n: UiNode) -> string { var s = n.tag if s == "" { s = `({n.kind})` } @@ -21,6 +22,7 @@ function dp_name(n: UiNode) -> string { return s } # the computed styles that differ from an element's defaults +@alloc_ok("developer builds only, once, under LUDIC_UI_DUMP") function dp_style(n: UiNode) -> string { var s = "" if n.size > 0.0 { s = s + ` font {int(n.size)}` } diff --git a/packages/ludic.ui/frame_box.ludic b/packages/ludic.ui/frame_box.ludic index ff73525c..24f63b3a 100644 --- a/packages/ludic.ui/frame_box.ludic +++ b/packages/ludic.ui/frame_box.ludic @@ -18,6 +18,7 @@ function fb_keep(ui_st: mut UiState, screen: string, root: UiNode) -> void { 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 +@alloc_ok("a question the game asks now and then (an anchor, a screen's box), never per frame") export function ui_box(ui_st: UiState, id: string) -> UiBox { for i in 0 .. len(ui_st.fb_roots) { let n = fb_find(ui_st.fb_roots[i], id) diff --git a/packages/ludic.ui/link.ludic b/packages/ludic.ui/link.ludic index 51b42018..8eeb060a 100644 --- a/packages/ludic.ui/link.ludic +++ b/packages/ludic.ui/link.ludic @@ -28,6 +28,7 @@ function lk_in(words: string, w: string) -> bool { return false } # `any` finds an unexported one too, to say why it cannot be used +@alloc_ok("a template being linked: once per component class (uc_load)") function lk_find(f: UiFile, tag: string, any: bool) -> UiTpl { var colon = -1 for i in 0 .. len(tag) { diff --git a/packages/ludic.ui/pointer_ring.ludic b/packages/ludic.ui/pointer_ring.ludic index 3fac5168..e7ef1c39 100644 --- a/packages/ludic.ui/pointer_ring.ludic +++ b/packages/ludic.ui/pointer_ring.ludic @@ -1,11 +1,13 @@ # pointer_ring.ludic - a pointer event's record and the Value its handler is given, made once # events are made once and taken in turn: more than a frame fires before its handlers have run const PE_RING: int = 16 +@alloc_ok("once: made with the state") function pe_evs__new() -> []UiPointer { let xs = new []UiPointer for i in 0 .. PE_RING { push(xs, new UiPointer) } return xs } +@alloc_ok("once: made with the state") function pe_vals__new() -> []Val { let xs = new []Val for i in 0 .. PE_RING { diff --git a/packages/ludic.ui/select_match.ludic b/packages/ludic.ui/select_match.ludic index 4476b742..c1a4781e 100644 --- a/packages/ludic.ui/select_match.ludic +++ b/packages/ludic.ui/select_match.ludic @@ -77,7 +77,7 @@ function sel_pseudo_ok(ui_st: mut UiState, p: string, arg: string, n: UiNode) -> if p == "checked" { return ui_attr_on(n, "checked") } if p == "active" { return fr_active(ui_st, n) } if p == "capturing" { return ui_st.ct_capture != "" and n.key == ui_st.ct_capture } - if p == "nth-child" { return sel_nth(arg, n.index + 1) } + if p == "nth-child" { return sel_nth(ui_st, arg, n.index + 1) } @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `:{p}: there is no such pseudo-class here`) return false diff --git a/packages/ludic.ui/select_nth.ludic b/packages/ludic.ui/select_nth.ludic index 4c69cce6..7d9f4778 100644 --- a/packages/ludic.ui/select_nth.ludic +++ b/packages/ludic.ui/select_nth.ludic @@ -1,21 +1,43 @@ # select_nth.ludic - :nth-child's formulas -# :nth-child(odd), (even), (3), (2n+1), (n+2), (-n+3): is the k-th child one of them -function sel_nth(arg: string, k: int) -> bool { +# :nth-child(odd), (even), (3), (2n+1), (n+2), (-n+3): is the k-th child one of them. The a and b of +# an + b are read once per argument (matched per element per frame, a read made text every time) +const MM_NTH: int = -16 # n1 a, n2 b +function sel_nth(ui_st: mut UiState, arg: string, k: int) -> bool { if arg == "odd" { return k % 2 == 1 } if arg == "even" { return k % 2 == 0 } - var npos = -1 - for i in 0 .. len(arg) { - if arg[i] == 110 { npos = i } - } - if npos < 0 { return string(k) == arg } - var a = 1 - let head: string = arg[0..npos] - if head == "-" { a = -1 } else if len(head) > 0 { a = int(el_num(head)) } - var b = 0 - var tail = tpl_words(arg[npos + 1..len(arg)]) - if len(tail) > 0 and tail[0] == 43 { tail = tpl_words(tail[1..len(tail)]) } - if len(tail) > 0 { b = int(el_num(tail)) } + let m = mm_of(ui_st) + let i = mm_find(m, MM_NTH, arg) + if i >= 0 { + ui_st.nth_a = int(m.n1[i]) + ui_st.nth_b = int(m.n2[i]) + } else { sel_nth_read(ui_st, m, arg) } + let a = ui_st.nth_a + let b = ui_st.nth_b if a == 0 { return k == b } let d = k - b return d % a == 0 and d / a >= 0 } +@alloc_ok("a memo miss: once per :nth-child argument, bounded by MM_CAP") +function sel_nth_read(ui_st: mut UiState, m: UiMemo, arg: string) -> void { + var npos = -1 + for i in 0 .. len(arg) { + if arg[i] == 110 { npos = i } + } + var a = 0 + var b = 0 + if npos < 0 { b = int(el_num(arg)) } else { + a = 1 + let head: string = arg[0..npos] + if head == "-" { a = -1 } else if len(head) > 0 { a = int(el_num(head)) } + var tail = tpl_words(arg[npos + 1..len(arg)]) + if len(tail) > 0 and tail[0] == 43 { tail = tpl_words(tail[1..len(tail)]) } + if len(tail) > 0 { b = int(el_num(tail)) } + } + ui_st.nth_a = a + ui_st.nth_b = b + let j = mm_add(m, MM_NTH, arg) + if j >= 0 { + m.n1[j] = float(a) + m.n2[j] = float(b) + } +} diff --git a/packages/ludic.ui/select_parts.ludic b/packages/ludic.ui/select_parts.ludic index a0d7ffbe..f3e2b027 100644 --- a/packages/ludic.ui/select_parts.ludic +++ b/packages/ludic.ui/select_parts.ludic @@ -13,6 +13,7 @@ function sel_attr(r: UiRd, s: UiSel) -> void { } if not rd_op(r, "]") { rd_fail(r, "']'") } } +@alloc_ok("a stylesheet being read: once per component class (uc_load)") function sel_pseudo(r: UiRd, s: UiSel) -> void { r.i += 1 let name = sel_name(r) diff --git a/packages/ludic.ui/style.ludic b/packages/ludic.ui/style.ludic index ca05ff00..4a654a82 100644 --- a/packages/ludic.ui/style.ludic +++ b/packages/ludic.ui/style.ludic @@ -27,6 +27,7 @@ function lss_file(ui_st: mut UiState, src: string, path: string) -> UiFile { f.sheet = sheet_of(f) return f } +@alloc_ok("a stylesheet being read: once per component class (uc_load)") function lss_text(ui_st: mut UiState, f: UiFile, src: string) -> void { let err = new []string lss_block(ui_st, f, lss_strip(src), null, err) diff --git a/packages/ludic.ui/tpl.ludic b/packages/ludic.ui/tpl.ludic index 0551121c..755caec4 100644 --- a/packages/ludic.ui/tpl.ludic +++ b/packages/ludic.ui/tpl.ludic @@ -16,6 +16,7 @@ export property UiTpl { raw: string = "" # a