use crate::{error::ComponentInitError, RegisterResources, Resources};
use linuxcnc_hal_sys::{hal_exit, hal_init, hal_ready, EINVAL, ENOMEM, HAL_NAME_LEN};
use signal_hook::iterator::Signals;
use std::{cell::RefCell, ffi::CString};
#[derive(Debug)]
pub struct HalComponent<R> {
name: &'static str,
id: i32,
signals: RefCell<Signals>,
resources: Option<R>,
}
impl<R> HalComponent<R>
where
R: Resources,
{
pub fn new(name: &'static str) -> Result<Self, ComponentInitError> {
let id = Self::create_component(name)?;
let resources = R::register_resources(&RegisterResources { id, name })
.map_err(|e| ComponentInitError::ResourceRegistration(e.into()))?;
let signals = Self::register_signals()?;
let comp = Self {
name,
id,
resources: Some(resources),
signals: RefCell::new(signals),
};
comp.ready()
}
fn register_signals() -> Result<Signals, ComponentInitError> {
let signals = Signals::new(&[signal_hook::consts::SIGTERM, signal_hook::consts::SIGINT])
.map_err(ComponentInitError::Signals)?;
debug!("Signals registered");
Ok(signals)
}
fn create_component(name: &'static str) -> Result<i32, ComponentInitError> {
if name.len() > HAL_NAME_LEN as usize {
error!(
"Component name must be no longer than {} bytes",
HAL_NAME_LEN
);
Err(ComponentInitError::NameLength)
} else {
let name_c = CString::new(name).map_err(|_| ComponentInitError::InvalidName)?;
let id = unsafe { hal_init(name_c.as_ptr().cast()) };
match id {
x if x == -(EINVAL as i32) => Err(ComponentInitError::Init),
x if x == -(ENOMEM as i32) => Err(ComponentInitError::Memory),
id if id > 0 => {
debug!("Init component {} with ID {}", name, id);
Ok(id)
}
code => unreachable!("Hit unreachable error code {}", code),
}
}
}
fn ready(self) -> Result<Self, ComponentInitError> {
let ret = unsafe { hal_ready(self.id) };
match ret {
x if x == -(EINVAL as i32) => Err(ComponentInitError::Ready),
0 => {
debug!("Component is ready");
Ok(self)
}
ret => unreachable!("Unknown error status {} returned from hal_ready()", ret),
}
}
pub fn id(&self) -> i32 {
self.id
}
pub fn name(&self) -> &str {
self.name
}
pub fn should_exit(&self) -> bool {
self.signals.borrow_mut().pending().any(|signal| {
matches!(
signal,
signal_hook::consts::SIGTERM
| signal_hook::consts::SIGINT
| signal_hook::consts::SIGKILL
)
})
}
pub fn resources(&self) -> &R {
&self.resources.as_ref().unwrap()
}
}
impl<R> Drop for HalComponent<R> {
fn drop(&mut self) {
self.resources = None;
debug!("Closing component ID {}, name {}", self.id, self.name);
unsafe {
hal_exit(self.id);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
error::{ComponentInitError, PinRegisterError},
RegisterResources,
};
#[derive(Debug)]
struct EmptyResources {}
impl Resources for EmptyResources {
type RegisterError = PinRegisterError;
fn register_resources(_comp: &RegisterResources) -> Result<Self, Self::RegisterError> {
Ok(Self {})
}
}
#[test]
fn name_too_long() -> Result<(), ComponentInitError> {
let comp = HalComponent::<EmptyResources>::new(
"name-thats-way-too-long-for-linuxcnc-to-handle-wow-this-is-ridiculous",
);
println!("{:?}", comp);
match comp {
Err(ComponentInitError::NameLength) => Ok(()),
Err(e) => Err(e),
Ok(_) => Err(ComponentInitError::Init),
}
}
}