use std::ffi::{CStr, c_char};
use std::net::IpAddr;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr;
use std::str::FromStr;
use crate::devicediscovery::{DeviceDiscovery, DeviceInfo};
use crate::marker_pose::MarkerPose;
use crate::posestream::PoseStream;
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EnposeStatus {
Ok = 0,
InvalidArg = -1,
Io = -2,
Panic = -3,
}
const IP_BUF_LEN: usize = 46;
#[repr(C)]
pub struct EnposeDeviceInfo {
pub ip: [c_char; IP_BUF_LEN],
pub serial: u32,
pub compatible: bool,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn enpose_discover(
out_devices: *mut *mut EnposeDeviceInfo,
out_count: *mut usize,
) -> EnposeStatus {
let result = catch_unwind(|| {
if out_devices.is_null() || out_count.is_null() {
return EnposeStatus::InvalidArg;
}
let devices = match DeviceDiscovery::new().discover() {
Ok(d) => d,
Err(_) => return EnposeStatus::Io,
};
let (ptr, count) = leak_array(devices.iter().map(device_to_c).collect());
unsafe {
*out_devices = ptr;
*out_count = count;
}
EnposeStatus::Ok
});
result.unwrap_or(EnposeStatus::Panic)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn enpose_device_info_array_free(
devices: *mut EnposeDeviceInfo,
count: usize,
) {
let _ = catch_unwind(|| unsafe { free_array(devices, count) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn enpose_pose_stream_connect(
ip: *const c_char,
create_thread: bool,
) -> *mut PoseStream {
let result = catch_unwind(|| {
if ip.is_null() {
return ptr::null_mut();
}
let Ok(text) = (unsafe { CStr::from_ptr(ip) }).to_str() else {
return ptr::null_mut();
};
let Ok(addr) = IpAddr::from_str(text) else {
return ptr::null_mut();
};
match PoseStream::from_ip(addr, create_thread) {
Ok(stream) => Box::into_raw(Box::new(stream)),
Err(_) => ptr::null_mut(),
}
});
result.unwrap_or(ptr::null_mut())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn enpose_pose_stream_receive(
stream: *mut PoseStream,
block: bool,
out_poses: *mut *mut MarkerPose,
out_count: *mut usize,
) -> EnposeStatus {
let result = catch_unwind(AssertUnwindSafe(|| {
if stream.is_null() || out_poses.is_null() || out_count.is_null() {
return EnposeStatus::InvalidArg;
}
let stream = unsafe { &mut *stream };
match stream.receive_pose_updates(block) {
Ok(poses) => {
let (ptr, count) = leak_array(poses);
unsafe {
*out_poses = ptr;
*out_count = count;
}
EnposeStatus::Ok
}
Err(_) => EnposeStatus::Io,
}
}));
result.unwrap_or(EnposeStatus::Panic)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn enpose_marker_pose_array_free(poses: *mut MarkerPose, count: usize) {
let _ = catch_unwind(|| unsafe { free_array(poses, count) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn enpose_pose_stream_free(stream: *mut PoseStream) {
if stream.is_null() {
return;
}
let _ = catch_unwind(AssertUnwindSafe(|| drop(unsafe { Box::from_raw(stream) })));
}
fn device_to_c(info: &DeviceInfo) -> EnposeDeviceInfo {
let mut ip = [0 as c_char; IP_BUF_LEN];
let text = info.ip.to_string();
let bytes = text.as_bytes();
let n = bytes.len().min(IP_BUF_LEN - 1);
for (slot, &byte) in ip.iter_mut().zip(&bytes[..n]) {
*slot = byte as c_char;
}
EnposeDeviceInfo {
ip,
serial: info.serial,
compatible: info.compatible,
}
}
fn leak_array<T>(items: Vec<T>) -> (*mut T, usize) {
if items.is_empty() {
return (ptr::null_mut(), 0);
}
let boxed = items.into_boxed_slice();
let count = boxed.len();
let ptr = boxed.as_ptr() as *mut T;
std::mem::forget(boxed);
(ptr, count)
}
unsafe fn free_array<T>(ptr: *mut T, count: usize) {
if ptr.is_null() || count == 0 {
return;
}
unsafe {
drop(Box::from_raw(std::slice::from_raw_parts_mut(ptr, count)));
}
}
#[cfg(test)]
#[path = "ffi_tests.rs"]
mod tests;