From 6e132d25c8512339f80ec56b6767c760bc0d2cb6 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Mon, 28 Sep 2026 13:05:28 +0300 Subject: [PATCH] fix(ludic.physics): a removed walker's place is taken again, and phys_close keeps the lists it empties - nothing grows per horse called or per world swap tests/reuse_test: 200 walkers made and removed hold one place and 0 bytes of heap (Os.heap_bytes); twenty closes and opens keep the same shape and walker lists. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.physics/shapes.ludic | 7 ++-- packages/ludic.physics/tests/reuse_test.ludic | 42 +++++++++++++++++++ packages/ludic.physics/walker.ludic | 10 ++++- 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 packages/ludic.physics/tests/reuse_test.ludic diff --git a/packages/ludic.physics/shapes.ludic b/packages/ludic.physics/shapes.ludic index 3ec7a04d..bb525d94 100644 --- a/packages/ludic.physics/shapes.ludic +++ b/packages/ludic.physics/shapes.ludic @@ -16,14 +16,15 @@ export function phys_is_open(physics_st: PhysicsState) -> bool { return physics_ # which world this is: a body, shape or walker id kept from an older one means nothing now export function phys_generation(physics_st: PhysicsState) -> int { return physics_st.ph_gen } -# everything let go: the world, every body in it and every shape (a world swap, a quit) +# everything let go: the world, every body in it and every shape (a world swap, a quit); the lists +# that held them are emptied and kept, since nothing is given back export function phys_close(physics_st: mut PhysicsState) -> void { if physics_st.ph_walkers != null { for i in 0 .. len(physics_st.ph_walkers) { if physics_st.ph_walkers[i] != null { jph_char_free(physics_st.ph_walkers[i]) } } } - physics_st.ph_walkers = new []pointer + if physics_st.ph_walkers != null { List.clear(physics_st.ph_walkers) } if physics_st.ph_world != null { jph_world_free(physics_st.ph_world) } physics_st.ph_world = null if physics_st.ph_shapes != null { @@ -31,7 +32,7 @@ export function phys_close(physics_st: mut PhysicsState) -> void { if physics_st.ph_shapes[i] != null { jph_shape_free(physics_st.ph_shapes[i]) } } } - physics_st.ph_shapes = new []pointer + if physics_st.ph_shapes != null { List.clear(physics_st.ph_shapes) } List.clear(physics_st.ph_spare) List.clear(physics_st.ph_floats) List.clear(physics_st.ph_float_k) diff --git a/packages/ludic.physics/tests/reuse_test.ludic b/packages/ludic.physics/tests/reuse_test.ludic new file mode 100644 index 00000000..ecbe8bcd --- /dev/null +++ b/packages/ludic.physics/tests/reuse_test.ludic @@ -0,0 +1,42 @@ +# reuse_test.ludic - what is taken away is given back: a walker made and removed two hundred times +# holds one place in the list and no more heap, and a world closed and opened again keeps its lists +import "ludic.physics" +import "ludic.base" +program ReuseTest { + numbers float + + test "a walker made and removed takes the same place and grows nothing" (physics_st: mut PhysicsState) { + expect(phys_open(physics_st, 64, 1)) + let w = phys_walker_add(physics_st, 0.4, 1.8, 0.0, 0.0, 0.0, 50.0, 80.0) + phys_walker_remove(physics_st, w) + let before = Os.heap_bytes() + for k in 0 .. 200 { + let id = phys_walker_add(physics_st, 0.4, 1.8, float(k), 0.0, 0.0, 50.0, 80.0) + expect_eq(id, w) + phys_walker_remove(physics_st, id) + } + let grew = Os.heap_bytes() - before + print(`reuse: 200 walkers made and removed, {len(physics_st.ph_walkers)} place, heap {grew} bytes`) + expect_eq(len(physics_st.ph_walkers), 1) + expect(grew < long(1024)) + phys_close(physics_st) + } + + test "a world closed and opened again keeps the lists it held" (physics_st: mut PhysicsState) { + expect(phys_open(physics_st, 64, 1)) + phys_box(physics_st, 1.0, 1.0, 1.0) + phys_walker_add(physics_st, 0.4, 1.8, 0.0, 0.0, 0.0, 50.0, 80.0) + let shapes = physics_st.ph_shapes + let walkers = physics_st.ph_walkers + for k in 0 .. 20 { + phys_close(physics_st) + expect(phys_open(physics_st, 64, 1)) + phys_box(physics_st, 1.0, 1.0, 1.0) + phys_walker_add(physics_st, 0.4, 1.8, 0.0, 0.0, 0.0, 50.0, 80.0) + } + expect(physics_st.ph_shapes == shapes) + expect(physics_st.ph_walkers == walkers) + expect_eq(phys_shapes(physics_st), 1) + phys_close(physics_st) + } +} diff --git a/packages/ludic.physics/walker.ludic b/packages/ludic.physics/walker.ludic index eb439790..c77c5776 100644 --- a/packages/ludic.physics/walker.ludic +++ b/packages/ludic.physics/walker.ludic @@ -27,7 +27,15 @@ function ph_walker(physics_st: PhysicsState, id: int) -> pointer { export function phys_walker_add(physics_st: mut PhysicsState, r: float, h: float, x: float, y: float, z: float, max_slope: float, mass: float) -> int { if physics_st.ph_world == null { return -1 } ph_buffers() - push(physics_st.ph_walkers, jph_char_new(physics_st.ph_world, r, h, x, y, z, max_slope, mass)) + let k = jph_char_new(physics_st.ph_world, r, h, x, y, z, max_slope, mass) + # a removed walker's place is taken again, so a horse called and sent away grows nothing + for i in 0 .. len(physics_st.ph_walkers) { + if physics_st.ph_walkers[i] == null { + physics_st.ph_walkers[i] = k + return i + } + } + push(physics_st.ph_walkers, k) return len(physics_st.ph_walkers) - 1 }