I am unfamiliar with Rust but comfortable with languages like C++, C#, and Javascript.
SAMPLE CODE
Rapier is a physics simulator you can use in Rust. An example of setting up a simulation is given by:
https://www.rapier.rs/docs/user_guides/rust/getting_started
use rapier3d::prelude::*;
fn main() {
let mut rigid_body_set = RigidBodySet::new();
let mut collider_set = ColliderSet::new();
/* Create the ground. */
let collider = ColliderBuilder::cuboid(100.0, 0.1, 100.0).build();
collider_set.insert(collider);
/* 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();
let ball_body_handle = rigid_body_set.insert(rigid_body);
collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);
/* Create other structures necessary for the simulation. */
let gravity = vector![0.0, -9.81, 0.0];
let integration_parameters = IntegrationParameters::default();
let mut physics_pipeline = PhysicsPipeline::new();
let mut island_manager = IslandManager::new();
let mut broad_phase = DefaultBroadPhase::new();
let mut narrow_phase = NarrowPhase::new();
let mut impulse_joint_set = ImpulseJointSet::new();
let mut multibody_joint_set = MultibodyJointSet::new();
let mut ccd_solver = CCDSolver::new();
let mut query_pipeline = QueryPipeline::new();
let physics_hooks = ();
let event_handler = ();
/* Run the game loop, stepping the simulation once per frame. */
for _ in 0..200 {
physics_pipeline.step(
&gravity,
&integration_parameters,
&mut island_manager,
&mut broad_phase,
&mut narrow_phase,
&mut rigid_body_set,
&mut collider_set,
&mut impulse_joint_set,
&mut multibody_joint_set,
&mut ccd_solver,
Some(&mut query_pipeline),
&physics_hooks,
&event_handler,
);
let ball_body = &rigid_body_set[ball_body_handle];
println!("Ball altitude: {}", ball_body.translation().y);
}
}
So basically as you see there we are creating a list of "objects" including the main one physics_pipeline
which is stepped forward in basically a for loop for a few hundred steps there.
GOAL
But what if we actually want to run this continuously at a certain time rate (not just in a for loop like there)? And either advance it or turn it on/off via external trigger?
1) CREATE PHYSICS OBJECTS AS STATIC MUT
Like I would intuitively want to re-write this as:
use rapier3d::prelude::*;
static mut rigid_body_set: RigidBodySet;
static mut collider_set: ColliderSet;
static mut integration_parameters: IntegrationParameters;
static mut physics_pipeline: PhysicsPipeline;
static mut island_manager: IslandManager;
static mut broad_phase: DefaultBroadPhase;
static mut narrow_phase: NarrowPhase;
static mut impulse_joint_set: ImpulseJointSet;
static mut multibody_joint_set: MultibodyJointSet;
static mut ccd_solver: CCDSolver;
static mut query_pipeline: QueryPipeline;
fn initialize_simulation() {
unsafe {
rigid_body_set = RigidBodySet::new();
collider_set = ColliderSet::new();
/* Create the ground. */
let collider = ColliderBuilder::cuboid(100.0, 0.1, 100.0).build();
collider_set.insert(collider);
/* 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();
let ball_body_handle = rigid_body_set.insert(rigid_body);
collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);
/* Create other structures necessary for the simulation. */
integration_parameters = IntegrationParameters::default();
physics_pipeline = PhysicsPipeline::new();
island_manager = IslandManager::new();
broad_phase = DefaultBroadPhase::new();
narrow_phase = NarrowPhase::new();
impulse_joint_set = ImpulseJointSet::new();
multibody_joint_set = MultibodyJointSet::new();
ccd_solver = CCDSolver::new();
query_pipeline = QueryPipeline::new();
}
}
fn advance_simulation(){
unsafe {
let gravity = vector![0.0, -9.81, 0.0];
let physics_hooks = ();
let event_handler = ();
/* Run the game loop, stepping the simulation once per frame. */
physics_pipeline.step(
&gravity,
&integration_parameters,
&mut island_manager,
&mut broad_phase,
&mut narrow_phase,
&mut rigid_body_set,
&mut collider_set,
&mut impulse_joint_set,
&mut multibody_joint_set,
&mut ccd_solver,
Some(&mut query_pipeline),
&physics_hooks,
&event_handler,
);
let ball_body = &rigid_body_set[ball_body_handle];
println!("Ball altitude: {}", ball_body.translation().y);
}
}
The idea is I want to be able to keep state and invoke the updates externally, as I will be triggering this from elsewhere. Is this what is intended for Rust?
Ie. If I want to change the above top demo code to have a separate initialize
and advance
function like this, that can then be externally controlled, is this how it must be done?
I get warnings on any usage of modifying even static mut magic_int: i32 = 5;
in Rust playground code. I read that static mut is terrifyingly unsafe.
But Rapier is used as the physics engine for Bevy a game engine. How can it run such a Physics simulation in a game engine without holding state like I describe in my modified code?
2) SWITCH TO WHILE LOOP WITH WAITING AND BOOL ON/OFF
An alternative approach would be to make the original for loop operation a while
loop instead and let it run infinitely (unless disrupted). We could set a static mutable atomic bool for should_run_sim
which can be turned on/off. We would now need a wait
command at the end to make it wait before re-looping since it is now controlling the timing of its own loop.
Then the for loop from the original demo code becomes (pseudo-code):
while (1<2){ //always true
if (should_run_sim){
//run the simulation code from the top for loop here
}
//delay before looping again
let delay = time::Duration::from_secs(1/60.0);
thread::sleep(delay);
}
And then we can switch the sim on and off by external influence by changing the bool.
Benefit here is we only need one static mut
(the bool). Now we need it to run its own "wait" function to increment at the desired time rate.
QUESTION
What is the correct solution? Are both these approaches valid? Or is there something else I'm not thinking of?
Edit: I think I must keep the static mut
to minimum and wrap them in Arc Mutexes.
So maybe my best approach is to use the second option where I am letting it loop but controlling it on/off with a Mutex bool.