A load freed only the parsed trip's nodes (Json.free) and kept every string the parser made, because
a loader might keep one; free_all freed every string and every key, so on a tree a migration had
added a literal to (`Value.put(v, "sver", Value.str("2"))`) it freed the literal and aborted.
- The parser marks the string values it makes (JP_OWNED, in the node's otherwise unused num) and
interns object keys (a few names, never freed); free_all frees only marked strings, never keys,
and a list's spares too. A setter that gives a marked node other text (value_set_str/_strs,
value_into_str/_strs, value_become) frees the parser's text first. value_as_int / _as_float read a
string as 0 as before.
- ludic.base sv_str returns intern(...): every package load that keeps a text read from a section
(minimap labels, a Thing's look, photo tags and files, an effect's label, ...) holds its own copy.
Golden json_free_edited: parse, put a literal key and string in, set a string, keep an interned copy,
free_all - 1100 loads: the copy reads on and nothing grows (the toolchain before this aborts, 134).
Tests: ludic.save, base, settings, minimap, things, photo, effects; json_saves, value_list_regrow; the
36 ui examples.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.2 KiB
Text
61 lines
2.2 KiB
Text
# ludic.base/save_fields.ludic - reading a field with a fallback, and writing one. A float is kept
|
|
# as thousandths in an int (rounded), so a value read back and written again is the same value.
|
|
function sv_has(v: Val, key: string) -> bool {
|
|
if v == null { return false }
|
|
if Value.kind(v) != 6 { return false }
|
|
return Value.has(v, key) != 0
|
|
}
|
|
|
|
export function sv_int(v: Val, key: string, fallback: int) -> int {
|
|
if not sv_has(v, key) { return fallback }
|
|
return Value.as_int(Value.get(v, key))
|
|
}
|
|
|
|
export function sv_float(v: Val, key: string, fallback: float) -> float {
|
|
if not sv_has(v, key) { return fallback }
|
|
let f = Value.get(v, key)
|
|
if Value.kind(f) == 7 { return Value.as_float(f) }
|
|
return float(Value.as_int(f)) / 1000.0
|
|
}
|
|
|
|
export function sv_bool(v: Val, key: string, fallback: bool) -> bool {
|
|
if not sv_has(v, key) { return fallback }
|
|
return Value.as_int(Value.get(v, key)) != 0
|
|
}
|
|
|
|
export function sv_str(v: Val, key: string, fallback: string) -> string {
|
|
if not sv_has(v, key) { return fallback }
|
|
let s = Value.get(v, key)
|
|
if Value.kind(s) != 4 { return fallback }
|
|
return intern(Value.as_str(s)) # a copy of its own: the tree it came from is let go whole once read
|
|
}
|
|
|
|
# a list of ints; a missing key or a non-list is the empty list
|
|
@alloc_ok("on load: a saved list read back")
|
|
export function sv_ints(v: Val, key: string) -> []int {
|
|
let out = new []int
|
|
if not sv_has(v, key) { return out }
|
|
let l = Value.get(v, key)
|
|
for i in 0 .. Value.count(l) { push(out, Value.as_int(Value.at(l, i))) }
|
|
return out
|
|
}
|
|
|
|
export function sv_put_int(v: Val, key: string, n: int) -> void { Value.put(v, key, Value.int(n)) }
|
|
|
|
export function sv_put_float(v: Val, key: string, f: float) -> void {
|
|
Value.put(v, key, Value.int(int(Math.floor(f * 1000.0 + 0.5))))
|
|
}
|
|
|
|
export function sv_put_bool(v: Val, key: string, b: bool) -> void {
|
|
var n = 0
|
|
if b { n = 1 }
|
|
Value.put(v, key, Value.bool(n))
|
|
}
|
|
|
|
export function sv_put_str(v: Val, key: string, s: string) -> void { Value.put(v, key, Value.str(s)) }
|
|
|
|
export function sv_put_ints(v: Val, key: string, xs: []int) -> void {
|
|
let l = Value.list()
|
|
for i in 0 .. len(xs) { Value.add(l, Value.int(xs[i])) }
|
|
Value.put(v, key, l)
|
|
}
|