ludic.ui: a hole's text found by its pieces in the memo (built only when it first reads that way), avals left for the attributes read as text, a class's [root] made once, and a dev build's reload lets go of every file it read only to compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6c663cc87b
commit
ea5eedb5a5
10 changed files with 76 additions and 18 deletions
|
|
@ -43,6 +43,8 @@ export state UiState {
|
|||
ev_depth: int = 0 # eval.ludic: a call's arguments, in lists kept one a depth
|
||||
ev_args: []Val = new []Val
|
||||
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
|
||||
tx_depth: int = 0
|
||||
ca_pool: []CaVal = new []CaVal # calc.ludic: an evaluation's terms, used again by the next
|
||||
ca_used: int = 0
|
||||
ca_args: [][]CaVal = new [][]CaVal
|
||||
|
|
|
|||
|
|
@ -25,8 +25,11 @@ function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui
|
|||
for i in 0 .. len(k.on_keys) { push(ce.on_envs, e) }
|
||||
lc_note(ui_st, key, k.on_keys, k.on_acts, e)
|
||||
let at = len(out)
|
||||
let root = new []UiTpl
|
||||
push(root, cls.root)
|
||||
if cls.roots == null or len(cls.roots) != 1 or cls.roots[0] != cls.root {
|
||||
cls.roots = new []UiTpl # its template as a list of one, made once (again after a reload)
|
||||
push(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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export property UiClass {
|
|||
seen_xml: string = "" # the files as a developer's build last found them on disk
|
||||
seen_lss: string = ""
|
||||
use: UiTpl = null # screen_class.ludic: the element that shows it as a screen, made once
|
||||
roots: []UiTpl = null # build_class.ludic: [root], for bd_kids
|
||||
none: Val = null # and the empty model it is shown with
|
||||
}
|
||||
export function ui_register(ui_st: mut UiState, k: UiClass) -> int {
|
||||
|
|
@ -52,8 +53,14 @@ function uc_reload(ui_st: mut UiState) -> bool {
|
|||
if k.xml_path != "" and Fs.exists(k.xml_path) {
|
||||
let x = lib_text(ui_st, k.xml_path)
|
||||
var l = ""
|
||||
if k.lss_path != "" and Fs.exists(k.lss_path) { l = lib_text(ui_st, k.lss_path) }
|
||||
if k.seen_xml != "" and (x != k.seen_xml or l != k.seen_lss) {
|
||||
let has_l = k.lss_path != "" and Fs.exists(k.lss_path)
|
||||
if has_l { l = lib_text(ui_st, k.lss_path) }
|
||||
if k.seen_xml != "" and x == k.seen_xml and l == k.seen_lss {
|
||||
rl_let_go(x) # read only to compare, and the same: nothing keeps it (a dev build
|
||||
if has_l { rl_let_go(l) } # asks every second, and each read was kept for good)
|
||||
continue
|
||||
}
|
||||
if k.seen_xml != "" {
|
||||
k.xml = x
|
||||
k.lss = l
|
||||
k.root = null
|
||||
|
|
|
|||
|
|
@ -52,10 +52,11 @@ 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
|
||||
# an attribute's text is worked out only where it is read as text (the rest keep their Value; a selector
|
||||
# that asks for one reads it then) - a float's text written per element per frame was a leak
|
||||
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 = mm_split(ui_st, s, 32)
|
||||
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]) }
|
||||
}
|
||||
|
|
@ -63,12 +64,12 @@ function el_keep(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void {
|
|||
n.popover = true
|
||||
n.pos = 2
|
||||
push(n.akeys, key)
|
||||
push(n.avals, s)
|
||||
push(n.avals, "")
|
||||
push(n.vals, v)
|
||||
} else if key == "tabindex" { n.focusable = s != "-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 = s } else {
|
||||
} 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, s)
|
||||
push(n.avals, "")
|
||||
push(n.vals, v)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,7 @@ function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
|
|||
if vl_truthy(ui_eval(ui_st, x.a, e)) { return ui_eval(ui_st, x.b, e) }
|
||||
return ui_eval(ui_st, x.c, e)
|
||||
}
|
||||
if x.op == X_TEXT {
|
||||
var out = ""
|
||||
for i in 0 .. len(x.args) { out = out + vl_text_u(ui_st, ui_eval(ui_st, x.args[i], e)) }
|
||||
return uv_str(ui_st, out)
|
||||
}
|
||||
if x.op == X_TEXT { return ev_text(ui_st, x, e) }
|
||||
return vl_binary(ui_st, x.s, ui_eval(ui_st, x.a, e), ui_eval(ui_st, x.b, e))
|
||||
}
|
||||
# a call: len() and range() are the template's own, anything else is the view's
|
||||
|
|
|
|||
38
packages/ludic.ui/eval_text.ludic
Normal file
38
packages/ludic.ui/eval_text.ludic
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# eval_text.ludic - a text with holes ("{n} left") worked out: its pieces are found in the memo by
|
||||
# comparing in place, so the text it was last frame (and the frame before) is its kept Value, and a
|
||||
# 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
|
||||
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
|
||||
List.clear(ps)
|
||||
for i in 0 .. len(x.args) { push(ps, vl_text_u(ui_st, ui_eval(ui_st, x.args[i], e))) }
|
||||
ui_st.tx_depth = depth
|
||||
let m = mm_of(ui_st)
|
||||
var h = 216613626 ^ (MM_TEXT & 255)
|
||||
for p in 0 .. len(ps) {
|
||||
let t = ps[p]
|
||||
for i in 0 .. len(t) { h = ((h ^ t[i]) * 16777619) & 2147483647 }
|
||||
}
|
||||
var at = h & (MM_CAP - 1)
|
||||
while m.slots[at] != 0 {
|
||||
let i = m.slots[at] - 1
|
||||
if m.tags[i] == MM_TEXT and tx_same(ps, m.keys[i]) { return m.vals[i] }
|
||||
at = (at + 1) & (MM_CAP - 1)
|
||||
}
|
||||
var out = ""
|
||||
for p in 0 .. len(ps) { out = out + ps[p] }
|
||||
let j = mm_add(m, MM_TEXT, out)
|
||||
if j < 0 { return uv_str(ui_st, out) }
|
||||
m.vals[j] = Value.str(out)
|
||||
return m.vals[j]
|
||||
}
|
||||
function tx_same(ps: []string, k: string) -> bool {
|
||||
var at = 0
|
||||
for p in 0 .. len(ps) {
|
||||
if not sat(k, at, ps[p]) { return false }
|
||||
at += len(ps[p])
|
||||
}
|
||||
return at == len(k)
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import "pool_reset.ludic"
|
|||
import "pool_reset_more.ludic"
|
||||
import "memo.ludic"
|
||||
import "memo_join.ludic"
|
||||
import "eval_text.ludic"
|
||||
import "units_pieces.ludic"
|
||||
import "expr.ludic"
|
||||
import "expr_parse.ludic"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# split into its words, a gradient, a key joined from its parent's. Ludic frees nothing, so parsing
|
||||
# "2px 6px" per element per frame was a leak; the table is bounded, and past it a result is made
|
||||
# (and lost) as before rather than the table growing without end.
|
||||
const MM_CAP: int = 8192 # slots; at most three quarters are used
|
||||
const MM_CAP: int = 32768 # slots; at most three quarters are used
|
||||
export property UiMemo {
|
||||
slots: words = null # index + 1 into keys, 0 empty
|
||||
keys: []string = null # the text a result was made from (or the joined text itself)
|
||||
|
|
@ -24,6 +24,7 @@ const MM_VARS: int = -6 # cs_vars_in: keys[i] is the text with every
|
|||
const MM_VTPL: int = -7 # a value's var()s parsed (units_pieces.ludic)
|
||||
const MM_COLOR: int = -8 # ui_color: n1 the colour, n2 its alpha
|
||||
const MM_BIMG: int = -9 # border-image: texts the picture, lists what follows its url()
|
||||
const MM_TEXT: int = -10 # a text with holes, keys[i] the text itself (eval_text.ludic)
|
||||
function mm_new() -> UiMemo {
|
||||
let m = new UiMemo
|
||||
m.slots = words(MM_CAP)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ export function ui_reload(ui_st: mut UiState) -> bool {
|
|||
var changed = uc_reload(ui_st)
|
||||
for i in 0 .. len(ui_st.lib_files) {
|
||||
let f = ui_st.lib_files[i]
|
||||
if f.disk and lib_text(ui_st, f.path) != f.src { changed = true }
|
||||
if f.disk {
|
||||
let now = lib_text(ui_st, f.path)
|
||||
if now != f.src { changed = true }
|
||||
rl_let_go(now) # read only to compare; a change is read again below
|
||||
}
|
||||
}
|
||||
if not changed { return false }
|
||||
ui_st.ui_errs = new []string
|
||||
|
|
@ -31,3 +35,8 @@ export function ui_reload(ui_st: mut UiState) -> bool {
|
|||
print(`ui: templates reloaded`)
|
||||
return true
|
||||
}
|
||||
# a text read from disk only to be compared, given back (never a literal: lib_text's "" is not freed)
|
||||
function rl_let_go(s: string) -> void {
|
||||
if s == null or len(s) == 0 { return }
|
||||
unsafe { free(s) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function sel_has(xs: []string, x: string) -> bool {
|
|||
function sel_attr_of(n: UiNode, key: string) -> string {
|
||||
if n.akeys == null { return null }
|
||||
for i in 0 .. len(n.akeys) {
|
||||
if n.akeys[i] == key { return n.avals[i] }
|
||||
if n.akeys[i] == key { return vl_text(n.vals[i]) }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue