fix(render3d, runtime): a glTF document is freed whole, layouts found by number, block records reused

- Json.free_all (value_free_all): a parsed tree's nodes, lists and strings. render3d frees each
  glTF document that way at the next load; the names kept out of it are copies (a primitive's
  material, a skin's joints, an animation clip's name in ludic.anim) - a model's strings were
  ~640 KB left behind per load
- jp_number made a digits list per decimal in a document and never freed it
- gvk_layout_id matches a mesh's layout as numbers in a scratch made once, against the layouts
  known end to end; a new mesh no longer builds a key string
- gvk_mem_new puts a new block into the record of one given back rather than appending, so a
  buffer made again every few frames no longer grows the block lists

steady.ludic adds a glTF parsed and freed whole 200 times: 0 bytes (38,400 before the digits fix),
beside the buffer path and the frame, still 0. A model loaded and let go still keeps ~2 KB a round
(texture and buffer handles are not reused yet); it is bounded at 8 KB a round.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 11:36:42 +03:00
parent 866037a0d7
commit a465984c52
9 changed files with 114 additions and 16 deletions

View file

@ -144,7 +144,8 @@ function gltf_prim(render3d_st: mut Render3dState, p: Val) -> Prim {
if un != null { pr.nrm = gltf_texture(render3d_st, un, false) }
if ua != null { pr.arm = gltf_texture(render3d_st, ua, false) }
var mname: string = null
if value_has(mat, "name") != 0 { mname = value_as_str(value_get(mat, "name")) }
# a copy: the document goes, strings and all, at the next load
if value_has(mat, "name") != 0 { let s = value_as_str(value_get(mat, "name")); mname = s[0 .. len(s)] }
pr.name = mname
if mname != null {
if ud != null { gltf_mat_remember(render3d_st, mname, pr.diff, pr.nrm, pr.arm) }
@ -168,7 +169,7 @@ function gltf_load(render3d_st: mut Render3dState, dir: string, file: string, no
if text == null { print(`gltf: cannot read {dir}/{file}`); return null }
# the last file's tree goes first (it is read only until the next load: ludic.anim's read.ludic),
# and the text once parsed - nothing gives memory back on its own (plan 23 of maroon-lake)
if render3d_st.gltf_doc != null { Json.free(render3d_st.gltf_doc) }
if render3d_st.gltf_doc != null { Json.free_all(render3d_st.gltf_doc) }
render3d_st.gltf_doc = Json.parse(text)
free(text)
let buffers = value_get(render3d_st.gltf_doc, "buffers")