I started to investigate out why the performance is so slow. On Rust version I am getting 9k circles, on new one I am getting 6k circles. So about 33% slower.
I am using Instruments app on mac which gives very good insight into what takes a lot of time from app and what functions take a lot of time. However as the result of this app is not exportable, I will either write by hand here by copy pasting or make screenshots. The following is a 5s overview of what calls take the most time from a scene where a lot of circles are created and hit the ground:
Initial Overview
We have here the main thread with 84% usage, 4.69s out of 5.55s.
The other threads:
seem to be Rayon calling into Rapier, and there isn't much that can be done there. In these threads there is also a callback from our lib where we filter objects, however as the percentage isn't that big, our investigation won't focus on this.
That being said, we are left with the Main Thread, so lets go into that.
Note that going forward, percentages are based on the thing we are focused on. Here it's going to be based on Main Thread duration, 4.66s.
Main Thread (4.66s)
The main thread is then split into 4 parts:
43.8% (2.06s) call to RapierPhysicsServer2D. (flush queries)
37.5% (1.76s) call to RenderingServerDefault::_draw. This is godot code. In here we have most notably vulkan functions, and nothing much to do with us.
13.6% (640ms) call to RapierPhysicsServer2D. (step)
3.6% (173ms) call to SceneTree::physics_process. This is godot code. It seems to handle updating of objects to be drawn.
Flow 2 and 4 don't seem to call back into our lib. So we focus on 1 and 4.
Main Thread -> Flow 3(640ms, 13.6%) - Step
The purpose of this function is to simulate the whole physics world with a step interval. Handles collisions and everything.
Same as before, gdext hides away the function, but our specific class calls into step function, so it's most likely that.
Here going down into the step function, it splits into:
73.1% call into rapier lib. Here there isn't much we can do about it, aside for a callback down the line
with a small percentage is in our code as well, handle_contact_force :
Not much to be said about this, and even if we do take this out of the equation and re-run everything, we don't see a big change in the time.
Also, seeing as the main physics process part, the Step function only takes this little, and the rest alone is just the Flush Queries part, means probably the bottle neck is there.
Main Thread -> Flow 1 (2.05s, 43.8%) - Flush Queries
The purpose of this is to call into the Callables godot offer to some physics objects. These objects call these in case of collision events or active change events, etc.
Notice that we have a call into RapierPhysicsServer2D, but it doesn't say specifically where. This is because of how gdext works, it seems to hide away the call. But we do see later down the call it calls into our implementation of flush_queries, so it's coming from the PhysicsServer2D flush_queries method. This method is mainly going to be calling back into godot by calling Callables (that it previously handed down to physics objects) that had collision events or just are active.
If we open this flow, we can see that we have:
69.4% (1.43s) Callable::callv function. We will go into this a bit later, as the other ones are small.
6.4% (133ms) godot_core::meta::class_name::ClassName::to_string_name
5.5% (108ms) godot_core::builtin::callable::Callable::bindv - quite expensive for just binding the arguments it seems.
3.6% (76ms) godot_core::builtin::callable::Callable::is_valid. I like how here it uses os_unfair_lock. Haha.
2% (54ms) godot_core::gen::builtin_classes::array::InnerArray::push_back
Then the rest seem to be like this, keep getting smaller and smaller:
Main -> Flow1 -> Callable::callv (1.43s)
Going inside the Callable, it seems godot calls back into the library 5 times (from RigidBody2D::_sync_body_state):
352ms (27.8%) Here we see a split into drop_in_place and RawGd::bind :
323ms (25.5%) Here most of it is from drop_in_place and RawGd::bind:
199ms (15.7%) Same as previous call, drop_in_place and RawGd::bind
168ms (13.3%) Same
164ms (12.9%) Same

I started to investigate out why the performance is so slow. On Rust version I am getting 9k circles, on new one I am getting 6k circles. So about 33% slower.
I am using Instruments app on mac which gives very good insight into what takes a lot of time from app and what functions take a lot of time. However as the result of this app is not exportable, I will either write by hand here by copy pasting or make screenshots. The following is a 5s overview of what calls take the most time from a scene where a lot of circles are created and hit the ground:
Initial Overview
We have here the main thread with
84%usage,4.69sout of5.55s.The other threads:
3.7%1.7%seem to be Rayon calling into Rapier, and there isn't much that can be done there. In these threads there is also a callback from our lib where we filter objects, however as the percentage isn't that big, our investigation won't focus on this.
That being said, we are left with the Main Thread, so lets go into that.
Note that going forward, percentages are based on the thing we are focused on. Here it's going to be based on Main Thread duration,
4.66s.Main Thread (
4.66s)The main thread is then split into 4 parts:
43.8%(2.06s) call to RapierPhysicsServer2D. (flush queries)37.5%(1.76s) call toRenderingServerDefault::_draw. This is godot code. In here we have most notably vulkan functions, and nothing much to do with us.13.6%(640ms) call to RapierPhysicsServer2D. (step)3.6%(173ms) call toSceneTree::physics_process. This is godot code. It seems to handle updating of objects to be drawn.Flow 2 and 4 don't seem to call back into our lib. So we focus on 1 and 4.
Main Thread -> Flow 3(
640ms,13.6%) - StepThe purpose of this function is to simulate the whole physics world with a step interval. Handles collisions and everything.
Same as before, gdext hides away the function, but our specific class calls into step function, so it's most likely that.
Here going down into the step function, it splits into:
73.1%call into rapier lib. Here there isn't much we can do about it, aside for a callback down the linewith a small percentage is in our code as well,
handle_contact_force:Not much to be said about this, and even if we do take this out of the equation and re-run everything, we don't see a big change in the time.
Also, seeing as the main physics process part, the
Stepfunction only takes this little, and the rest alone is just theFlush Queriespart, means probably the bottle neck is there.Main Thread -> Flow 1 (
2.05s,43.8%) - Flush QueriesThe purpose of this is to call into the
Callablesgodot offer to some physics objects. These objects call these in case of collision events or active change events, etc.Notice that we have a call into RapierPhysicsServer2D, but it doesn't say specifically where. This is because of how gdext works, it seems to hide away the call. But we do see later down the call it calls into our implementation of
flush_queries, so it's coming from thePhysicsServer2Dflush_queriesmethod. This method is mainly going to be calling back into godot by callingCallables(that it previously handed down to physics objects) that had collision events or just are active.If we open this flow, we can see that we have:
69.4%(1.43s)Callable::callvfunction. We will go into this a bit later, as the other ones are small.6.4%(133ms)godot_core::meta::class_name::ClassName::to_string_name5.5%(108ms)godot_core::builtin::callable::Callable::bindv- quite expensive for just binding the arguments it seems.3.6%(76ms)godot_core::builtin::callable::Callable::is_valid. I like how here it usesos_unfair_lock. Haha.2%(54ms)godot_core::gen::builtin_classes::array::InnerArray::push_backThen the rest seem to be like this, keep getting smaller and smaller:
Main -> Flow1 ->
Callable::callv(1.43s)Going inside the Callable, it seems godot calls back into the library 5 times (from
RigidBody2D::_sync_body_state):352ms(27.8%) Here we see a split intodrop_in_placeandRawGd::bind:323ms(25.5%) Here most of it is fromdrop_in_placeandRawGd::bind:199ms(15.7%) Same as previous call,drop_in_placeandRawGd::bind168ms(13.3%) Same164ms(12.9%) Same