boxddd 0.2.0

Safe, ergonomic Rust bindings for Box3D
Documentation
#![cfg_attr(all(target_arch = "wasm32", boxddd_wasm_provider), allow(dead_code))]

use crate::callbacks::{MaterialMixContext, MaterialMixInput};
use boxddd_sys::ffi;
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};

pub(crate) const MATERIAL_MIX_SLOT_COUNT: usize = 16;

struct MaterialMixSlot {
    in_use: AtomicBool,
    friction: AtomicPtr<MaterialMixContext>,
    restitution: AtomicPtr<MaterialMixContext>,
}

impl MaterialMixSlot {
    const fn new() -> Self {
        Self {
            in_use: AtomicBool::new(false),
            friction: AtomicPtr::new(core::ptr::null_mut()),
            restitution: AtomicPtr::new(core::ptr::null_mut()),
        }
    }
}

static MATERIAL_MIX_SLOTS: [MaterialMixSlot; MATERIAL_MIX_SLOT_COUNT] =
    [const { MaterialMixSlot::new() }; MATERIAL_MIX_SLOT_COUNT];

#[inline]
fn slot_ref(slot: usize) -> &'static MaterialMixSlot {
    &MATERIAL_MIX_SLOTS[slot]
}

pub(crate) fn acquire_slot() -> Option<usize> {
    MATERIAL_MIX_SLOTS
        .iter()
        .enumerate()
        .find_map(|(idx, slot)| {
            slot.in_use
                .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
                .ok()
                .map(|_| idx)
        })
}

pub(crate) fn release_slot(slot: usize) {
    debug_assert!(slot_ref(slot).friction.load(Ordering::Acquire).is_null());
    debug_assert!(slot_ref(slot).restitution.load(Ordering::Acquire).is_null());
    slot_ref(slot).in_use.store(false, Ordering::Release);
}

#[inline]
pub(crate) fn set_friction_ptr(slot: usize, ptr: *mut MaterialMixContext) {
    slot_ref(slot).friction.store(ptr, Ordering::Release);
}

#[inline]
pub(crate) fn set_restitution_ptr(slot: usize, ptr: *mut MaterialMixContext) {
    slot_ref(slot).restitution.store(ptr, Ordering::Release);
}

#[inline]
pub(crate) fn has_any_callback(slot: usize) -> bool {
    let slot = slot_ref(slot);
    !slot.friction.load(Ordering::Acquire).is_null()
        || !slot.restitution.load(Ordering::Acquire).is_null()
}

#[inline]
fn default_friction_mix(friction_a: f32, friction_b: f32) -> f32 {
    (friction_a * friction_b).sqrt()
}

#[inline]
fn default_restitution_mix(restitution_a: f32, restitution_b: f32) -> f32 {
    restitution_a.max(restitution_b)
}

unsafe fn invoke_mix_callback(
    ctx_ptr: *mut MaterialMixContext,
    value_a: f32,
    user_material_id_a: u64,
    value_b: f32,
    user_material_id_b: u64,
    default_mix: fn(f32, f32) -> f32,
) -> f32 {
    if ctx_ptr.is_null() {
        return default_mix(value_a, value_b);
    }

    let ctx = unsafe { &*ctx_ptr };
    if ctx.panicked.load(Ordering::Relaxed) {
        return default_mix(value_a, value_b);
    }

    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        let _guard = crate::core::callback_state::CallbackGuard::enter();
        (ctx.callback)(
            MaterialMixInput::new(value_a, user_material_id_a),
            MaterialMixInput::new(value_b, user_material_id_b),
        )
    })) {
        Ok(value) if value.is_finite() => value,
        Ok(_) => default_mix(value_a, value_b),
        Err(_) => {
            ctx.panicked.store(true, Ordering::SeqCst);
            default_mix(value_a, value_b)
        }
    }
}

unsafe fn invoke_friction_callback(
    slot: usize,
    friction_a: f32,
    user_material_id_a: u64,
    friction_b: f32,
    user_material_id_b: u64,
) -> f32 {
    let ctx_ptr = slot_ref(slot).friction.load(Ordering::Acquire);
    unsafe {
        invoke_mix_callback(
            ctx_ptr,
            friction_a,
            user_material_id_a,
            friction_b,
            user_material_id_b,
            default_friction_mix,
        )
    }
}

unsafe fn invoke_restitution_callback(
    slot: usize,
    restitution_a: f32,
    user_material_id_a: u64,
    restitution_b: f32,
    user_material_id_b: u64,
) -> f32 {
    let ctx_ptr = slot_ref(slot).restitution.load(Ordering::Acquire);
    unsafe {
        invoke_mix_callback(
            ctx_ptr,
            restitution_a,
            user_material_id_a,
            restitution_b,
            user_material_id_b,
            default_restitution_mix,
        )
    }
}

type MaterialMixTrampoline = unsafe extern "C" fn(f32, u64, f32, u64) -> f32;

unsafe extern "C" fn friction_trampoline<const SLOT: usize>(
    friction_a: f32,
    user_material_id_a: u64,
    friction_b: f32,
    user_material_id_b: u64,
) -> f32 {
    unsafe {
        invoke_friction_callback(
            SLOT,
            friction_a,
            user_material_id_a,
            friction_b,
            user_material_id_b,
        )
    }
}

unsafe extern "C" fn restitution_trampoline<const SLOT: usize>(
    restitution_a: f32,
    user_material_id_a: u64,
    restitution_b: f32,
    user_material_id_b: u64,
) -> f32 {
    unsafe {
        invoke_restitution_callback(
            SLOT,
            restitution_a,
            user_material_id_a,
            restitution_b,
            user_material_id_b,
        )
    }
}

static FRICTION_TRAMPOLINES: [MaterialMixTrampoline; MATERIAL_MIX_SLOT_COUNT] = [
    friction_trampoline::<0>,
    friction_trampoline::<1>,
    friction_trampoline::<2>,
    friction_trampoline::<3>,
    friction_trampoline::<4>,
    friction_trampoline::<5>,
    friction_trampoline::<6>,
    friction_trampoline::<7>,
    friction_trampoline::<8>,
    friction_trampoline::<9>,
    friction_trampoline::<10>,
    friction_trampoline::<11>,
    friction_trampoline::<12>,
    friction_trampoline::<13>,
    friction_trampoline::<14>,
    friction_trampoline::<15>,
];

static RESTITUTION_TRAMPOLINES: [MaterialMixTrampoline; MATERIAL_MIX_SLOT_COUNT] = [
    restitution_trampoline::<0>,
    restitution_trampoline::<1>,
    restitution_trampoline::<2>,
    restitution_trampoline::<3>,
    restitution_trampoline::<4>,
    restitution_trampoline::<5>,
    restitution_trampoline::<6>,
    restitution_trampoline::<7>,
    restitution_trampoline::<8>,
    restitution_trampoline::<9>,
    restitution_trampoline::<10>,
    restitution_trampoline::<11>,
    restitution_trampoline::<12>,
    restitution_trampoline::<13>,
    restitution_trampoline::<14>,
    restitution_trampoline::<15>,
];

#[inline]
pub(crate) fn friction_callback(slot: usize) -> ffi::b3FrictionCallback {
    Some(FRICTION_TRAMPOLINES[slot])
}

#[inline]
pub(crate) fn restitution_callback(slot: usize) -> ffi::b3RestitutionCallback {
    Some(RESTITUTION_TRAMPOLINES[slot])
}