fix(leaks): what play allocated every frame and never freed - a shadow uniform's name, the Tick, a ridden vehicle's moves

Found with malloc_history over 1200 frames of play without the interface (the first Ludic function
on each allocating stack, live bytes at two marks): sh_loc built `name + "[0]"` per program per pass
per frame - 24.6 MB of 33 in the window; its callers pass both names as literals now. tick_set fills
the frame loop's one Tick instead of tick_new making one a frame. A ridden boat or horse said
VEHICLE_MOVED as a new fact every frame; the metres are added up in VehiclesState and taken once
(vehicle_moved_take / _kind / _x / _z). Play's growth without the interface: 92 -> 14.7 MB a minute
(maroon-lake tests/leakcheck.sh); what is left is phys_push (Physics' fix on lang/nav) and the HUD's
strings. Packages 407, ludic-dev test 305 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 23:51:37 +03:00
parent 8fbb1f7d03
commit 231715bf98
9 changed files with 47 additions and 10 deletions

View file

@ -232,8 +232,10 @@ function shadow_dump(render3d_st: mut Render3dState) -> void {
}
# a uniform array's location: some drivers only answer to the "[0]" spelling
function sh_loc(render3d_st: Render3dState, prog: int, name: string) -> int {
var loc = gpu_uniform(render3d_st, prog, name + "[0]")
# an array uniform by its first element's name, else its bare one; both names are literals, because
# building `name + "[0]"` here made a string per program per pass per frame that was never freed
function sh_loc(render3d_st: Render3dState, prog: int, first: string, name: string) -> int {
var loc = gpu_uniform(render3d_st, prog, first)
if loc < 0 { loc = gpu_uniform(render3d_st, prog, name) }
return loc
}
@ -252,10 +254,10 @@ function shadow_bind(render3d_st: mut Render3dState, prog: int) -> void {
if loc < 0 { loc = gpu_uniform(render3d_st, prog, "u_cascade_vp") }
if render3d_st.r3d_debug_shadow and not render3d_st.sh_printed { render3d_st.sh_printed = true; print(`cascade vp loc {loc} / {gpu_uniform(render3d_st, prog, "u_cascade_vp")} split loc {gpu_uniform(render3d_st, prog, "u_cascade_split")} shadow loc {gpu_uniform(render3d_st, prog, "u_shadow")}`) }
u_mat4n(render3d_st, loc, SHADOW_CASCADES, render3d_st.sh_vp)
u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_split"), SHADOW_CASCADES, render3d_st.sh_split)
u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_split[0]", "u_cascade_split"), SHADOW_CASCADES, render3d_st.sh_split)
if render3d_st.r3d_debug_shadow and not render3d_st.sh_printed3 { render3d_st.sh_printed3 = true; print(`range {fixed(render3d_st.sh_range[0])} {fixed(render3d_st.sh_range[1])} {fixed(render3d_st.sh_range[2])} {fixed(render3d_st.sh_range[3])} texel*1000 {fixed(render3d_st.sh_texel[0] * 1000.0)} {fixed(render3d_st.sh_texel[1] * 1000.0)} {fixed(render3d_st.sh_texel[2] * 1000.0)} {fixed(render3d_st.sh_texel[3] * 1000.0)} locs {gpu_uniform(render3d_st, prog, "u_cascade_range")} {gpu_uniform(render3d_st, prog, "u_cascade_texel")}`) }
u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_range"), SHADOW_CASCADES, render3d_st.sh_range)
u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_range[0]", "u_cascade_range"), SHADOW_CASCADES, render3d_st.sh_range)
if r3d_env_has(render3d_st, "R3D_FORCE") { render3d_st.sh_force = Text.to_int(r3d_env(render3d_st, "R3D_FORCE")) }
u_i(render3d_st, gpu_uniform(render3d_st, prog, "u_force_cascade"), render3d_st.sh_force)
u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_texel"), SHADOW_CASCADES, render3d_st.sh_texel)
u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_texel[0]", "u_cascade_texel"), SHADOW_CASCADES, render3d_st.sh_texel)
}