ludic.ui, compiler: a component's model is filled in place into an object its instance keeps (value_set_*); colours, border-images and a class screen's stub made once; object-fit into a kept list - benchmark 20 -> 10 KB a frame

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 00:17:47 +03:00
parent 08ac2f1125
commit 429eeb7754
15 changed files with 44939 additions and 44681 deletions

View file

@ -21,10 +21,18 @@ function el_url(s: string) -> string {
}
# border-image: url(chip.png) 14 / 12px - the source's corner size, then how wide they are drawn
function el_border_image(ui_st: mut UiState, n: UiNode, s: string) -> void {
var close = 0
while close < len(s) and s[close] != 41 { close += 1 }
n.bi_src = el_url_u(ui_st, s[0..Math.min(close + 1, len(s))])
let rest = mm_split(ui_st, s[Math.min(close + 1, len(s))..len(s)], 47)
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)
}
n.bi_src = m.texts[at]
let rest = m.lists[at]
n.bi_slice = el_num(tw(ui_st, rest[0]))
n.bi_width = el_px(ui_st, n.bi_slice)
if len(rest) > 1 { n.bi_width = el_unit(ui_st, tw(ui_st, rest[1])) }

View file

@ -40,6 +40,7 @@ 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
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
r3n_xs: []float = new []float
r3n_ys: []float = new []float

View file

@ -14,7 +14,8 @@ function bd_class(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []Ui
}
cls.props(it.ptr, props)
let ce = env_new(ui_st, null)
ce.model = cls.model(it.ptr)
if it.model == null { it.model = Value.object() }
ce.model = cls.model(it.ptr, it.model)
ce.inst = it
ce.ctx = e.ctx
ce.slot = k

View file

@ -9,12 +9,14 @@ export property UiClass {
lss: string = ""
make: fn() -> pointer = null
props: fn(pointer, Val) -> void = null
model: fn(pointer) -> Val = null
model: fn(pointer, Val) -> Val = null # fills the object it is given (the instance keeps one)
call: fn(pointer, string, Val) -> Val = null
root: UiTpl = null # the template, once read
sheet: UiSheet = null
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
none: Val = null # and the empty model it is shown with
}
export function ui_register(ui_st: mut UiState, k: UiClass) -> int {
push(ui_st.uc_list, k)

View file

@ -1,7 +1,23 @@
# color.ludic - CSS colours: #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(r, g, b), rgba(r, g, b, a), the
# basic names, transparent and none. A colour is 0xRRGGBB; its alpha is left in cl_alpha.
const CL_NAMES: string = " black=000000 white=ffffff red=ff0000 green=008000 lime=00ff00 blue=0000ff yellow=ffff00 cyan=00ffff aqua=00ffff magenta=ff00ff fuchsia=ff00ff gray=808080 grey=808080 silver=c0c0c0 maroon=800000 olive=808000 navy=000080 purple=800080 teal=008080 orange=ffa500 "
# a colour is worked out once per text and kept with its alpha
function ui_color(ui_st: mut UiState, name0: string) -> int {
let m = mm_of(ui_st)
let i = mm_find(m, MM_COLOR, name0)
if i >= 0 {
ui_st.cl_alpha = m.n2[i]
return int(m.n1[i])
}
let c = ui_color_new(ui_st, name0)
let j = mm_add(m, MM_COLOR, name0)
if j >= 0 {
m.n1[j] = float(c)
m.n2[j] = ui_st.cl_alpha
}
return c
}
function ui_color_new(ui_st: mut UiState, name0: string) -> int {
let name = tw(ui_st, name0)
ui_st.cl_alpha = 1.0
if name == "none" or name == "" or name == "transparent" { return UI_NONE }

View file

@ -56,7 +56,7 @@ function fr_image(ui_st: mut UiState, n: UiNode) -> void {
nat_w = ui_st.ui_be.image_w(n.src)
nat_h = ui_st.ui_be.image_h(n.src)
}
let r = ui_object_fit(n, nat_w, nat_h)
let r = ui_object_fit_into(ui_st.fit_out, n, nat_w, nat_h)
let clipped = ui_st.ui_be.clip != null and (r[2] > n.cw - ly_hin(n) or r[3] > n.ch - ly_vin(n))
if clipped { fr_clip_push(ui_st, n.x + n.pl + n.bl, n.y + n.pt + n.bt, n.cw - ly_hin(n), n.ch - ly_vin(n)) }
ui_st.ui_be.image(n.src, r[0], r[1], r[2], r[3], c, a)

View file

@ -6,6 +6,7 @@ property UiInst {
ptr: pointer = null
seen: int = 0
props: Val = null # its props, filled again each frame into the same object
model: Val = null # and its model, filled in place by the class's model()
}
function in_get(ui_st: mut UiState, key: string, k: UiClass) -> UiInst {
for i in 0 .. len(ui_st.in_list) {

View file

@ -12,7 +12,8 @@ function ly_ratio(n: UiNode, fixed_w: float, avail: float) -> void {
} else if fixed_w < 0.0 and n.w != UI_FILL and n.h >= 0.0 { n.mw = n.h * r }
}
# x, y, w, h: a `nat_w` x `nat_h` picture in the node's content box by its object-fit
export function ui_object_fit(n: UiNode, nat_w: float, nat_h: float) -> []float {
export function ui_object_fit(n: UiNode, nat_w: float, nat_h: float) -> []float { return ui_object_fit_into(new []float, n, nat_w, nat_h) }
function ui_object_fit_into(out: []float, n: UiNode, nat_w: float, nat_h: float) -> []float {
let bx = n.x + n.pl + n.bl
let by = n.y + n.pt + n.bt
let bw = n.cw - ly_hin(n)
@ -29,7 +30,7 @@ export function ui_object_fit(n: UiNode, nat_w: float, nat_h: float) -> []float
w = nat_w * s
h = nat_h * s
}
let out = new []float
List.clear(out)
push(out, bx + (bw - w) / 2.0)
push(out, by + (bh - h) / 2.0)
push(out, w)

View file

@ -12,6 +12,8 @@ export property UiMemo {
texts: []string = null # a text worked out from the key: tw, el_url
vals: []Val = null # a Value made from it: cs_vars_in's
vtpls: []UiVarTpl = null
n1: []float = null # numbers worked out from the key: a colour and its alpha
n2: []float = null
}
const MM_WORDS: int = -1 # el_words: spaces, a bracket's own kept
const MM_GRAD: int = -2
@ -20,6 +22,8 @@ const MM_TW: int = -4 # tpl_words
const MM_URL: int = -5 # el_url
const MM_VARS: int = -6 # cs_vars_in: keys[i] is the text with every var() put in
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()
function mm_new() -> UiMemo {
let m = new UiMemo
m.slots = words(MM_CAP)
@ -30,6 +34,8 @@ function mm_new() -> UiMemo {
m.texts = new []string
m.vals = new []Val
m.vtpls = new []UiVarTpl
m.n1 = new []float
m.n2 = new []float
return m
}
function mm_of(ui_st: mut UiState) -> UiMemo {
@ -64,6 +70,8 @@ function mm_add(m: UiMemo, tag: int, s: string) -> int {
push(m.texts, null)
push(m.vals, null)
push(m.vtpls, null)
push(m.n1, 0.0)
push(m.n2, 0.0)
m.slots[at] = len(m.keys)
return len(m.keys) - 1
}

View file

@ -31,6 +31,8 @@ function mm_join(ui_st: mut UiState, a: string, sep: string, b: string) -> strin
push(m.texts, null)
push(m.vals, null)
push(m.vtpls, null)
push(m.n1, 0.0)
push(m.n2, 0.0)
m.slots[at] = len(m.keys)
return k
}

View file

@ -7,16 +7,21 @@ function fr_class_nodes(ui_st: mut UiState, cls: UiClass) -> UiNode {
ui_st.ui_view = null
uc_load(ui_st, cls)
let root = fr_root(ui_st, cls.name, cls.sheet)
let use = new UiTpl
use.tag = cls.name
use.cls = cls
use.keys = new []string
use.vals = new []UiExpr
use.on_keys = new []string
use.on_acts = new []UiAct
use.kids = new []UiTpl
if cls.use == null {
let use = new UiTpl
use.tag = cls.name
use.cls = cls
use.keys = new []string
use.vals = new []UiExpr
use.on_keys = new []string
use.on_acts = new []UiAct
use.kids = new []UiTpl
cls.use = use
cls.none = Value.object()
}
let use = cls.use
let e = env_new(ui_st, null)
e.model = Value.object()
e.model = cls.none
root.env = e
bd_class(ui_st, use, e, cls.name, root.children)
in_sweep(ui_st, cls.name)

View file

@ -69,6 +69,42 @@ 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 value_n(v) }
# a scalar field of an object set in place: the Value under `key` is changed when it is already that
# kind, and made only the first time (a component's model, filled into the same object every frame)
function value_slot(o: Val, key: pointer, tag: int) -> Val {
value_lists(o)
var i = 0
while i < len(o.keys) {
if o.keys[i] == key {
if o.kids[i].tag == tag { return o.kids[i] }
let v = value_new(tag)
o.kids[i] = v
return v
}
i += 1
}
let v = value_new(tag)
push(o.keys, key)
push(o.kids, v)
return v
}
function value_set_int(o: Val, key: pointer, n: int) -> void {
let v = value_slot(o, key, 1)
v.num = n
}
function value_set_float(o: Val, key: pointer, x: float) -> void {
let v = value_slot(o, key, 7)
v.num = float_bits(x)
}
function value_set_str(o: Val, key: pointer, s: pointer) -> void {
let v = value_slot(o, key, 4)
v.txt = s
}
function value_set_bool(o: Val, key: pointer, b: bool) -> void {
let v = value_slot(o, key, 3)
v.num = 0
if b { v.num = 1 }
}
# a list or an object emptied in place, its room kept - for one filled again every frame
function value_clear(v: Val) -> void {
if v.kids == null { return }

View file

@ -23,24 +23,31 @@ function cm_from(c: int, ty: pointer, x: pointer) -> pointer {
cm_err(c, `a prop or state is an int, a float, a bool, a string or a Val, not a {ty}`)
return x
}
# the model is filled into the object the instance keeps (ludic.ui's): a scalar field updates its own
# Value in place, so showing a component allocates nothing for it frame after frame
function cm_model_src(c: int, px: pointer, rec: pointer, head: Node) -> pointer {
var src = `function {px}model(p: pointer) -> Val {{\n let s: {rec} = p\n let o = value_object()\n`
var src = `function {px}model(p: pointer, o: Val) -> Val {{\n let s: {rec} = p\n`
var i = 0
while i < len(g_cm_keep[c]) {
let k = g_cm_keep[c][i]
let put = cm_to(c, g_cm_keep_ty[c][i], "s." + k, head)
src = src + ` value_put(o, "{k}", {put})\n`
src = src + cm_set(c, g_cm_keep_ty[c][i], k, "s." + k, head)
i += 1
}
i = 0
while i < len(g_cm_fields[c]) {
let g = g_cm_fields[c][i]
let put = cm_to(c, g.ty, g.s + "(s)", head)
src = src + ` value_put(o, "{g_cm_fnames[c][i]}", {put})\n`
src = src + cm_set(c, g.ty, g_cm_fnames[c][i], g.s + "(s)", head)
i += 1
}
return src + " return o\n}\n"
}
function cm_set(c: int, ty: pointer, k: pointer, x: pointer, head: Node) -> pointer {
if (ty == "int") { return ` value_set_int(o, "{k}", {x})\n` }
if (ty == "float") { return ` value_set_float(o, "{k}", {x})\n` }
if (ty == "string") { return ` value_set_str(o, "{k}", {x})\n` }
if (ty == "bool") { return ` value_set_bool(o, "{k}", {x})\n` }
return ` value_put(o, "{k}", {cm_to(c, ty, x, head)})\n`
}
function cm_to(c: int, ty: pointer, x: pointer, head: Node) -> pointer {
if (ty == "Val") { return x }
let conv = vw_conv(0, ty, x, head)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff