use crate::backend::{DacBackend, FifoBackend, WriteOutcome};
use crate::buffer_estimate::BufferEstimator;
use crate::device::{DacCapabilities, DacType};
use crate::error::{Error, Result};
use crate::point::LaserPoint;
use super::diagnostics::LaserCubeNetworkDiagnostics;
use super::profiles::ConnectionProfile;
use super::protocol::{Point, DEFAULT_BUFFER_CAPACITY};
use super::transport::{AddressedDevice, SharedTransportState, TransportHandle};
pub struct LaserCubeNetworkBackend {
addressed: AddressedDevice,
transport: Option<TransportHandle>,
caps: DacCapabilities,
point_buffer: Vec<Point>,
fallback_estimator: SharedTransportState,
}
impl LaserCubeNetworkBackend {
pub(crate) fn new(addressed: AddressedDevice) -> Self {
let caps = super::capabilities_for_status(addressed.profile, &addressed.status);
Self {
fallback_estimator: SharedTransportState::disconnected(addressed.profile),
addressed,
transport: None,
caps,
point_buffer: Vec::new(),
}
}
pub fn diagnostics(&self) -> Option<LaserCubeNetworkDiagnostics> {
self.transport.as_ref().map(|t| t.state().diagnostics())
}
}
impl DacBackend for LaserCubeNetworkBackend {
fn dac_type(&self) -> DacType {
DacType::LaserCubeNetwork
}
fn caps(&self) -> &DacCapabilities {
&self.caps
}
fn connect(&mut self) -> Result<()> {
if self.transport.is_some() {
return Ok(());
}
let transport = TransportHandle::connect(self.addressed.clone()).map_err(Error::backend)?;
self.transport = Some(transport);
Ok(())
}
fn disconnect(&mut self) -> Result<()> {
if let Some(mut transport) = self.transport.take() {
transport.shutdown();
}
Ok(())
}
fn is_connected(&self) -> bool {
self.transport.as_ref().is_some_and(|t| t.is_usable())
}
fn stop(&mut self) -> Result<()> {
if let Some(transport) = &self.transport {
transport.stop_output().map_err(Error::backend)?;
}
Ok(())
}
fn set_shutter(&mut self, open: bool) -> Result<()> {
if let Some(transport) = &self.transport {
transport.set_output(open).map_err(Error::backend)?;
}
Ok(())
}
}
impl FifoBackend for LaserCubeNetworkBackend {
fn try_write_points(&mut self, pps: u32, points: &[LaserPoint]) -> Result<WriteOutcome> {
let transport = self
.transport
.as_ref()
.ok_or_else(|| Error::disconnected("LaserCube network backend is not connected"))?;
if !transport.is_usable() {
return Err(Error::disconnected(
"LaserCube network communication timed out",
));
}
self.point_buffer.clear();
self.point_buffer.extend(points.iter().map(Point::from));
match transport.enqueue(pps, self.point_buffer.clone()) {
Ok(()) => Ok(WriteOutcome::Written),
Err(super::error::CommunicationError::QueueFull) => Ok(WriteOutcome::WouldBlock),
Err(err) => Err(Error::backend(err)),
}
}
fn estimator(&self) -> &dyn BufferEstimator {
match &self.transport {
Some(t) => t.state(),
None => &self.fallback_estimator,
}
}
}
impl Default for LaserCubeNetworkBackend {
fn default() -> Self {
let status = super::status::LaserCubeNetworkStatus::minimal(
"0.0.0.0".parse().expect("valid default IP"),
);
let profile = ConnectionProfile::unknown_conservative(DEFAULT_BUFFER_CAPACITY as usize);
Self::new(AddressedDevice {
source_addr: "0.0.0.0:0".parse().expect("valid default socket address"),
status,
profile,
})
}
}