Thank you! This handle
concept seems to be what they have done in the demo code.
THEIR HANDLE USE
It says: https://www.rapier.rs/docs/user_guides/rust/getting_started/
/* Create the bounding ball. */
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, 10.0, 0.0]).build();
let collider = ColliderBuilder::ball(0.5).restitution(0.7).build();
//returns RigidBodyHandle:
let ball_body_handle = rigid_body_set.insert(rigid_body);
//returns ColliderHandle:
collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);
//then to get a read on this object's position:
let ball_body = &rigid_body_set[ball_body_handle]; //use the handle to access
println!("Ball altitude: {}", ball_body.translation().y);
So it appears this code does provide a valid potential handler for both elements (collider and rigid body) in just the above. They are just not saving both handles as not using both.
I was able to figure this all out from what you said. I documented my research and solution below in part for my own future reference, though I have a few minor questions as well if you don't mind. Either way, thanks already.
WHAT IS THE HANDLE TYPE HERE?
The source code for RigidBodySet & ColliderSet shows these handles that are returned are made in a similar way by in each of these pages:
let handle = ColliderHandle(self.colliders.insert(coll)); //for ColliderSet
let handle = RigidBodyHandle(self.bodies.insert(rb)); //for RigidBodySet
To trace this further, bodies
and colliders
are:
pub(crate) colliders: Arena<Collider>, //for ColliderSet
pub(crate) bodies: Arena<RigidBody>, //for RigidBodySet
Arena is defined here where it says:
/// The `Arena` allows inserting and removing elements that are referred to by `Index`.
pub struct Arena<T> {
items: Vec<Entry<T>>, //THE ACTUAL VECTOR HOLDING THE ELEMENTS
generation: u32,
free_list_head: Option<u32>,
len: usize,
}
pub fn insert(&mut self, value: T) -> Index { //THE INSERT COMMAND BEING RUN
match self.try_insert(value) {
Ok(i) => i,
Err(value) => self.insert_slow_path(value),
}
}
#[inline]
pub fn try_insert(&mut self, value: T) -> Result<Index, T> {
match self.try_alloc_next_index() {
None => Err(value),
Some(index) => {
//INSERT TO ACTUAL VECTOR HERE
self.items[index.index as usize] = Entry::Occupied {
generation: self.generation,
value,
};
Ok(index)
}
}
}
So this is where we have the core vector (inside Arena
) storing the objects and the insert
function returns a Index
struct.
The Index
struct is defined here, showing it is just a wrapper for a u32 index
and generation
(unclear significance of generation
) int:
pub struct Index {
index: u32,
generation: u32,
}
To return the final handle, this Index
is then passed into the ColliderHandle
here and RigidBodyHandle
here, though I can't really understand what it is doing by passing it in here, or what it truly is returning as the handle
.
pub struct RigidBodyHandle(pub crate::data::arena::Index);
impl RigidBodyHandle {
/// Converts this handle into its (index, generation) components.
pub fn into_raw_parts(self) -> (u32, u32) {
self.0.into_raw_parts()
}
/// Reconstructs an handle from its (index, generation) components.
pub fn from_raw_parts(id: u32, generation: u32) -> Self {
Self(crate::data::arena::Index::from_raw_parts(id, generation))
}
/// An always-invalid rigid-body handle.
pub fn invalid() -> Self {
Self(crate::data::arena::Index::from_raw_parts(
crate::INVALID_U32,
crate::INVALID_U32,
))
}
}
What is this in the end then? ie. What is the handle? There seems to be no core handle
type in Rust, so what is it actually in this case from the above? I can't make sense of that last step in terms of what the final object is or what its structure looks like.
BOTTOM LINE CONCEPTUALLY
I suppose conceptually the bottom line is that the handle
is a conceptual design strategy meant as a way to safely reference the object after it is taken control of by a system (here, Rapier).
The usage here is designed to appear to emulate a simple indexed vector (which is what I thought it was doing). In let ball_body = &rigid_body_set[ball_body_handle];
, it appears as if ball_body_handle
is a plain int and rigid_body_set
is a plain vector. But this is not the case. It is just made to look this way for familiar function.
For example ChatGPT suggests to add then remove a Collider:
fn main() {
// Create a ColliderSet
let mut colliders = ColliderSet::new();
// Create a collider and insert it into the set
let circle = Circle::new(1.0);
let handle: ColliderHandle = colliders.insert(circle);
// Now, remove the collider using its handle
colliders.remove(handle);
// Optionally, check if the collider was removed
if !colliders.contains(handle) {
println!("Collider successfully removed.");
}
}
So I don't technically need to know "how this works" inside. They have wrapped the core vectors in all these layers to provide safe control.
CHAT GPT EXPLANATION
Again ChatGPT is too smart for my tastes :). They say:
Key Features of ColliderHandle:
Unique Identifier: Each ColliderHandle is unique within the ColliderSet, meaning you can use it to identify and manipulate a specific collider.
Ownership Management: The handle abstracts away the underlying data structure, allowing you to safely remove or modify colliders without directly dealing with the internals of the ColliderSet.
Type Safety: Using a ColliderHandle ensures that operations on colliders are type-safe, preventing issues that might arise from using raw pointers or indices.
Summary
ColliderHandle is used to reference colliders within a ColliderSet.
It provides a safe and efficient way to manage colliders without dealing with raw pointers.
It ensures that each collider can be uniquely identified and manipulated.
This design pattern promotes safety and encapsulation, which are core principles in Rust.
FOR MY PURPOSES (WRAP THE HANDLE) - WORKING METHOD
My goal to wrap the objects means I must instead wrap the handles and store those or references to those instead. It seems to be working okay.
This seems to work to replace their demo code:
pub struct BallSphere {
object_id: String,
rigid_body: RigidBodyHandle,
collider: ColliderHandle
}
impl BallSphere{
pub fn new(object_id: String, body_handle: RigidBodyHandle, collider_handle: ColliderHandle)-> Self {
let mut ball_sphere = BallSphere {
object_id: object_id,
rigid_body: body_handle,
collider: collider_handle
};
return ball_sphere;
}
}
Then usage:
// Create the bounding ball.
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, 10.0, 0.0]).build();
let collider = ColliderBuilder::ball(0.5).restitution(0.7).build();
//set into rapier system & get handles
let ball_body_handle = physics_unit.rigid_body_set.insert(rigid_body); //WORKS
let ball_collider_handle = physics_unit.collider_set.insert_with_parent(collider, ball_body_handle, &mut physics_unit.rigid_body_set); //WORKS
//save handle references into my wrapper
let ball_sphere : BallSphere = BallSphere::new("ball1".to_string(), ball_body_handle, ball_collider_handle);
physics_unit.ball_spheres.push(ball_sphere);
//then to get a read on this object's position:
let ball_body = &physics_unit.rigid_body_set[ball_sphere.body_handle]; //use the handle to access
println ("Ball altitude: {}", ball_body.translation().y);
What's interesting to me is it looks like we are allowed to have multiple copies of the handle references. I think this is because they are not mut
. I think you can have multiple immutable refs but only one mutable ref and this is why. We see this, because the ball_body_handle
is passed into collider_set.insert_with_parent
which saves a copy inside, but I am also able to save a copy in my BallSphere
wrapper without conflict. So I presume that is what is happening. Correct?
Anyway, either way, thanks, as that got me on the right track and seemed to work. Appreciated yet again. 