#![deny(unsafe_op_in_unsafe_fn)]
use std::cell::RefCell;
use std::ffi::{c_char, c_int, CString};
use std::ptr;
use crate::{Cell, Error};
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct lc_cell {
pub ax: f64,
pub ay: f64,
pub az: f64,
pub bx: f64,
pub by: f64,
pub bz: f64,
pub cx: f64,
pub cy: f64,
pub cz: f64,
pub ox: f64,
pub oy: f64,
pub oz: f64,
}
thread_local! {
static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
}
fn set_error(msg: &str) {
LAST_ERROR.with(|slot| {
let cstr = CString::new(msg).unwrap_or_else(|_| {
CString::new("error message contained NUL").expect("fallback has no NUL")
});
*slot.borrow_mut() = Some(cstr);
});
}
fn clear_error() {
LAST_ERROR.with(|slot| {
*slot.borrow_mut() = None;
});
}
fn fail(err: Error) -> c_int {
set_error(&err.to_string());
1
}
fn fail_msg(msg: &str) -> c_int {
set_error(msg);
1
}
#[no_mangle]
pub extern "C" fn lc_last_error() -> *const c_char {
LAST_ERROR.with(|slot| {
slot.borrow()
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null())
})
}
#[no_mangle]
pub extern "C" fn lc_version() -> *const c_char {
concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
}
#[no_mangle]
pub unsafe extern "C" fn lc_knearest(
xyz: *const f64,
n: usize,
simbox: *const lc_cell,
k: usize,
mask: *const c_int,
cell_hint: f64,
out_nn: *mut c_int,
) -> c_int {
unsafe {
lc_knearest_many(
xyz,
n,
1,
simbox,
k,
mask,
cell_hint,
out_nn,
ptr::null_mut(),
)
}
}
#[no_mangle]
pub unsafe extern "C" fn lc_knearest_d2(
xyz: *const f64,
n: usize,
simbox: *const lc_cell,
k: usize,
mask: *const c_int,
cell_hint: f64,
out_nn: *mut c_int,
out_d2: *mut f64,
) -> c_int {
unsafe { lc_knearest_many(xyz, n, 1, simbox, k, mask, cell_hint, out_nn, out_d2) }
}
#[no_mangle]
pub unsafe extern "C" fn lc_knearest_many(
xyz: *const f64,
n: usize,
n_frames: usize,
simbox: *const lc_cell,
k: usize,
mask: *const c_int,
cell_hint: f64,
out_nn: *mut c_int,
out_d2: *mut f64,
) -> c_int {
if n == 0 || n_frames == 0 {
return fail(Error::Empty);
}
if k == 0 {
return fail(Error::ZeroK);
}
if xyz.is_null() || simbox.is_null() || out_nn.is_null() {
return fail_msg("null pointer");
}
let Some(n_pts) = n.checked_mul(n_frames) else {
return fail(Error::Overflow);
};
let Some(need) = n_pts.checked_mul(k) else {
return fail(Error::Overflow);
};
let max_xyz = (isize::MAX as usize) / 3;
let max_out = (isize::MAX as usize) / std::mem::size_of::<c_int>();
if n_pts > max_xyz || need > max_out {
return fail(Error::Overflow);
}
let box_c = unsafe { *simbox };
let sim = match Cell::from_vectors(
[box_c.ax, box_c.ay, box_c.az],
[box_c.bx, box_c.by, box_c.bz],
[box_c.cx, box_c.cy, box_c.cz],
[box_c.ox, box_c.oy, box_c.oz],
) {
Ok(b) => b,
Err(e) => {
set_error(&e.to_string());
return 1;
}
};
let pts: &[[f64; 3]] = unsafe { std::slice::from_raw_parts(xyz.cast::<[f64; 3]>(), n_pts) };
let mask_vec: Option<Vec<bool>> = if mask.is_null() {
None
} else {
let raw = unsafe { std::slice::from_raw_parts(mask, n) };
Some(raw.iter().map(|&v| v != 0).collect())
};
let hint = if cell_hint > 0.0 {
Some(cell_hint)
} else {
None
};
let nn = unsafe { std::slice::from_raw_parts_mut(out_nn, need) };
let d2_store;
let d2 = if out_d2.is_null() {
None
} else {
d2_store = unsafe { std::slice::from_raw_parts_mut(out_d2, need) };
Some(&mut d2_store[..])
};
let err = if n_frames == 1 {
crate::knearest_into_d2(pts, &sim, k, mask_vec.as_deref(), hint, nn, d2)
} else {
crate::knearest_into_many(pts, n, n_frames, &sim, k, mask_vec.as_deref(), hint, nn, d2)
};
if let Err(e) = err {
set_error(&e.to_string());
return 1;
}
clear_error();
0
}
#[no_mangle]
pub unsafe extern "C" fn lc_pairs_within(
xyz: *const f64,
n: usize,
simbox: *const lc_cell,
cutoff: f64,
mask: *const c_int,
cell_hint: f64,
half: c_int,
out_i: *mut c_int,
out_j: *mut c_int,
out_shift: *mut c_int,
out_d2: *mut f64,
cap: usize,
out_count: *mut usize,
) -> c_int {
if out_count.is_null() {
return fail_msg("null pointer");
}
if n == 0 {
return fail(Error::Empty);
}
if xyz.is_null() || simbox.is_null() {
return fail_msg("null pointer");
}
if n > c_int::MAX as usize {
return fail(Error::Overflow);
}
let max_xyz = (isize::MAX as usize) / 3;
if n > max_xyz {
return fail(Error::Overflow);
}
let box_c = unsafe { *simbox };
let sim = match Cell::from_vectors(
[box_c.ax, box_c.ay, box_c.az],
[box_c.bx, box_c.by, box_c.bz],
[box_c.cx, box_c.cy, box_c.cz],
[box_c.ox, box_c.oy, box_c.oz],
) {
Ok(b) => b,
Err(e) => {
set_error(&e.to_string());
return 1;
}
};
let pts: &[[f64; 3]] = unsafe { std::slice::from_raw_parts(xyz.cast::<[f64; 3]>(), n) };
let mask_vec: Option<Vec<bool>> = if mask.is_null() {
None
} else {
let raw = unsafe { std::slice::from_raw_parts(mask, n) };
Some(raw.iter().map(|&v| v != 0).collect())
};
let hint = if cell_hint > 0.0 {
Some(cell_hint)
} else {
None
};
let pairs = match crate::pairs_within(pts, &sim, cutoff, mask_vec.as_deref(), hint, half != 0) {
Ok(p) => p,
Err(e) => return fail(e),
};
unsafe {
*out_count = pairs.len();
}
let query = out_i.is_null() && out_j.is_null() && out_shift.is_null() && out_d2.is_null();
if query {
clear_error();
return 0;
}
if out_i.is_null() || out_j.is_null() || out_shift.is_null() || out_d2.is_null() {
return fail_msg("null pointer");
}
if cap < pairs.len() {
return fail_msg("pair buffer is shorter than the pair count");
}
let shift_len = match pairs.len().checked_mul(3) {
Some(v) if v <= isize::MAX as usize => v,
_ => return fail(Error::Overflow),
};
let ii = unsafe { std::slice::from_raw_parts_mut(out_i, pairs.len()) };
let jj = unsafe { std::slice::from_raw_parts_mut(out_j, pairs.len()) };
let ss = unsafe { std::slice::from_raw_parts_mut(out_shift, shift_len) };
let dd = unsafe { std::slice::from_raw_parts_mut(out_d2, pairs.len()) };
for (t, pair) in pairs.iter().enumerate() {
ii[t] = pair.i as c_int;
jj[t] = pair.j as c_int;
ss[3 * t] = pair.shift[0];
ss[3 * t + 1] = pair.shift[1];
ss[3 * t + 2] = pair.shift[2];
dd[t] = pair.dist2;
}
clear_error();
0
}