|
physac-rs is a Rust-native translation of Physac with built-in support for raylib-rs. Raylib is not required however, and can be disabled by disabling the See the examples directory for usage examples converted 1:1 from the original C implementation of Physac. While this library tries to mirror the C API, some changes have been made in order to improve soundness, and to shorten the names of certain methods where they are implied by the name of the type they are implemented for. |
-
Resources are automatically cleaned up when they go out of scope (or when
std::mem::dropis called). This means thatClosePhysicsis not exposed and not necessary.destroy_physics_body()is still required for unloading physics bodies though, because thecreatemethods store "strong" references (eitherstd::sync::Arcif thesyncfeature flag is enabled orstd::rc::Rcif not) inside ofPhysacand only return a reference to those references. -
Most of the Physac API is exposed through
Physac, which is used for storing all fields which the original C implementation has as static globals. This ensures thread safety, and can even allow multiple independent instances of Physac to safely run in the same program simultaneously. -
If the
syncfeature flag is enabled (which is the default),Physacis borrowed through thePhysacHandleto prevent race conditions between threads. If thesyncfeature flag is not enabled,PhysacHandlewill implementstd::ops::DerefMut, which allowsPhysacto be accessed directly by reference without borrowing (the borrow functions will still be available in case you would like to write your code in a way that works both with and without thesyncfeature flag). -
A
PhysacHandlecan be obtained through theinit_physics()function, which will allow you tobuildthe physics environment with some of the settings normally provided to Physac through#defines.Most of these values can be provided by chaining methods. However, while
MAX_VERTICESandCIRCLE_VERTICEShave default values, explicit values for both constants are unfortunately required at the moment to be provided to theinit_physics()function through "turbofish" (::<>) syntax (ex:init_physics::<24, 24>()). -
The fixed-capacity arrays that store
PhysicsBodys andPhysicsManifolds have been replaced with growableVecs, whose initial capacities are set ininit_physicswith themax_bodies()andmax_manifolds()chain methods respectively. -
Manually closing physics is not necessary, because it will automatically close when
PhysacHandlegoes out of scope or otherwise drops (such as withstd::mem::dropor during unwinding). If thephys_threadfeature flag is enabled, the physics thread will also finish & join when this happens.The physics thread can also finish if any unrecoverable errors occur on that thread, such as running out of IDs or a resource being poisoned (i.e. another thread panicks while mutably borrowing a physics body or
Physac). -
See the section on Thread Safety for important information about one of the more influencial differences between the C and Rust implementations of Physac.
Requires the Rust standard library. Optionally requires raylib-rs.
- Add the dependency to your
Cargo.toml
[dependencies]
physac = { version = "0.1" }or
[dependencies]
raylib = { version = "5.5" } # can be any version >=1.0
physac = { version = "0.1", features = ["raylib"] }- Start coding
use raylib::prelude::*;
use physac::prelude::*;
fn main() {
let (mut rl, thread) = raylib::init()
.size(640, 480)
.title("Hello, Physics")
.build();
let mut ph = init_physics::<24, 48>()
.gravity_force(0.0, 3.0)
.build();
let ball = ph.borrow_mut()
.create_physics_body_circle(Vector2::new(320.0, 240.0), 45.0, 10.0)
.clone();
ball.borrow_mut().restitution = 0.9;
let ball_id = ball.borrow().id;
ph.borrow_mut()
.create_physics_body_rectangle(Vector2::new(320.0, 450.0), 620.0, 40.0, 10.0)
.borrowed_mut(|floor| {
floor.enabled = false;
floor.restitution = 0.9;
});
while !rl.window_should_close() {
if rl.is_key_pressed(KeyboardKey::KEY_SPACE) {
ball.borrowed_mut(|ball| ball.velocity.y -= 1.0)
}
let mut d = rl.begin_drawing(&thread);
d.clear_background(Color::RAYWHITE);
for body in ph.borrow().physics_body_iter() {
let color = if body.id == ball_id {
Color::DODGERBLUE
} else {
Color::BLUEVIOLET
};
let vertices = body
.vertices_iter_closed()
.rev()
.collect::<Vec<Vector2>>();
d.draw_triangle_fan(&vertices, color);
}
}
}See the hello_physics example to run the code above.
| Feature Flag | Description | Default |
|---|---|---|
raylib |
Use raylib-rs in the library. If disabled, data types defined on raylib are defined internally in the library and input management and drawing functions must be provided by the user (check library implementation for further details). | Disabled |
sync |
The library will use std::sync instead of std::rc and std::cell. |
Enabled transitively by phys_thread |
phys_thread |
If disabled, the library won't use std::thread and user must create a secondary thread to call run_physics_step(), or call it in the main thread if sync is disabled. Requres and automatically enables sync. |
Enabled |
debug |
Traces log messages when creating and destroying physics bodies and detects errors in physics calculations and reference exceptions; it is useful for debug purposes. | Disabled |
Physac is inherently multithreading-compatible. In the C implementation, this is accomplished with statics and raw pointers. In the Rust version, some additional steps are needed to ensure safeness. This is accomplished with std::sync::Arc, std::sync::Weak, and std::sync::RwLock (which have been abstracted into Strong and Weak to enable compatibility between the multithreaded (sync) and single-threaded (rc) implementations).
If you want to "remember" a particular PhysicsBody across multiple frames (like if it the body of some object, or is part of a physics constraint), use a Strong<PhysicsBody> or Weak<PhysicsBody>.
Strong and Weak references can be clone()'d to obtain duplicate references to the same physics body. The body they refer to can be temporarily accessed with borrow(), borrow_mut(), borrowed(), borrowed_mut(), read(), or write().
borrow()andborrow_mut()return temporaryRead/WriteGuards, borrowing the body until the guard goes out of scope or gets dropped withstd::mem::drop().borrowed()andborrowed_mut()borrow the body for the duration of the closure you pass into them, allowing you to access the body by&-reference instead of through a guard.read()andwrite()are identical toborrow()andborrow_mut()respectively--except instead of panicking in the case of poisoning, they return the poison error itself.
Note:read()andwrite()do still panic if Rust's "no-mutable-aliases" rule is broken. They only make poison errors recoverable.
If another thread is currently borrowing the body, these functions will block (wait) until the other thread is finished borrowing it. If you try to borrow a body while it is already borrowed by the same thread, the program may panic.
let body_a = body.borrow_mut();
let body_b = body.borrow_mut(); // `body` is already borrowed in `body_a`See std::rc::Rc and std::sync::Arc for more information about this behavior.
-
Strong: Use a strong reference if the body should not end during the lifetime of the reference. Note that this does not enforce such behavior: holding onto aStrongreference after callingdestroy_physics_bodyon the body it refers to does not keep it inPhysac, it only makes it so that you don't need to callupgrade()on it, and allows the body's information to stay alive after it has been removed from the simulation.
Calldowngrade()on aStrongreference to get aWeakreference to the same object. -
Weak: Use a weak reference if it is possible for the body to be destroyed while the reference exists, and you want your code to be conditional on whether that has happened.
Callupgrade()on aWeakreference to get aStrongreference to the same object, then let theStrongreference go out of scope when you are done accessing it.
Seestd::rc::Weakandstd::sync::Weakfor more information about this behavior.
Important: Remember not to borrow physics bodies, nor Physac, across multiple frames. Storing Strong and Weak references across frames is fine, just not Read/WriteGuards.
When the physics thread needs to borrow a physics body, it will block (wait its turn) until no thread is borrowing it exclusively. The physics thread will mutably borrow every physics body being simulated at multiple points during each physics step.
If you are borrowing a physics body for longer than a frame, there's a high likelihood that the physics thread will borrow another body that you need for rendering the frame, and hold onto it until you give up the one you're borrowing. This can lead to a deadlock, where the main thread can't borrow a particular physics body until physics thread is finished borrowing it and the physics thread can't finish borrowing it until you finish borrowing yours. This can cause the program to freeze and stop responding, because the main thread needs to finish rendering in order for the program to stay responsive.
It is perfectly reasonable to borrow Physac and/or one or multiple PhysicBodys for the full duration of a function--as long as the borrow(s) are given up during the same frame.
You should not store Read/WriteGuards in variables that will persist across multiple frames.
If your program is encountering physics-related bugs, try disabling sync & phys_thread and call run_physics_step() on your main thread. That way, you can put a breakpoint on the main thread and step into the physics function to see where the problem is occurring.
If this seems to magically solve the problem, the bug might have something to do with how the physics thread isn't synchronized with your mutations on physics bodies.
If you have a lot of individual borrows of Physac and/or PhysicsBodyDatas, try borrowing them for the entire duration of whatever operation you are performing and see if that fixes it.
Before
do_thing(&mut *ph.borrow_mut());
// because Physac is only borrowed long enough to do_thing(), the borrow will drop when it's finished
// and the physics thread will be free to update simultaneously with the following lines.
if some_condition(&*body1.borrow()) {
// the physics thread *may have* modified body1 in the split-second since some_condition was tested
modification1(&mut *body1.borrow_mut());
// the physics thread *may have* modified body1 in the split-second since modification1() occurred
modification2(&mut *body1.borrow_mut());
}After
ph.borrowed_mut(|ph| {
// borrowed_mut locks ph, blocking the physics thread, until this closure finishes.
do_thing(&mut *ph);
body1.borrow_mut(|body1| {
// even though the physics thread can't do anything while Physac is borrowed, other threads with access
// to your physics bodies may still try to borrow them. by borrowing them for the entire time you need
// uninterrupted access to them, you can ensure they won't be modified elsewhere.
if some_condition(&*body1) {
modification1(&mut *body1);
modification2(&mut *body1);
}
}); // borrow of body1 drops, so other threads are free to use it again.
}); // borrow of ph drops, so the physics thread is free to update again.After (using borrow_mut() instead of borrowed_mut(), so that you can still return--but be careful not to let borrows overstay their welcome!)
{
let mut ph = ph.borrow_mut();
// note that this does not necessarily guarantee ph is still borrowed after this line like borrowed_mut() would
do_thing(&mut *ph);
{
let mut body1 = body1.borrow_mut();
if some_condition(&*body1) {
modification1(&mut *body1);
modification2(&mut *body1);
}
}
}