boxddd 0.4.0

Safe, ergonomic Rust bindings for Box3D
Documentation
use boxddd::error::{HandleKind, InvalidValueReason};
use boxddd::{
    Aabb, BodyDef, BodyDefBuilder, BodyId, BodyType, ContactId, DynamicTree, DynamicTreeProxyId,
    Error, JointId, RecPlayer, Recording, ReplayWorldId, ShapeDef, ShapeDefBuilder, ShapeId,
    Sphere, Vec3, Version, World, WorldDef, WorldDefBuilder,
};
use static_assertions::{assert_impl_all, assert_not_impl_any};

fn foundation() -> &'static boxddd::Foundation {
    boxddd::Foundation::initialize_default().unwrap()
}

assert_not_impl_any!(World: Send, Sync);
assert_not_impl_any!(DynamicTree: Send, Sync);
assert_not_impl_any!(Recording: Send, Sync);
assert_not_impl_any!(RecPlayer: Send, Sync);
assert_not_impl_any!(BodyDef: Default);
assert_not_impl_any!(BodyDefBuilder: Default);
assert_not_impl_any!(ShapeDef: Default);
assert_not_impl_any!(ShapeDefBuilder: Default);
assert_not_impl_any!(WorldDef: Default);
assert_not_impl_any!(WorldDefBuilder: Default);
assert_impl_all!(BodyId: Copy, Eq, std::hash::Hash, Send, Sync);
assert_impl_all!(ShapeId: Copy, Eq, std::hash::Hash, Send, Sync);
assert_impl_all!(JointId: Copy, Eq, std::hash::Hash, Send, Sync);
assert_impl_all!(ContactId: Copy, Eq, std::hash::Hash, Send, Sync);
assert_impl_all!(DynamicTreeProxyId: Copy, Eq, std::hash::Hash, Send, Sync);
assert_impl_all!(ReplayWorldId: Copy, Eq, std::hash::Hash, Send, Sync);

#[test]
fn manual_break_inventory_matches_public_result_signatures() {
    let _: fn() -> boxddd::Result<Version> = boxddd::version;
    let _: fn() -> boxddd::Result<Version> = boxddd::prelude::version;
    let _: fn() -> boxddd::Result<Version> = boxddd::world::version;
    let _: fn(Aabb) -> boxddd::Result<bool> = Aabb::is_bounded;
    let _: fn(Aabb) -> boxddd::Result<bool> = Aabb::is_sane;
    let _: fn(&World, ContactId) -> boxddd::Result<bool> = World::contains_contact;
}

#[test]
fn invalid_world_definition_returns_error() {
    let foundation = foundation();
    assert_eq!(
        foundation
            .world_def_builder()
            .gravity([f32::NAN, 0.0, 0.0])
            .build()
            .unwrap_err(),
        Error::InvalidValue {
            context: "world.gravity",
            reason: InvalidValueReason::NonFinite,
        }
    );
}

#[test]
fn callback_guard_blocks_result_first_apis() {
    let foundation = foundation();
    let mut world = foundation.create_world(foundation.world_def()).unwrap();
    let _guard = boxddd::__private::enter_callback_guard_for_test();

    assert_eq!(
        world.set_gravity(Vec3::ZERO).unwrap_err(),
        Error::InCallback
    );
    assert_eq!(world.counters().unwrap_err(), Error::InCallback);
}

#[test]
fn stale_body_id_returns_result_first_error() {
    let foundation = foundation();
    let mut world = foundation.create_world(foundation.world_def()).unwrap();
    let body = world
        .create_body(
            foundation
                .body_def_builder()
                .body_type(BodyType::Dynamic)
                .position([0.0, 2.0, 0.0])
                .build()
                .unwrap(),
        )
        .unwrap();
    assert_eq!(world.contains_body(body), Ok(true));

    world.destroy_body(body).unwrap();
    assert_eq!(world.contains_body(body), Ok(false));

    assert_eq!(
        world.body_position(body).unwrap_err(),
        Error::StaleHandle {
            kind: HandleKind::Body,
        }
    );
}

#[test]
fn invalid_create_inputs_return_typed_errors() {
    let foundation = foundation();
    let mut world = foundation.create_world(foundation.world_def()).unwrap();

    assert_eq!(
        foundation
            .body_def_builder()
            .position([f32::INFINITY, 0.0, 0.0])
            .build()
            .unwrap_err(),
        Error::InvalidValue {
            context: "body.position",
            reason: InvalidValueReason::NonFinite,
        }
    );

    let body = world.create_body(foundation.body_def()).unwrap();
    let shape_def = foundation.shape_def();
    let bad_sphere = Sphere::new([0.0, 0.0, 0.0], f32::NAN);
    assert_eq!(
        world.create_sphere_shape(body, &shape_def, &bad_sphere),
        Err(Error::InvalidValue {
            context: "sphere.radius",
            reason: InvalidValueReason::NonFinite,
        })
    );
}