#![allow(dead_code)]
use std::ffi::{c_char, CStr};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError};
use oxijolt_sys::*;
pub const OL_NON_MOVING: JPH_ObjectLayer = 0;
pub const OL_MOVING: JPH_ObjectLayer = 1;
const OBJECT_LAYER_COUNT: u32 = 2;
pub const BPL_NON_MOVING: JPH_BroadPhaseLayer = 0;
pub const BPL_MOVING: JPH_BroadPhaseLayer = 1;
const BROAD_PHASE_LAYER_COUNT: u32 = 2;
unsafe extern "C" fn abort_on_assert(
expression: *const c_char,
message: *const c_char,
file: *const c_char,
line: u32,
) -> bool {
let _ = catch_unwind(AssertUnwindSafe(|| {
let text = |text: *const c_char| {
(!text.is_null()).then(|| unsafe { CStr::from_ptr(text) }.to_string_lossy())
};
let (expression, message, file) = (text(expression), text(message), text(file));
eprintln!(
"Jolt assertion failed: {}:{line}: ({}) {}",
file.unwrap_or_default(),
expression.unwrap_or_default(),
message.unwrap_or_default()
);
}));
std::process::abort()
}
pub fn init() {
static INIT: OnceLock<()> = OnceLock::new();
INIT.get_or_init(|| {
let initialized = unsafe {
JPH_SetAssertFailureHandler(Some(abort_on_assert));
JPH_Init()
};
assert!(initialized, "JPH_Init failed");
});
}
fn world_lock() -> MutexGuard<'static, ()> {
static WORLD: Mutex<()> = Mutex::new(());
WORLD.lock().unwrap_or_else(PoisonError::into_inner)
}
fn create_system() -> *mut JPH_PhysicsSystem {
let system = unsafe {
let pair_filter = JPH_ObjectLayerPairFilterTable_Create(OBJECT_LAYER_COUNT);
JPH_ObjectLayerPairFilterTable_EnableCollision(pair_filter, OL_NON_MOVING, OL_MOVING);
JPH_ObjectLayerPairFilterTable_EnableCollision(pair_filter, OL_MOVING, OL_MOVING);
let broad_phase =
JPH_BroadPhaseLayerInterfaceTable_Create(OBJECT_LAYER_COUNT, BROAD_PHASE_LAYER_COUNT);
JPH_BroadPhaseLayerInterfaceTable_MapObjectToBroadPhaseLayer(
broad_phase,
OL_NON_MOVING,
BPL_NON_MOVING,
);
JPH_BroadPhaseLayerInterfaceTable_MapObjectToBroadPhaseLayer(
broad_phase,
OL_MOVING,
BPL_MOVING,
);
let object_vs_broad_phase = JPH_ObjectVsBroadPhaseLayerFilterTable_Create(
broad_phase,
BROAD_PHASE_LAYER_COUNT,
pair_filter,
OBJECT_LAYER_COUNT,
);
let settings = JPH_PhysicsSystemSettings {
maxBodies: 1024,
maxBodyPairs: 1024,
maxContactConstraints: 1024,
broadPhaseLayerInterface: broad_phase,
objectLayerPairFilter: pair_filter,
objectVsBroadPhaseLayerFilter: object_vs_broad_phase,
..std::mem::zeroed()
};
JPH_PhysicsSystem_Create(&settings)
};
assert!(!system.is_null(), "JPH_PhysicsSystem_Create failed");
system
}
pub struct TestWorld {
system: *mut JPH_PhysicsSystem,
job_system: *mut JPH_JobSystem,
temp_allocator: *mut JPH_TempAllocator,
_guard: Option<MutexGuard<'static, ()>>,
}
impl TestWorld {
pub fn new(worker_threads: i32) -> Self {
assert!(worker_threads > 0, "joltc maps 0 or less to automatic");
init();
let guard = world_lock();
let system = create_system();
let pool_config = JobSystemThreadPoolConfig {
maxJobs: 0,
maxBarriers: 0,
numThreads: worker_threads,
};
let job_system = unsafe { JPH_JobSystemThreadPool_Create(&pool_config) };
Self::assemble(system, job_system, Some(guard))
}
pub unsafe fn with_job_system(job_system: *mut JPH_JobSystem) -> Self {
assert!(!job_system.is_null(), "job system creation failed");
init();
let guard = world_lock();
let system = create_system();
Self::assemble(system, job_system, Some(guard))
}
fn assemble(
system: *mut JPH_PhysicsSystem,
job_system: *mut JPH_JobSystem,
guard: Option<MutexGuard<'static, ()>>,
) -> Self {
let temp_allocator = unsafe { JPH_TempAllocator_Create(10 * 1024 * 1024) };
TestWorld {
system,
job_system,
temp_allocator,
_guard: guard,
}
}
pub fn step(&self, dt: f32) {
let result = unsafe {
JPH_PhysicsSystem_Update2(self.system, dt, 1, self.temp_allocator, self.job_system)
};
assert_eq!(result, JPH_PhysicsUpdateError_None);
}
pub fn system(&self) -> *mut JPH_PhysicsSystem {
self.system
}
pub fn body_interface(&self) -> *mut JPH_BodyInterface {
unsafe { JPH_PhysicsSystem_GetBodyInterface(self.system) }
}
}
pub struct TestWorldPair {
pub first: TestWorld,
pub second: TestWorld,
_guard: MutexGuard<'static, ()>,
}
impl TestWorldPair {
pub fn new() -> Self {
init();
let guard = world_lock();
let world = || {
let system = create_system();
let pool_config = JobSystemThreadPoolConfig {
maxJobs: 0,
maxBarriers: 0,
numThreads: 1,
};
let job_system = unsafe { JPH_JobSystemThreadPool_Create(&pool_config) };
TestWorld::assemble(system, job_system, None)
};
let (first, second) = (world(), world());
Self {
first,
second,
_guard: guard,
}
}
}
impl Drop for TestWorld {
fn drop(&mut self) {
unsafe {
JPH_PhysicsSystem_Destroy(self.system);
JPH_JobSystem_Destroy(self.job_system);
JPH_TempAllocator_Destroy(self.temp_allocator);
}
}
}
pub fn vec3(x: f32, y: f32, z: f32) -> JPH_Vec3 {
JPH_Vec3 { x, y, z }
}
pub fn rvec3(x: Real, y: Real, z: Real) -> JPH_RVec3 {
JPH_RVec3 { x, y, z }
}
pub fn quat_identity() -> JPH_Quat {
JPH_Quat {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
}
}
pub fn create_box(
body_interface: *mut JPH_BodyInterface,
half_extent: JPH_Vec3,
position: JPH_RVec3,
motion_type: JPH_MotionType,
layer: JPH_ObjectLayer,
activation: JPH_Activation,
) -> JPH_BodyID {
let rotation = quat_identity();
let body = unsafe {
let shape = JPH_BoxShape_Create(&half_extent, JPH_DEFAULT_CONVEX_RADIUS as f32);
let settings = JPH_BodyCreationSettings_Create3(
shape as *const JPH_Shape,
&position,
&rotation,
motion_type,
layer,
);
let body = JPH_BodyInterface_CreateAndAddBody(body_interface, settings, activation);
JPH_BodyCreationSettings_Destroy(settings);
JPH_Shape_Destroy(shape as *mut JPH_Shape);
body
};
assert_ne!(body, u32::MAX, "body creation failed");
body
}