#![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");
}
let (key, found) = match unsafe { pairs_call(xyz, n, simbox, cutoff, mask, cell_hint, half) } {
Ok(v) => v,
Err(code) => return code,
};
let rows = found.rows();
unsafe {
*out_count = rows;
}
let query = out_i.is_null() && out_j.is_null() && out_shift.is_null() && out_d2.is_null();
if query {
park(&key, found);
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 < rows {
park(&key, found);
return fail_msg("pair buffer is shorter than the pair count");
}
match rows.checked_mul(3) {
Some(v) if v <= isize::MAX as usize => {}
_ => return fail(Error::Overflow),
}
let cols = ColumnOut(out_i, out_j, out_shift, out_d2);
crate::pairs::in_pool(found.rows(), move || {
let (i, j, s, d) = cols.get();
unsafe { found.write_columns(i, j, s, d) }
});
clear_error();
0
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct lc_pair {
pub i: c_int,
pub j: c_int,
pub shift: [c_int; 3],
pub dist2: f64,
}
#[no_mangle]
pub unsafe extern "C" fn lc_pairs_within_rows(
xyz: *const f64,
n: usize,
simbox: *const lc_cell,
cutoff: f64,
mask: *const c_int,
cell_hint: f64,
half: c_int,
out: *mut lc_pair,
cap: usize,
out_count: *mut usize,
) -> c_int {
if out_count.is_null() {
return fail_msg("null pointer");
}
let (key, found) = match unsafe { pairs_call(xyz, n, simbox, cutoff, mask, cell_hint, half) } {
Ok(v) => v,
Err(code) => return code,
};
let rows = found.rows();
unsafe {
*out_count = rows;
}
if out.is_null() {
park(&key, found);
clear_error();
return 0;
}
if cap < rows {
park(&key, found);
return fail_msg("pair buffer is shorter than the pair count");
}
let rows_out = RowOut(out);
crate::pairs::in_pool(found.rows(), move || {
let out = rows_out.get();
unsafe { found.write_rows(out, |i, j, shift, dist2| lc_pair { i, j, shift, dist2 }) }
});
clear_error();
0
}
unsafe fn pairs_call<'a>(
xyz: *const f64,
n: usize,
simbox: *const lc_cell,
cutoff: f64,
mask: *const c_int,
cell_hint: f64,
half: c_int,
) -> Result<(PairKey<'a>, crate::pairs::Found), c_int> {
if n == 0 {
return Err(fail(Error::Empty));
}
if xyz.is_null() || simbox.is_null() {
return Err(fail_msg("null pointer"));
}
if n > c_int::MAX as usize || n > (isize::MAX as usize) / 3 {
return Err(fail(Error::Overflow));
}
let box_c = unsafe { *simbox };
let sim = 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],
)
.map_err(|e| {
set_error(&e.to_string());
1
})?;
let pts: &'a [[f64; 3]] = unsafe { std::slice::from_raw_parts(xyz.cast::<[f64; 3]>(), n) };
let mask_raw: Option<&'a [c_int]> = if mask.is_null() {
None
} else {
Some(unsafe { std::slice::from_raw_parts(mask, n) })
};
let hint = if cell_hint > 0.0 {
Some(cell_hint)
} else {
None
};
let key = PairKey {
xyz: pts,
simbox: box_c,
cutoff,
mask: mask_raw,
cell_hint: hint,
half: half != 0,
};
if let Some(found) = take_parked(&key) {
return Ok((key, found));
}
let mask_vec: Option<Vec<bool>> = mask_raw.map(|m| m.iter().map(|&v| v != 0).collect());
let found = crate::pairs::in_pool(crate::pairs::hit_estimate(n, &sim, cutoff), || {
crate::pairs::search(pts, &sim, cutoff, mask_vec.as_deref(), hint, half != 0)
});
match found {
Ok(found) => Ok((key, found)),
Err(e) => Err(fail(e)),
}
}
#[derive(Clone, Copy)]
struct ColumnOut(*mut c_int, *mut c_int, *mut c_int, *mut f64);
unsafe impl Send for ColumnOut {}
impl ColumnOut {
fn get(self) -> (*mut c_int, *mut c_int, *mut c_int, *mut f64) {
(self.0, self.1, self.2, self.3)
}
}
#[derive(Clone, Copy)]
struct RowOut(*mut lc_pair);
unsafe impl Send for RowOut {}
impl RowOut {
fn get(self) -> *mut lc_pair {
self.0
}
}
struct PairKey<'a> {
xyz: &'a [[f64; 3]],
simbox: lc_cell,
cutoff: f64,
mask: Option<&'a [c_int]>,
cell_hint: Option<f64>,
half: bool,
}
struct ParkedPairs {
xyz: Vec<[f64; 3]>,
simbox: [u64; 12],
cutoff: u64,
mask: Option<Vec<bool>>,
cell_hint: Option<u64>,
half: bool,
found: crate::pairs::Found,
}
thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static PARKED_PAIRS: RefCell<Option<ParkedPairs>> = RefCell::new(None);
}
fn box_bits(c: &lc_cell) -> [u64; 12] {
[
c.ax.to_bits(),
c.ay.to_bits(),
c.az.to_bits(),
c.bx.to_bits(),
c.by.to_bits(),
c.bz.to_bits(),
c.cx.to_bits(),
c.cy.to_bits(),
c.cz.to_bits(),
c.ox.to_bits(),
c.oy.to_bits(),
c.oz.to_bits(),
]
}
fn same_bits(a: &[[f64; 3]], b: &[[f64; 3]]) -> bool {
a.len() == b.len()
&& a.iter().zip(b).all(|(p, q)| {
p[0].to_bits() == q[0].to_bits()
&& p[1].to_bits() == q[1].to_bits()
&& p[2].to_bits() == q[2].to_bits()
})
}
fn take_parked(key: &PairKey<'_>) -> Option<crate::pairs::Found> {
let parked = PARKED_PAIRS.with(|slot| slot.borrow_mut().take())?;
let mask_same = match (&parked.mask, key.mask) {
(None, None) => true,
(Some(a), Some(b)) => a.len() == b.len() && a.iter().zip(b).all(|(&x, &y)| x == (y != 0)),
_ => false,
};
let same = parked.half == key.half
&& parked.cutoff == key.cutoff.to_bits()
&& parked.cell_hint == key.cell_hint.map(f64::to_bits)
&& parked.simbox == box_bits(&key.simbox)
&& mask_same
&& same_bits(&parked.xyz, key.xyz);
if same {
Some(parked.found)
} else {
None
}
}
fn park(key: &PairKey<'_>, found: crate::pairs::Found) {
let parked = ParkedPairs {
xyz: key.xyz.to_vec(),
simbox: box_bits(&key.simbox),
cutoff: key.cutoff.to_bits(),
mask: key.mask.map(|m| m.iter().map(|&v| v != 0).collect()),
cell_hint: key.cell_hint.map(f64::to_bits),
half: key.half,
found,
};
PARKED_PAIRS.with(|slot| *slot.borrow_mut() = Some(parked));
}