diff --git a/packages/ludic.physics/bodies.ludic b/packages/ludic.physics/bodies.ludic index d53eedc3..76ae83d4 100644 --- a/packages/ludic.physics/bodies.ludic +++ b/packages/ludic.physics/bodies.ludic @@ -36,11 +36,12 @@ export function phys_count(physics_st: PhysicsState) -> int { return jph_world_bodies(physics_st.ph_world) } -# gone from the world, for good: what a struck tent needs +# gone from the world, for good: what a struck tent needs, and the shape it owned (owned.ludic) with it export function phys_remove(physics_st: mut PhysicsState, id: int) -> void { if physics_st.ph_world == null or id < 0 { return } phys_sink(physics_st, id) jph_body_remove(physics_st.ph_world, id) + ph_own_free(physics_st, id) } # where a body is, how it is turned and how fast it goes, into the caller's own record (a query asked diff --git a/packages/ludic.physics/hull.ludic b/packages/ludic.physics/hull.ludic index 65f93453..cd29d487 100644 --- a/packages/ludic.physics/hull.ludic +++ b/packages/ludic.physics/hull.ludic @@ -3,8 +3,10 @@ # hx, hy, hz its half width, depth and length; buoyancy over 1 floats it (4: a quarter under) export function phys_hull_add(physics_st: mut PhysicsState, x: float, y: float, z: float, yaw: float, hx: float, hy: float, hz: float, mass: float, buoyancy: float, drag: float) -> int { - let id = phys_body_add(physics_st, phys_box(physics_st, hx, hy, hz), x, y, z, yaw, mass) + let s = phys_box(physics_st, hx, hy, hz) + let id = phys_body_add(physics_st, s, x, y, z, yaw, mass) if id < 0 { return -1 } + phys_own(physics_st, id, s) phys_float(physics_st, id, buoyancy, drag, drag * 1.5) return id } diff --git a/packages/ludic.physics/index.ludic b/packages/ludic.physics/index.ludic index 9fd169bc..df3b930b 100644 --- a/packages/ludic.physics/index.ludic +++ b/packages/ludic.physics/index.ludic @@ -6,6 +6,7 @@ import "ludic.base" import "native.ludic" import "state.ludic" import "shape_ids.ludic" +import "owned.ludic" import "shapes.ludic" import "bodies.ludic" import "queries.ludic" diff --git a/packages/ludic.physics/owned.ludic b/packages/ludic.physics/owned.ludic new file mode 100644 index 00000000..78f7c879 --- /dev/null +++ b/packages/ludic.physics/owned.ludic @@ -0,0 +1,28 @@ +# owned.ludic - a shape a body owns: made for that body alone (a trunk's pillar, a boulder's scaled +# hull, a dropped thing's box) and freed when the body is removed, so a still thing put in and taken out +# again and again - a chunk that comes and goes - holds no more shapes than are standing +const PH_INDEX: int = 8388607 # a body id's low 23 bits are its index (Jolt's BodyID) + +# every body owns nothing: at a world's opening, the table made once at its size and kept after +function ph_own_reset(physics_st: mut PhysicsState, max_bodies: int) -> void { + if len(physics_st.ph_own) < max_bodies { + List.clear(physics_st.ph_own) + for i in 0 .. max_bodies { push(physics_st.ph_own, -1) } + return + } + for i in 0 .. len(physics_st.ph_own) { physics_st.ph_own[i] = -1 } +} + +# body `id` owns `shape`: phys_remove frees it with the body +export function phys_own(physics_st: mut PhysicsState, id: int, shape: int) -> void { + if id < 0 or shape < 0 { return } + let i = id & PH_INDEX + if i < len(physics_st.ph_own) { physics_st.ph_own[i] = shape } +} + +function ph_own_free(physics_st: mut PhysicsState, id: int) -> void { + let i = id & PH_INDEX + if i >= len(physics_st.ph_own) or physics_st.ph_own[i] < 0 { return } + phys_shape_free(physics_st, physics_st.ph_own[i]) + physics_st.ph_own[i] = -1 +} diff --git a/packages/ludic.physics/shapes.ludic b/packages/ludic.physics/shapes.ludic index bb525d94..f72ae383 100644 --- a/packages/ludic.physics/shapes.ludic +++ b/packages/ludic.physics/shapes.ludic @@ -6,6 +6,7 @@ export function phys_open(physics_st: mut PhysicsState, max_bodies: int, threads phys_close(physics_st) ph_buffers() physics_st.ph_world = jph_world_new(max_bodies, threads) + ph_own_reset(physics_st, max_bodies) physics_st.ph_gen += 1 physics_st.ph_acc = 0.0 physics_st.ph_steps = 0 diff --git a/packages/ludic.physics/state.ludic b/packages/ludic.physics/state.ludic index dd5cf4cc..5cbdc201 100644 --- a/packages/ludic.physics/state.ludic +++ b/packages/ludic.physics/state.ludic @@ -38,6 +38,7 @@ export state PhysicsState { ph_world: pointer = null ph_shapes: []pointer = new []pointer # a shape id is its place here; null once freed ph_spare: []int = new []int # the freed places, handed out again (shape_ids.ludic) + ph_own: []int = new []int # by a body's index: the shape it owns, freed with it (owned.ludic) ph_step: float = 0.016666668 # seconds a step ph_sub: int = 1 # collision steps in each ph_max_steps: int = 4 # a slow frame catches up this far and no further diff --git a/packages/ludic.physics/tests/reuse_test.ludic b/packages/ludic.physics/tests/reuse_test.ludic index ecbe8bcd..ca187bdb 100644 --- a/packages/ludic.physics/tests/reuse_test.ludic +++ b/packages/ludic.physics/tests/reuse_test.ludic @@ -39,4 +39,25 @@ program ReuseTest { expect_eq(phys_shapes(physics_st), 1) phys_close(physics_st) } + + test "a body that owns its shape takes the shape with it, a thousand times over" (physics_st: mut PhysicsState) { + expect(phys_open(physics_st, 64, 1)) + let s0 = phys_cylinder(physics_st, 5.0, 0.3) + phys_own(physics_st, phys_static_add(physics_st, s0, 0.0, 0.0, 0.0, 0.0), s0) + let first = phys_static_add(physics_st, phys_cylinder(physics_st, 5.0, 0.3), 3.0, 0.0, 0.0, 0.0) + phys_remove(physics_st, first) + let before = Os.heap_bytes() + for k in 0 .. 1000 { + let s = phys_cylinder(physics_st, 5.0, 0.3) + let b = phys_static_add(physics_st, s, float(k % 10), 0.0, 5.0, 0.0) + phys_own(physics_st, b, s) + phys_remove(physics_st, b) + } + let grew = Os.heap_bytes() - before + print(`reuse: 1000 owned bodies put and removed, {phys_shapes(physics_st)} shapes held in {len(physics_st.ph_shapes)} places, heap {grew} bytes`) + expect(phys_shapes(physics_st) <= 2) + expect(len(physics_st.ph_shapes) <= 3) + expect(grew < long(1024)) + phys_close(physics_st) + } }