runtime, ludic.ui: a scalar Value is one allocation, not three (lists only for a list or an object); layout and cascade lists kept a depth; small numbers' text made once - benchmark 63 -> 36 KB a frame
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1d40f6d95a
commit
46888b6716
14 changed files with 84 additions and 33 deletions
|
|
@ -12,7 +12,7 @@ function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool {
|
|||
ui_st.el_cur = n
|
||||
ui_st.el_key = k
|
||||
if len(k) > 2 and sat(k, 0, "--") {
|
||||
cs_var_set(n, k, vl_text(v0))
|
||||
cs_var_set(n, k, vl_text_u(ui_st, v0))
|
||||
return true
|
||||
}
|
||||
let v = cs_vars_in(ui_st, n, v0)
|
||||
|
|
@ -48,7 +48,7 @@ function el_size(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
|||
return true
|
||||
}
|
||||
function el_flex(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
||||
let s = vl_text(v)
|
||||
let s = vl_text_u(ui_st, v)
|
||||
if key == "dir" {
|
||||
n.dir = UI_COL
|
||||
if s == "row" { n.dir = UI_ROW }
|
||||
|
|
@ -56,7 +56,7 @@ function el_flex(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
|||
return true
|
||||
}
|
||||
function el_look(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
||||
let s = vl_text(v)
|
||||
let s = vl_text_u(ui_st, v)
|
||||
if key == "size" { n.size = el_len(ui_st, v) } else if key == "color" {
|
||||
n.fg = ui_color(ui_st, s)
|
||||
n.fg_a = ui_st.cl_alpha
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function el_sides(ui_st: mut UiState, v: Val) -> void {
|
|||
ui_st.el_s0 = el_px(ui_st, Value.as_float(v))
|
||||
n = 1
|
||||
} else {
|
||||
let words = mm_words(ui_st, vl_text(v))
|
||||
let words = mm_words(ui_st, vl_text_u(ui_st, v))
|
||||
for i in 0 .. len(words) {
|
||||
if len(words[i]) > 0 and n < 4 {
|
||||
el_side_set(ui_st, n, el_word_len(ui_st, words[i]))
|
||||
|
|
@ -43,7 +43,7 @@ function el_side_set(ui_st: mut UiState, i: int, v: float) -> void {
|
|||
# a length: a number, 10px, 1.5em, 2rem, 50vw, calc(...), fill, fit or auto (a percentage is el_pct's)
|
||||
function el_len(ui_st: mut UiState, v: Val) -> float {
|
||||
if vl_num(v) { return el_px(ui_st, Value.as_float(v)) }
|
||||
let s = vl_text(v)
|
||||
let s = vl_text_u(ui_st, v)
|
||||
if s == "fill" { return UI_FILL }
|
||||
if ca_is(s) { return el_calc_len(ui_st, s) }
|
||||
if s == "fit" or s == "auto" or (len(s) > 0 and s[len(s) - 1] == 37) { return 0.0 }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# attr_edge.ludic - border-radius, outline (and its width, colour and offset), scrollbar-color
|
||||
function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
||||
let s = vl_text(v)
|
||||
let s = vl_text_u(ui_st, v)
|
||||
if key == "border-radius" { n.radius = el_len(ui_st, v) } else if key == "outline" { el_outline(ui_st, n, s) } else if key == "outline-width" { n.outline_w = el_len(ui_st, v) } else if key == "outline-offset" { n.outline_off = el_len(ui_st, v) } else if key == "outline-color" {
|
||||
n.outline_c = ui_color(ui_st, s)
|
||||
n.outline_a = ui_st.cl_alpha
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# percentage is kept apart and taken of the containing block's width (left, right) or height (top,
|
||||
# bottom) when the element is placed
|
||||
function el_inset(ui_st: mut UiState, n: UiNode, side: int, v: Val) -> float {
|
||||
let s = vl_text(v)
|
||||
let s = vl_text_u(ui_st, v)
|
||||
var pct = 0.0
|
||||
var px = UI_AUTO
|
||||
if s != "auto" {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# attr_more.ludic - width and height (where 0 is 0 and calc() may hold a percentage), the flex
|
||||
# shorthand, flex-shrink, order, text-shadow, pointer-events, text-fit and line-height (text_fit.ludic)
|
||||
function el_more(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
||||
let s = vl_text(v)
|
||||
let s = vl_text_u(ui_st, v)
|
||||
if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(ui_st, n, s) } else if key == "pointer-events" { nx(n).no_pointer = tpl_words(s) == "none" } else if key == "text-fit" { el_text_fit(ui_st, n, s) } else if key == "line-height" { el_line_h(ui_st, n, s) } else { return el_fit_props(ui_st, n, key, s) }
|
||||
return true
|
||||
}
|
||||
# width or height: a length (0 included), fill, a percentage, calc(), or auto/fit (none)
|
||||
function el_dim(ui_st: mut UiState, n: UiNode, wide: bool, v: Val) -> void {
|
||||
let s = vl_text(v)
|
||||
let s = vl_text_u(ui_st, v)
|
||||
var fixed = UI_AUTO
|
||||
var pct = 0.0
|
||||
var off = 0.0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# attr_place.ludic - position and its insets, z-index, overflow, white-space and text-overflow
|
||||
function el_place_prop(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool {
|
||||
let s = vl_text(v)
|
||||
let s = vl_text_u(ui_st, v)
|
||||
if key == "position" {
|
||||
n.pos = 0
|
||||
if s == "relative" { n.pos = 1 }
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ export state UiState {
|
|||
pl_cur: UiPool = null
|
||||
mm: UiMemo = null # memo.ludic: splits, gradients and keys by their text
|
||||
mm_ints: []string = null
|
||||
ly_depth: int = 0 # layout_run.ludic: the lists a line lays out with, one a depth
|
||||
ly_sizes: [][]float = new [][]float
|
||||
ly_frozen: [][]bool = new [][]bool
|
||||
cs_rules: []UiRule = new []UiRule # cascade_merge.ludic: a component root's rules, gathered
|
||||
cs_envs: []UiEnv = new []UiEnv
|
||||
cs_seen: []UiSheet = new []UiSheet
|
||||
an_frames: []UiKeyframes = new []UiKeyframes
|
||||
an_keys: []string = new []string
|
||||
an_start: []float = new []float
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
# specificity as one sheet's are. On a tie the user's rule wins (the outermost user's last), as a
|
||||
# later rule does. Then `order`, which rearranges a flex box's children without touching the tree.
|
||||
function cs_merged(ui_st: mut UiState, n: UiNode) -> void {
|
||||
let rules = new []UiRule
|
||||
let envs = new []UiEnv
|
||||
let seen = new []UiSheet
|
||||
let rules = ui_st.cs_rules # kept and emptied: a merge runs once an element, never inside another
|
||||
let envs = ui_st.cs_envs
|
||||
let seen = ui_st.cs_seen
|
||||
List.clear(rules)
|
||||
List.clear(envs)
|
||||
List.clear(seen)
|
||||
if n.sheet != null { cs_gather(ui_st, n, n.sheet, n.env, rules, envs, seen) }
|
||||
for i in 0 .. len(n.ups) {
|
||||
if n.ups[i].sheet != null { cs_gather(ui_st, n, n.ups[i].sheet, n.ups[i].env, rules, envs, seen) }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function bd_elem(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string) -> UiNode
|
|||
n.sheet = k.sheet
|
||||
n.style = k.style
|
||||
el_kind(ui_st, n, k.tag)
|
||||
for i in 0 .. len(k.keys) { el_keep(n, k.keys[i], ui_eval(ui_st, k.vals[i], e)) }
|
||||
for i in 0 .. len(k.keys) { el_keep(ui_st, n, k.keys[i], ui_eval(ui_st, k.vals[i], e)) }
|
||||
n.hovered = fr_was_hovered(ui_st, key)
|
||||
n.on_names = k.on_keys
|
||||
n.on_acts = k.on_acts
|
||||
|
|
@ -52,8 +52,8 @@ function el_text(ui_st: mut UiState, n: UiNode, s: string) -> void {
|
|||
push(n.children, t)
|
||||
}
|
||||
# id, class, hidden, disabled and src say what the element is; the rest are applied after the rules
|
||||
function el_keep(n: UiNode, key: string, v: Val) -> void {
|
||||
let s = vl_text(v)
|
||||
function el_keep(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void {
|
||||
let s = vl_text_u(ui_st, v)
|
||||
if key == "id" { n.id = s } else if key == "class" {
|
||||
let parts = lss_split(s, 32)
|
||||
for i in 0 .. len(parts) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> Ui
|
|||
root.sheet = t.sheet
|
||||
root.tag = "screen"
|
||||
for i in 0 .. len(t.keys) {
|
||||
if t.keys[i] != "name" { el_keep(root, t.keys[i], ui_eval(ui_st, t.vals[i], e)) }
|
||||
if t.keys[i] != "name" { el_keep(ui_st, root, t.keys[i], ui_eval(ui_st, t.vals[i], e)) }
|
||||
}
|
||||
bd_kids(ui_st, t.kids, e, screen, root.children)
|
||||
cs_tree(ui_st, root)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ function ly_place_line(ui_st: mut UiState, n: UiNode, from: int, to: int, top: f
|
|||
var used = 0.0
|
||||
var grows = 0.0
|
||||
var count = 0
|
||||
let sizes = new []float
|
||||
let depth = ui_st.ly_depth
|
||||
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))
|
||||
if n.children[i].pos < 2 {
|
||||
|
|
@ -52,7 +54,7 @@ function ly_place_line(ui_st: mut UiState, n: UiNode, from: int, to: int, top: f
|
|||
}
|
||||
used = used + n.gap * float(Math.max(count - 1, 0))
|
||||
let free = main_room - used
|
||||
if free < 0.0 { ly_shrink_line(n, from, sizes, 0.0 - free) }
|
||||
if free < 0.0 { ly_shrink_line(ui_st, depth, n, from, sizes, 0.0 - free) }
|
||||
ly_justify(ui_st, n, free, count, grows)
|
||||
let extra = ui_st.ly_extra
|
||||
var at = ui_st.ly_lead
|
||||
|
|
@ -65,6 +67,17 @@ function ly_place_line(ui_st: mut UiState, n: UiNode, from: int, to: int, top: f
|
|||
if c.pos == 1 { ly_nudge(n, c) }
|
||||
at = at + m + n.gap + extra
|
||||
}
|
||||
ui_st.ly_depth = depth
|
||||
}
|
||||
# a line's sizes, and its shrink's frozen flags, in lists kept one a depth (a line's children lay out
|
||||
# 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 {
|
||||
push(ui_st.ly_sizes, new []float)
|
||||
push(ui_st.ly_frozen, new []bool)
|
||||
}
|
||||
List.clear(ui_st.ly_sizes[depth])
|
||||
return ui_st.ly_sizes[depth]
|
||||
}
|
||||
# one child: `m` along the run and its own size (or the whole room, stretched) across, less margins
|
||||
function ly_place_child(ui_st: mut UiState, n: UiNode, c: UiNode, at: float, top: float, m: float, cross_room: float) -> void {
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ function ly_min_h(c: UiNode) -> float {
|
|||
return c.mh
|
||||
}
|
||||
# `sizes` (the children from..to) shrunk by `over` in all
|
||||
function ly_shrink_line(n: UiNode, from: int, sizes: []float, over0: float) -> void {
|
||||
function ly_shrink_line(ui_st: mut UiState, depth: int, n: UiNode, from: int, sizes: []float, over0: float) -> void {
|
||||
var over = over0
|
||||
let frozen = new []bool
|
||||
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 pass in 0 .. 8 {
|
||||
var wsum = 0.0
|
||||
|
|
|
|||
|
|
@ -71,3 +71,14 @@ function vl_binary(op: string, a: Val, b: Val) -> Val {
|
|||
# a value as text, whatever kind it is: a component's string prop given a number (label="{3}") reads
|
||||
# "3", not nothing
|
||||
export function ui_val_text(v: Val) -> string { return vl_text(v) }
|
||||
# vl_text for what is asked per element per frame: a small whole number's text comes from the table
|
||||
# made once (mm_int) rather than being written out again
|
||||
function vl_text_u(ui_st: mut UiState, v: Val) -> string {
|
||||
let k = Value.kind(v)
|
||||
if k == 1 { return mm_int(ui_st, Value.as_int(v)) }
|
||||
if k == 2 or k == 7 {
|
||||
let f = Value.as_float(v)
|
||||
if f == float(int(f)) { return mm_int(ui_st, int(f)) }
|
||||
}
|
||||
return vl_text(v)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,12 +24,22 @@ property Val {
|
|||
kids: []Val
|
||||
}
|
||||
|
||||
# a number, a string or a null is one record; only a list or an object carries lists (made when first
|
||||
# filled) - three allocations for every scalar was most of what a UI frame made and never gave back
|
||||
function value_new(tag: int) -> Val {
|
||||
let v = new Val
|
||||
v.tag = tag
|
||||
if tag == 5 or tag == 6 { value_lists(v) }
|
||||
return v
|
||||
}
|
||||
function value_lists(v: Val) -> void {
|
||||
if v.kids != null { return }
|
||||
v.keys = new []pointer
|
||||
v.kids = new []Val
|
||||
return v
|
||||
}
|
||||
function value_n(v: Val) -> int {
|
||||
if v.kids == null { return 0 }
|
||||
return len(v.kids)
|
||||
}
|
||||
|
||||
# --- constructors -----------------------------------------------------------
|
||||
|
|
@ -43,8 +53,13 @@ function value_float(x: float) -> Val { let v = value_new(7); v.num = float_bits
|
|||
function value_object() -> Val { return value_new(6) }
|
||||
|
||||
# --- builders (return the container, so calls chain) ------------------------
|
||||
function value_add(list: Val, item: Val) -> Val { push(list.kids, item); return list }
|
||||
function value_add(list: Val, item: Val) -> Val {
|
||||
value_lists(list)
|
||||
push(list.kids, item)
|
||||
return list
|
||||
}
|
||||
function value_put(obj: Val, key: pointer, item: Val) -> Val {
|
||||
value_lists(obj)
|
||||
var i = 0
|
||||
while i < len(obj.keys) { if obj.keys[i] == key { obj.kids[i] = item; return obj }; i += 1 }
|
||||
push(obj.keys, key); push(obj.kids, item)
|
||||
|
|
@ -53,7 +68,7 @@ function value_put(obj: Val, key: pointer, item: Val) -> Val {
|
|||
|
||||
# --- accessors --------------------------------------------------------------
|
||||
function value_kind(v: Val) -> int { return v.tag }
|
||||
function value_count(v: Val) -> int { return len(v.kids) }
|
||||
function value_count(v: Val) -> int { return value_n(v) }
|
||||
function value_as_int(v: Val) -> int {
|
||||
if v.tag == 7 { return int(float_from_bits(v.num)) }
|
||||
return v.num
|
||||
|
|
@ -66,21 +81,21 @@ function value_as_float(v: Val) -> float {
|
|||
}
|
||||
function value_as_str(v: Val) -> string { if v.txt == null { return "" }; return v.txt }
|
||||
function value_at(list: Val, i: int) -> Val {
|
||||
if i < 0 or i >= len(list.kids) { return value_null() }
|
||||
if i < 0 or i >= value_n(list) { return value_null() }
|
||||
return list.kids[i]
|
||||
}
|
||||
function value_key_at(obj: Val, i: int) -> string {
|
||||
if i < 0 or i >= len(obj.keys) { return "" }
|
||||
if i < 0 or i >= value_n(obj) { return "" }
|
||||
return obj.keys[i]
|
||||
}
|
||||
function value_get(obj: Val, key: pointer) -> Val {
|
||||
var i = 0
|
||||
while i < len(obj.keys) { if obj.keys[i] == key { return obj.kids[i] }; i += 1 }
|
||||
while i < value_n(obj) { if obj.keys[i] == key { return obj.kids[i] }; i += 1 }
|
||||
return value_null()
|
||||
}
|
||||
function value_has(obj: Val, key: pointer) -> int {
|
||||
var i = 0
|
||||
while i < len(obj.keys) { if obj.keys[i] == key { return 1 }; i += 1 }
|
||||
while i < value_n(obj) { if obj.keys[i] == key { return 1 }; i += 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +147,7 @@ function json_encode(v: Val) -> string {
|
|||
if v.tag == 5 {
|
||||
var out = "["
|
||||
var i = 0
|
||||
while i < len(v.kids) {
|
||||
while i < value_n(v) {
|
||||
if i > 0 { out += "," }
|
||||
out += json_encode(v.kids[i])
|
||||
i += 1
|
||||
|
|
@ -141,7 +156,7 @@ function json_encode(v: Val) -> string {
|
|||
}
|
||||
var out = "{"
|
||||
var i = 0
|
||||
while i < len(v.keys) {
|
||||
while i < value_n(v) {
|
||||
if i > 0 { out += "," }
|
||||
out = out + json_quote(v.keys[i]) + ":" + json_encode(v.kids[i])
|
||||
i += 1
|
||||
|
|
@ -176,9 +191,11 @@ function json_parse(s: pointer) -> Val {
|
|||
# a node or hold a literal, is not for this (Ludic has no collector; plan 23 of maroon-lake).
|
||||
function value_free(v: Val) -> void {
|
||||
if v == null { return }
|
||||
for i in 0 .. len(v.kids) { value_free(v.kids[i]) }
|
||||
free(v.kids)
|
||||
free(v.keys)
|
||||
if v.kids != null {
|
||||
for i in 0 .. len(v.kids) { value_free(v.kids[i]) }
|
||||
free(v.kids)
|
||||
free(v.keys)
|
||||
}
|
||||
free(v)
|
||||
}
|
||||
function json_free(v: Val) -> void { value_free(v) }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue