ludic/packages/ludic.ui/memo.ludic

131 lines
5.2 KiB
Text

# memo.ludic - what a frame would work out again from the same text is kept by that text: a value
# 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 = 32768 # slots; at most three quarters are used
export property UiMemo {
slots: words = null # index + 1 into keys, 0 empty
@max(24576) keys: []string = null # the text a result was made from (or the joined text itself)
@max(24576) tags: []int = null # what kind of result: a separator, or a kind below
@max(24576) lists: [][]string = null
@max(24576) grads: []UiGradient = null
@max(24576) texts: []string = null # a text worked out from the key: tw, el_url
@max(24576) vals: []Val = null # a Value made from it: cs_vars_in's
@max(24576) vtpls: []UiVarTpl = null
@max(24576) n1: []float = null # numbers worked out from the key: a colour and its alpha
@max(24576) n2: []float = null
}
const MM_WORDS: int = -1 # el_words: spaces, a bracket's own kept
const MM_GRAD: int = -2
const MM_JOIN: int = -3 # keys[i] is the joined text itself
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()
const MM_TEXT: int = -10 # a text with holes, keys[i] the text itself (eval_text.ludic)
@alloc_ok("once: the memo is made the first time it is asked")
function mm_new() -> UiMemo {
let m = new UiMemo
m.slots = words(MM_CAP)
m.keys = new []string
m.tags = new []int
m.lists = new [][]string
m.grads = new []UiGradient
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 {
if ui_st.mm == null {
ui_st.mm = mm_new()
mm_warm_pct(ui_st, ui_st.mm)
}
return ui_st.mm
}
# every percentage a bar can be written as (0% .. 100%, to a tenth) made once at the start, so a bar
# moving through them never makes its text in play - a text seen for the first time was still one string
@alloc_ok("once: every percentage a bar can show, made with the memo")
function mm_warm_pct(ui_st: mut UiState, m: UiMemo) -> void {
for t in 0 .. 1001 {
var s = mm_int(ui_st, t / 10)
if t % 10 != 0 { s = s + tx_tenth(t % 10) }
let text = s + "%"
let j = mm_add(m, MM_TEXT, text)
if j >= 0 { m.vals[j] = Value.str(text) }
}
}
# FNV-1a over the text, the tag mixed in first
function mm_hash(tag: int, s: string) -> int {
var h = 216613626 ^ (tag & 255)
for i in 0 .. len(s) { h = ((h ^ s[i]) * 16777619) & 2147483647 }
return h
}
# the entry for (tag, s), or -1
function mm_find(m: UiMemo, tag: int, s: string) -> int {
var at = mm_hash(tag, s) & (MM_CAP - 1)
while m.slots[at] != 0 {
let i = m.slots[at] - 1
if m.tags[i] == tag and m.keys[i] == s { return i }
at = (at + 1) & (MM_CAP - 1)
}
return -1
}
# a new entry for (tag, s) with its results empty, or -1 when the table is full
@alloc_ok("a memo entry: one per distinct text, bounded by MM_CAP")
function mm_add(m: UiMemo, tag: int, s: string) -> int {
if len(m.keys) * 4 >= MM_CAP * 3 {
Mem.over("ludic.ui's memo, three quarters of MM_CAP") # 25.5a: past it every miss was made and lost
return -1
}
var at = mm_hash(tag, s) & (MM_CAP - 1)
while m.slots[at] != 0 { at = (at + 1) & (MM_CAP - 1) }
push(m.keys, s)
push(m.tags, tag)
push(m.lists, null)
push(m.grads, null)
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
}
# lss_split's parts of `s` at `c`, made once per text; the list is shared - read it, never change it
function mm_split(ui_st: mut UiState, s: string, c: int) -> []string {
let m = mm_of(ui_st)
let i = mm_find(m, c, s)
if i >= 0 { return m.lists[i] }
var parts: []string = null
if c == MM_WORDS { parts = el_words(s) } else { parts = lss_split(s, c) }
let j = mm_add(m, c, s)
if j >= 0 { m.lists[j] = parts }
return parts
}
function mm_words(ui_st: mut UiState, s: string) -> []string { return mm_split(ui_st, s, MM_WORDS) }
# tpl_words, made once per text (a clean one is its own answer, and nothing is kept for it)
function tw(ui_st: mut UiState, s: string) -> string {
if tpl_clean(s) { return s }
let m = mm_of(ui_st)
let i = mm_find(m, MM_TW, s)
if i >= 0 { return m.texts[i] }
let t = tpl_words(s)
let j = mm_add(m, MM_TW, s)
if j >= 0 { m.texts[j] = t }
return t
}
# el_url, made once per text
function el_url_u(ui_st: mut UiState, s: string) -> string {
let m = mm_of(ui_st)
let i = mm_find(m, MM_URL, s)
if i >= 0 { return m.texts[i] }
let t = el_url(s)
let j = mm_add(m, MM_URL, s)
if j >= 0 { m.texts[j] = t }
return t
}