Update getBodiesUnsafe() to return a slice of size getMaxBodies()#25
Update getBodiesUnsafe() to return a slice of size getMaxBodies()#25r0ckHopper wants to merge 3 commits into
Conversation
getBodiesUnsafe() returned ptr[0..getNumBodies()] but its values can go upto 1024 (getMaxBodies) After destroying bodies, the active count shrinks but remaining bodies stay at the same slots, causing out-of-bounds exceptions. The underlying Jolt array also uses max_bodies, so slicing to getMaxBodies() is safe.
Same issue as addressed in commit 77a907d getBodiesMutUnsafe() returned ptr[0..getNumBodies()] but its values can go upto 1024 (getMaxBodies) After destroying bodies, the active count shrinks but remaining bodies stay at the same slots, causing out-of-bounds exceptions. The underlying Jolt array also uses max_bodies, so slicing to getMaxBodies() is safe.
There was a problem hiding this comment.
Pull request overview
This PR fixes an API mismatch where PhysicsSystem.getBodiesUnsafe() / getBodiesMutUnsafe() returned a slice sized to getNumBodies() even though the underlying Jolt buffer is sized to max_bodies, which could cause out-of-bounds indexing when accessing bodies by BodyId after deletions create holes.
Changes:
- Return a
getMaxBodies()-sized slice fromgetBodiesUnsafe()andgetBodiesMutUnsafe(). - Add a regression test covering the “destroy middle body then index by
BodyId” scenario.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub fn getBodiesUnsafe(physics_system: *const PhysicsSystem) []const *const Body { | ||
| const ptr = c.JPC_PhysicsSystem_GetBodiesUnsafe( | ||
| @as(*c.JPC_PhysicsSystem, @ptrFromInt(@intFromPtr(physics_system))), | ||
| ); | ||
| return @as([*]const *const Body, @ptrCast(ptr))[0..physics_system.getNumBodies()]; | ||
| return @as([*]const *const Body, @ptrCast(ptr))[0..physics_system.getMaxBodies()]; |
There was a problem hiding this comment.
Which doc is Copilot referencing? Is it the comment above the function?
| /// NOTE: Advanced. This function is *not* protected by a lock, use with care! | ||
| pub fn getBodiesMutUnsafe(physics_system: *PhysicsSystem) []const *Body { | ||
| const ptr = c.JPC_PhysicsSystem_GetBodiesUnsafe(@as(*c.JPC_PhysicsSystem, @ptrCast(physics_system))); | ||
| return @as([*]const *Body, @ptrCast(ptr))[0..physics_system.getNumBodies()]; | ||
| return @as([*]const *Body, @ptrCast(ptr))[0..physics_system.getMaxBodies()]; | ||
| } |
| const physics_system = try PhysicsSystem.create( | ||
| @as(*const BroadPhaseLayerInterface, @ptrCast(&test_cb1.MyBroadphaseLayerInterface.init())), | ||
| @as(*const ObjectVsBroadPhaseLayerFilter, @ptrCast(&test_cb1.MyObjectVsBroadPhaseLayerFilter{})), | ||
| @as(*const ObjectLayerPairFilter, @ptrCast(&test_cb1.MyObjectLayerPairFilter{})), | ||
| .{ .max_bodies = 1024, .num_body_mutexes = 0, .max_body_pairs = 1024, .max_contact_constraints = 1024 }, | ||
| ); |
| const all_bodies = physics_system.getBodiesMutUnsafe(); | ||
|
|
||
| //following should not throw panic | ||
| //zig does not have panic handler for unit tests so best we can do is test happy path or crash program | ||
| _ = tryGetBody(all_bodies, b2); |
There was a problem hiding this comment.
That runs counter to the intention of the test. Checking if null does not assert that no panic is thrown (which is a zig limitation, that panic cannot be caught). So only option right now is to test happy path or remove test entirely.
Ideally there would be try catch for panic, and if panic is caught then test fails.
| const b1 = try body_interface.createAndAddBody(settings, .activate); | ||
| const b2 = try body_interface.createAndAddBody(settings, .activate); | ||
|
|
||
| _ = b0; //just to rid of compiler error |
Currently getBodiesUnsafe() returns a slice of size getNumBodies() but getBodiesUnsafe() actually returns a pointer to slice of size max_bodies.
The issue comes when you delete bodies from the middle and try to access bodies of higher index
getBodiesUnsafe does not reorganize the elements before giving a slice so you can get an out of bounds error
example of the issue is:
let bodies be
[b0, b1, b2]
if you delete b1 jolt physics updates bodies to
[b0, invalid, b2]
but if you call getBodiesUnsafe you only get
[b0, invalid]
so if you do tryGetBody( all_bodies, b2) then it will look for b2 at index 2 which is out of bounds