From 6251e60102fe14677aabe61a760808f32a903d0f Mon Sep 17 00:00:00 2001 From: shun suzuki Date: Thu, 9 Jan 2025 11:05:56 +0900 Subject: [PATCH 1/8] rename from `Link` to `AsyncLink` --- autd3-driver/src/{link.rs => link/async.rs} | 17 ++++++++--------- autd3-driver/src/link/mod.rs | 3 +++ autd3-link-simulator/src/lib.rs | 6 +++--- autd3-link-twincat/src/local/mod.rs | 6 +++--- .../src/remote/remote_twincat_link.rs | 6 +++--- autd3-protobuf/src/lightweight/server.rs | 6 +++--- autd3/src/controller/builder.rs | 6 +++--- autd3/src/controller/group.rs | 8 ++++---- autd3/src/controller/mod.rs | 18 +++++++++--------- autd3/src/controller/timer/mod.rs | 12 ++++++------ autd3/src/link/audit.rs | 6 +++--- autd3/src/link/nop.rs | 6 +++--- autd3/tests/link/audit.rs | 2 +- autd3/tests/link/nop.rs | 2 +- examples/src/tests/audio_file.rs | 4 ++-- examples/src/tests/bessel.rs | 4 ++-- examples/src/tests/custom.rs | 4 ++-- examples/src/tests/fir.rs | 4 ++-- examples/src/tests/flag.rs | 4 ++-- examples/src/tests/focus.rs | 4 ++-- examples/src/tests/group.rs | 6 +++--- examples/src/tests/holo.rs | 4 ++-- examples/src/tests/plane.rs | 4 ++-- examples/src/tests/stm.rs | 6 +++--- examples/src/tests/test_runner.rs | 4 ++-- .../src/tests/user_defined_gain_modulation.rs | 4 ++-- 26 files changed, 79 insertions(+), 77 deletions(-) rename autd3-driver/src/{link.rs => link/async.rs} (92%) create mode 100644 autd3-driver/src/link/mod.rs diff --git a/autd3-driver/src/link.rs b/autd3-driver/src/link/async.rs similarity index 92% rename from autd3-driver/src/link.rs rename to autd3-driver/src/link/async.rs index 5b36f647..e6d2b5bd 100644 --- a/autd3-driver/src/link.rs +++ b/autd3-driver/src/link/async.rs @@ -6,8 +6,7 @@ use crate::{ geometry::Geometry, }; -pub use internal::Link; -pub use internal::LinkBuilder; +pub use internal::{AsyncLink, AsyncLinkBuilder}; #[cfg(feature = "async-trait")] mod internal { @@ -15,7 +14,7 @@ mod internal { /// A trait that provides the interface with the device. #[async_trait::async_trait] - pub trait Link: Send { + pub trait AsyncLink: Send { /// Closes the link. async fn close(&mut self) -> Result<(), AUTDDriverError>; @@ -40,16 +39,16 @@ mod internal { /// A trait to build a link. #[async_trait::async_trait] - pub trait LinkBuilder: Send + Sync { + pub trait AsyncLinkBuilder: Send + Sync { /// The link type. - type L: Link; + type L: AsyncLink; /// Opens a link. async fn open(self, geometry: &Geometry) -> Result; } #[async_trait::async_trait] - impl Link for Box { + impl AsyncLink for Box { async fn close(&mut self) -> Result<(), AUTDDriverError> { self.as_mut().close().await } @@ -81,7 +80,7 @@ mod internal { use super::*; /// A trait that provides the interface with the device. - pub trait Link: Send { + pub trait AsyncLink: Send { /// Closes the link. fn close(&mut self) -> impl std::future::Future>; @@ -114,9 +113,9 @@ mod internal { } /// A trait to build a link. - pub trait LinkBuilder { + pub trait AsyncLinkBuilder { /// The link type. - type L: Link; + type L: AsyncLink; /// Opens a link. fn open( diff --git a/autd3-driver/src/link/mod.rs b/autd3-driver/src/link/mod.rs new file mode 100644 index 00000000..acbd6cf2 --- /dev/null +++ b/autd3-driver/src/link/mod.rs @@ -0,0 +1,3 @@ +mod r#async; + +pub use r#async::*; diff --git a/autd3-link-simulator/src/lib.rs b/autd3-link-simulator/src/lib.rs index 992f88ed..2e02405c 100644 --- a/autd3-link-simulator/src/lib.rs +++ b/autd3-link-simulator/src/lib.rs @@ -13,7 +13,7 @@ use std::net::SocketAddr; use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{Link, LinkBuilder}, + link::{AsyncLink, AsyncLinkBuilder}, }; /// A [`Link`] for [`AUTD3 Simulator`]. @@ -34,7 +34,7 @@ pub struct SimulatorBuilder { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl LinkBuilder for SimulatorBuilder { +impl AsyncLinkBuilder for SimulatorBuilder { type L = Simulator; #[tracing::instrument(level = "debug", skip(geometry))] @@ -74,7 +74,7 @@ impl Simulator { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl Link for Simulator { +impl AsyncLink for Simulator { async fn close(&mut self) -> Result<(), AUTDDriverError> { if !self.is_open { return Ok(()); diff --git a/autd3-link-twincat/src/local/mod.rs b/autd3-link-twincat/src/local/mod.rs index a2367fcd..5281df51 100644 --- a/autd3-link-twincat/src/local/mod.rs +++ b/autd3-link-twincat/src/local/mod.rs @@ -9,7 +9,7 @@ use zerocopy::IntoBytes; use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{Link, LinkBuilder}, + link::{AsyncLink, AsyncLinkBuilder}, }; #[repr(C)] @@ -47,7 +47,7 @@ pub struct TwinCAT { pub struct TwinCATBuilder {} #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl LinkBuilder for TwinCATBuilder { +impl AsyncLinkBuilder for TwinCATBuilder { type L = TwinCAT; async fn open(self, _: &Geometry) -> Result { @@ -93,7 +93,7 @@ impl TwinCAT { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl Link for TwinCAT { +impl AsyncLink for TwinCAT { async fn close(&mut self) -> Result<(), AUTDDriverError> { unsafe { self.dll diff --git a/autd3-link-twincat/src/remote/remote_twincat_link.rs b/autd3-link-twincat/src/remote/remote_twincat_link.rs index a8ada652..86ef5bee 100644 --- a/autd3-link-twincat/src/remote/remote_twincat_link.rs +++ b/autd3-link-twincat/src/remote/remote_twincat_link.rs @@ -7,7 +7,7 @@ use zerocopy::IntoBytes; use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{Link, LinkBuilder}, + link::{AsyncLink, AsyncLinkBuilder}, }; use crate::{error::AdsError, remote::native_methods::*}; @@ -44,7 +44,7 @@ pub struct RemoteTwinCATBuilder { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl LinkBuilder for RemoteTwinCATBuilder { +impl AsyncLinkBuilder for RemoteTwinCATBuilder { type L = RemoteTwinCAT; #[tracing::instrument(level = "debug", skip(_geometry))] @@ -135,7 +135,7 @@ impl RemoteTwinCAT { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl Link for RemoteTwinCAT { +impl AsyncLink for RemoteTwinCAT { async fn close(&mut self) -> Result<(), AUTDDriverError> { if self.port == 0 { return Ok(()); diff --git a/autd3-protobuf/src/lightweight/server.rs b/autd3-protobuf/src/lightweight/server.rs index e2cb7c00..0d1090eb 100644 --- a/autd3-protobuf/src/lightweight/server.rs +++ b/autd3-protobuf/src/lightweight/server.rs @@ -9,7 +9,7 @@ use tonic::{Request, Response, Status}; #[doc(hidden)] pub struct LightweightServer< - L: autd3_driver::link::LinkBuilder + 'static, + L: autd3_driver::link::AsyncLinkBuilder + 'static, F: Fn() -> L + Send + Sync + 'static, > where L::L: Sync, @@ -47,7 +47,7 @@ impl autd3_driver::datagram::Datagram } } -impl L + Send + Sync + 'static> +impl L + Send + Sync + 'static> LightweightServer where L::L: Sync, @@ -156,7 +156,7 @@ where } #[tonic::async_trait] -impl L + Send + Sync + 'static> +impl L + Send + Sync + 'static> ecat_light_server::EcatLight for LightweightServer where L::L: Sync, diff --git a/autd3/src/controller/builder.rs b/autd3/src/controller/builder.rs index 2dad0552..38f9046c 100644 --- a/autd3/src/controller/builder.rs +++ b/autd3/src/controller/builder.rs @@ -4,7 +4,7 @@ use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, geometry::{Device, Geometry, IntoDevice}, - link::LinkBuilder, + link::AsyncLinkBuilder, }; use derive_more::Debug; @@ -64,7 +64,7 @@ impl ControllerBuilder { } /// Equivalent to [`Self::open_with_timeout`] with a timeout of [`DEFAULT_TIMEOUT`]. - pub async fn open( + pub async fn open( self, link_builder: B, ) -> Result, AUTDError> { @@ -74,7 +74,7 @@ impl ControllerBuilder { /// Opens a controller with a timeout. /// /// Opens link, and then initialize and synchronize the devices. The `timeout` is used to send data for initialization and synchronization. - pub async fn open_with_timeout( + pub async fn open_with_timeout( self, link_builder: B, timeout: Duration, diff --git a/autd3/src/controller/group.rs b/autd3/src/controller/group.rs index e559a418..29f24710 100644 --- a/autd3/src/controller/group.rs +++ b/autd3/src/controller/group.rs @@ -8,13 +8,13 @@ use autd3_driver::{ }; use itertools::Itertools; -use super::{Controller, Link}; +use super::{AsyncLink, Controller}; use tracing; /// A struct for grouping devices and sending different data to each group. See also [`Controller::group`]. #[allow(clippy::type_complexity)] -pub struct Group<'a, K: PartialEq + Debug, L: Link> { +pub struct Group<'a, K: PartialEq + Debug, L: AsyncLink> { pub(crate) cnt: &'a mut Controller, pub(crate) keys: Vec>, pub(crate) done: Vec, @@ -23,7 +23,7 @@ pub struct Group<'a, K: PartialEq + Debug, L: Link> { pub(crate) operations: Vec, Box)>>, } -impl<'a, K: PartialEq + Debug, L: Link> Group<'a, K, L> { +impl<'a, K: PartialEq + Debug, L: AsyncLink> Group<'a, K, L> { #[must_use] pub(crate) fn new(cnt: &'a mut Controller, f: impl Fn(&Device) -> Option) -> Self { let keys = cnt.geometry.devices().map(f).collect::>(); @@ -165,7 +165,7 @@ impl<'a, K: PartialEq + Debug, L: Link> Group<'a, K, L> { } } -impl Controller { +impl Controller { /// Group the devices by given function and send different data to each group. /// /// If the key is `None`, nothing is done for the devices corresponding to the key. diff --git a/autd3/src/controller/mod.rs b/autd3/src/controller/mod.rs index 915c44a9..96f77535 100644 --- a/autd3/src/controller/mod.rs +++ b/autd3/src/controller/mod.rs @@ -18,7 +18,7 @@ use autd3_driver::{ version::FirmwareVersion, }, geometry::{Device, Geometry}, - link::Link, + link::AsyncLink, }; use timer::Timer; @@ -33,7 +33,7 @@ use derive_more::{Deref, DerefMut}; /// /// All operations to the devices are done through this struct. #[derive(Builder, Deref, DerefMut)] -pub struct Controller { +pub struct Controller { #[get(ref, ref_mut, no_doc)] link: L, #[get(ref, ref_mut, no_doc)] @@ -46,7 +46,7 @@ pub struct Controller { timer: Timer, } -impl Controller { +impl Controller { /// Sends a data to the devices. /// /// If the [`Datagram::timeout`] value is @@ -195,7 +195,7 @@ impl Controller { } } -impl<'a, L: Link> IntoIterator for &'a Controller { +impl<'a, L: AsyncLink> IntoIterator for &'a Controller { type Item = &'a Device; type IntoIter = std::slice::Iter<'a, Device>; @@ -204,7 +204,7 @@ impl<'a, L: Link> IntoIterator for &'a Controller { } } -impl<'a, L: Link> IntoIterator for &'a mut Controller { +impl<'a, L: AsyncLink> IntoIterator for &'a mut Controller { type Item = &'a mut Device; type IntoIter = std::slice::IterMut<'a, Device>; @@ -215,9 +215,9 @@ impl<'a, L: Link> IntoIterator for &'a mut Controller { #[cfg_attr(docsrs, doc(cfg(feature = "async-trait")))] #[cfg(feature = "async-trait")] -impl Controller { +impl Controller { /// Converts `Controller` into a `Controller>`. - pub fn into_boxed_link(self) -> Controller> { + pub fn into_boxed_link(self) -> Controller> { let cnt = std::mem::ManuallyDrop::new(self); let link = unsafe { std::ptr::read(&cnt.link) }; let geometry = unsafe { std::ptr::read(&cnt.geometry) }; @@ -239,7 +239,7 @@ impl Controller { /// /// This function must be used only when converting an instance created by [`Controller::into_boxed_link`] back to the original [`Controller`]. /// - pub unsafe fn from_boxed_link(cnt: Controller>) -> Controller { + pub unsafe fn from_boxed_link(cnt: Controller>) -> Controller { let cnt = std::mem::ManuallyDrop::new(cnt); let link = unsafe { std::ptr::read(&cnt.link) }; let geometry = unsafe { std::ptr::read(&cnt.geometry) }; @@ -256,7 +256,7 @@ impl Controller { } } -impl Drop for Controller { +impl Drop for Controller { fn drop(&mut self) { if !self.link.is_open() { return; diff --git a/autd3/src/controller/timer/mod.rs b/autd3/src/controller/timer/mod.rs index 2402e0d4..d43c38cb 100644 --- a/autd3/src/controller/timer/mod.rs +++ b/autd3/src/controller/timer/mod.rs @@ -11,7 +11,7 @@ use autd3_driver::{ cpu::{check_if_msg_is_processed, RxMessage, TxMessage}, operation::{Operation, OperationHandler}, }, - link::Link, + link::AsyncLink, }; use itertools::Itertools; @@ -66,7 +66,7 @@ impl Timer { geometry: &Geometry, tx: &mut [TxMessage], rx: &mut [RxMessage], - link: &mut impl Link, + link: &mut impl AsyncLink, operations: Vec<(impl Operation, impl Operation)>, timeout: Option, parallel_threshold: Option, @@ -110,7 +110,7 @@ impl Timer { geometry: &Geometry, tx: &mut [TxMessage], rx: &mut [RxMessage], - link: &mut impl Link, + link: &mut impl AsyncLink, mut operations: Vec<(impl Operation, impl Operation)>, timeout: Duration, parallel: bool, @@ -139,7 +139,7 @@ impl Timer { sleeper: &impl Sleeper, tx: &[TxMessage], rx: &mut [RxMessage], - link: &mut impl Link, + link: &mut impl AsyncLink, timeout: Duration, ) -> Result<(), AUTDDriverError> { if !link.is_open() { @@ -159,7 +159,7 @@ impl Timer { sleeper: &S, tx: &[TxMessage], rx: &mut [RxMessage], - link: &mut impl Link, + link: &mut impl AsyncLink, timeout: Duration, ) -> Result<(), AUTDDriverError> { let start = Instant::now(); @@ -207,7 +207,7 @@ mod tests { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] - impl Link for MockLink { + impl AsyncLink for MockLink { async fn close(&mut self) -> Result<(), AUTDDriverError> { self.is_open = false; Ok(()) diff --git a/autd3/src/link/audit.rs b/autd3/src/link/audit.rs index efa88e81..ea9ebed3 100644 --- a/autd3/src/link/audit.rs +++ b/autd3/src/link/audit.rs @@ -1,7 +1,7 @@ use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{Link, LinkBuilder}, + link::{AsyncLink, AsyncLinkBuilder}, }; use autd3_firmware_emulator::CPUEmulator; @@ -42,7 +42,7 @@ pub struct AuditBuilder { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl LinkBuilder for AuditBuilder { +impl AsyncLinkBuilder for AuditBuilder { type L = Audit; async fn open( @@ -116,7 +116,7 @@ impl Audit { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl Link for Audit { +impl AsyncLink for Audit { async fn close(&mut self) -> Result<(), AUTDDriverError> { self.is_open = false; Ok(()) diff --git a/autd3/src/link/nop.rs b/autd3/src/link/nop.rs index 1479da05..9946dcad 100644 --- a/autd3/src/link/nop.rs +++ b/autd3/src/link/nop.rs @@ -1,7 +1,7 @@ use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{Link, LinkBuilder}, + link::{AsyncLink, AsyncLinkBuilder}, }; use autd3_firmware_emulator::CPUEmulator; @@ -18,7 +18,7 @@ pub struct Nop { pub struct NopBuilder {} #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl LinkBuilder for NopBuilder { +impl AsyncLinkBuilder for NopBuilder { type L = Nop; async fn open(self, geometry: &Geometry) -> Result { @@ -34,7 +34,7 @@ impl LinkBuilder for NopBuilder { } #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl Link for Nop { +impl AsyncLink for Nop { async fn close(&mut self) -> Result<(), AUTDDriverError> { self.is_open = false; Ok(()) diff --git a/autd3/tests/link/audit.rs b/autd3/tests/link/audit.rs index 614078e8..0d7bd193 100644 --- a/autd3/tests/link/audit.rs +++ b/autd3/tests/link/audit.rs @@ -71,7 +71,7 @@ async fn audit_test() -> anyhow::Result<()> { } { - use autd3_driver::link::Link; + use autd3_driver::link::AsyncLink; assert!(autd.link_mut().close().await.is_ok()); assert_eq!( Err(AUTDDriverError::LinkClosed), diff --git a/autd3/tests/link/nop.rs b/autd3/tests/link/nop.rs index 8a43d4a1..416e6b8e 100644 --- a/autd3/tests/link/nop.rs +++ b/autd3/tests/link/nop.rs @@ -1,4 +1,4 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; #[tokio::test] async fn nop_test() -> anyhow::Result<()> { diff --git a/examples/src/tests/audio_file.rs b/examples/src/tests/audio_file.rs index 0b22a341..d2a64929 100644 --- a/examples/src/tests/audio_file.rs +++ b/examples/src/tests/audio_file.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn audio_file(autd: &mut Controller) -> anyhow::Result { +pub async fn audio_file(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::default()).await?; let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); diff --git a/examples/src/tests/bessel.rs b/examples/src/tests/bessel.rs index b0353032..d2ae2648 100644 --- a/examples/src/tests/bessel.rs +++ b/examples/src/tests/bessel.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn bessel(autd: &mut Controller) -> anyhow::Result { +pub async fn bessel(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::default()).await?; let center = autd.center(); diff --git a/examples/src/tests/custom.rs b/examples/src/tests/custom.rs index 6deefd0d..d5a97f60 100644 --- a/examples/src/tests/custom.rs +++ b/examples/src/tests/custom.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn custom(autd: &mut Controller) -> anyhow::Result { +pub async fn custom(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::disable()).await?; let m = autd3::modulation::Custom::new([0, 255], 4 * kHz)?; diff --git a/examples/src/tests/fir.rs b/examples/src/tests/fir.rs index 743c259b..f8708f8a 100644 --- a/examples/src/tests/fir.rs +++ b/examples/src/tests/fir.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn fir(autd: &mut Controller) -> anyhow::Result { +pub async fn fir(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::disable()).await?; let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); diff --git a/examples/src/tests/flag.rs b/examples/src/tests/flag.rs index 3d90a94c..de6ccd5d 100644 --- a/examples/src/tests/flag.rs +++ b/examples/src/tests/flag.rs @@ -1,8 +1,8 @@ use tokio::io::AsyncBufReadExt; -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn flag(autd: &mut Controller) -> anyhow::Result { +pub async fn flag(autd: &mut Controller) -> anyhow::Result { autd.send(ReadsFPGAState::new(|_dev| true)).await?; println!("press any key to force fan..."); diff --git a/examples/src/tests/focus.rs b/examples/src/tests/focus.rs index ed8850ed..f2513e77 100644 --- a/examples/src/tests/focus.rs +++ b/examples/src/tests/focus.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn focus(autd: &mut Controller) -> anyhow::Result { +pub async fn focus(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::default()).await?; let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); diff --git a/examples/src/tests/group.rs b/examples/src/tests/group.rs index b900955f..9c16cd7f 100644 --- a/examples/src/tests/group.rs +++ b/examples/src/tests/group.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn group_by_device(autd: &mut Controller) -> anyhow::Result { +pub async fn group_by_device(autd: &mut Controller) -> anyhow::Result { let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); autd.group(|dev| match dev.idx() { @@ -16,7 +16,7 @@ pub async fn group_by_device(autd: &mut Controller) -> anyhow::Result Ok(true) } -pub async fn group_by_transducer(autd: &mut Controller) -> anyhow::Result { +pub async fn group_by_transducer(autd: &mut Controller) -> anyhow::Result { let cx = autd.center().x; let g1 = Focus::new(autd[0].center() + Vector3::new(0., 0., 150.0 * mm)); let g2 = Null::new(); diff --git a/examples/src/tests/holo.rs b/examples/src/tests/holo.rs index 3959ba79..ad154377 100644 --- a/examples/src/tests/holo.rs +++ b/examples/src/tests/holo.rs @@ -1,7 +1,7 @@ use autd3::{ driver::{ datagram::{BoxedGain, IntoBoxedGain}, - link::Link, + link::AsyncLink, }, prelude::*, }; @@ -9,7 +9,7 @@ use autd3_gain_holo::*; use std::io::{self, Write}; -pub async fn holo(autd: &mut Controller) -> anyhow::Result { +pub async fn holo(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::default()).await?; let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); diff --git a/examples/src/tests/plane.rs b/examples/src/tests/plane.rs index 8cee6450..18f7e9e8 100644 --- a/examples/src/tests/plane.rs +++ b/examples/src/tests/plane.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn plane(autd: &mut Controller) -> anyhow::Result { +pub async fn plane(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::default()).await?; let dir = Vector3::z_axis(); diff --git a/examples/src/tests/stm.rs b/examples/src/tests/stm.rs index e65d3564..d60ce613 100644 --- a/examples/src/tests/stm.rs +++ b/examples/src/tests/stm.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; -pub async fn foci_stm(autd: &mut Controller) -> anyhow::Result { +pub async fn foci_stm(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::disable()).await?; let stm = FociSTM::new( @@ -21,7 +21,7 @@ pub async fn foci_stm(autd: &mut Controller) -> anyhow::Result Ok(true) } -pub async fn gain_stm(autd: &mut Controller) -> anyhow::Result { +pub async fn gain_stm(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::disable()).await?; let stm = GainSTM::new( diff --git a/examples/src/tests/test_runner.rs b/examples/src/tests/test_runner.rs index c6530e84..c7dd0213 100644 --- a/examples/src/tests/test_runner.rs +++ b/examples/src/tests/test_runner.rs @@ -1,13 +1,13 @@ use std::io::{self, Write}; -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; use super::{ audio_file::*, bessel::*, custom::*, fir::*, flag::*, focus::*, group::*, holo::*, plane::*, stm::*, user_defined_gain_modulation::*, }; -pub async fn run(mut autd: Controller) -> anyhow::Result<()> { +pub async fn run(mut autd: Controller) -> anyhow::Result<()> { type Test = ( &'static str, fn( diff --git a/examples/src/tests/user_defined_gain_modulation.rs b/examples/src/tests/user_defined_gain_modulation.rs index 8404e1c4..e9b5328b 100644 --- a/examples/src/tests/user_defined_gain_modulation.rs +++ b/examples/src/tests/user_defined_gain_modulation.rs @@ -1,4 +1,4 @@ -use autd3::{driver::link::Link, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*}; use autd3_driver::derive::*; #[derive(Gain, Clone, Copy, Debug)] @@ -59,7 +59,7 @@ impl Modulation for Burst { } } -pub async fn user_defined(autd: &mut Controller) -> anyhow::Result { +pub async fn user_defined(autd: &mut Controller) -> anyhow::Result { autd.send(Silencer::disable()).await?; let g = MyUniform::new(); From 5c768b2f3d20e2fd625503fdbcbc18870f6710ab Mon Sep 17 00:00:00 2001 From: shun suzuki Date: Fri, 10 Jan 2025 17:06:37 +0900 Subject: [PATCH 2/8] make async optional --- LICENSE | 2 +- README.md | 2 +- autd3-driver/Cargo.toml | 3 +- autd3-driver/README.md | 2 +- autd3-driver/src/lib.rs | 1 - autd3-driver/src/link/mod.rs | 7 + autd3-driver/src/link/sync.rs | 66 +++ autd3-link-simulator/Cargo.toml | 8 +- autd3-link-simulator/README.md | 2 +- autd3-link-simulator/src/lib.rs | 61 +- autd3-link-twincat/Cargo.toml | 5 +- autd3-link-twincat/README.md | 2 +- autd3-link-twincat/src/local/mod.rs | 51 +- .../src/remote/remote_twincat_link.rs | 55 +- autd3/Cargo.toml | 7 +- autd3/README.md | 2 +- autd3/src/async/controller/builder.rs | 136 +++++ autd3/src/async/controller/group.rs | 449 +++++++++++++++ autd3/src/async/controller/mod.rs | 541 ++++++++++++++++++ autd3/src/async/controller/timer/mod.rs | 387 +++++++++++++ autd3/src/async/controller/timer/sleep.rs | 152 +++++ autd3/src/async/mod.rs | 4 + autd3/src/controller/builder.rs | 21 +- autd3/src/controller/group.rs | 103 ++-- autd3/src/controller/mod.rs | 207 +++---- autd3/src/controller/timer/mod.rs | 146 ++--- autd3/src/controller/timer/sleep.rs | 34 +- autd3/src/lib.rs | 5 + autd3/src/link/audit.rs | 58 +- autd3/src/link/nop.rs | 51 +- .../tests/{ => async}/datagram/gain/group.rs | 2 +- autd3/tests/{ => async}/datagram/gain/mod.rs | 0 autd3/tests/{ => async}/datagram/mod.rs | 0 autd3/tests/{ => async}/link/audit.rs | 2 +- autd3/tests/{ => async}/link/mod.rs | 0 autd3/tests/{ => async}/link/nop.rs | 2 +- autd3/tests/async/mod.rs | 27 + autd3/tests/sync/datagram/gain/group.rs | 39 ++ autd3/tests/sync/datagram/gain/mod.rs | 1 + autd3/tests/sync/datagram/mod.rs | 1 + autd3/tests/sync/link/audit.rs | 81 +++ autd3/tests/sync/link/mod.rs | 2 + autd3/tests/sync/link/nop.rs | 14 + autd3/tests/sync/mod.rs | 25 + autd3/tests/test.rs | 30 +- build.py | 3 + examples/Cargo.toml | 12 +- examples/README.md | 2 +- examples/src/async.rs | 36 ++ examples/src/nop.rs | 8 +- examples/src/remote_twincat.rs | 8 +- examples/src/simulator.rs | 8 +- examples/src/tests/audio_file.rs | 8 +- examples/src/tests/bessel.rs | 8 +- examples/src/tests/custom.rs | 8 +- examples/src/tests/fir.rs | 8 +- examples/src/tests/flag.rs | 27 +- examples/src/tests/focus.rs | 8 +- examples/src/tests/group.rs | 11 +- examples/src/tests/holo.rs | 8 +- examples/src/tests/plane.rs | 8 +- examples/src/tests/stm.rs | 14 +- examples/src/tests/test_runner.rs | 47 +- .../src/tests/user_defined_gain_modulation.rs | 8 +- examples/src/twincat.rs | 9 +- 65 files changed, 2542 insertions(+), 503 deletions(-) create mode 100644 autd3-driver/src/link/sync.rs create mode 100644 autd3/src/async/controller/builder.rs create mode 100644 autd3/src/async/controller/group.rs create mode 100644 autd3/src/async/controller/mod.rs create mode 100644 autd3/src/async/controller/timer/mod.rs create mode 100644 autd3/src/async/controller/timer/sleep.rs create mode 100644 autd3/src/async/mod.rs rename autd3/tests/{ => async}/datagram/gain/group.rs (94%) rename autd3/tests/{ => async}/datagram/gain/mod.rs (100%) rename autd3/tests/{ => async}/datagram/mod.rs (100%) rename autd3/tests/{ => async}/link/audit.rs (98%) rename autd3/tests/{ => async}/link/mod.rs (100%) rename autd3/tests/{ => async}/link/nop.rs (85%) create mode 100644 autd3/tests/async/mod.rs create mode 100644 autd3/tests/sync/datagram/gain/group.rs create mode 100644 autd3/tests/sync/datagram/gain/mod.rs create mode 100644 autd3/tests/sync/datagram/mod.rs create mode 100644 autd3/tests/sync/link/audit.rs create mode 100644 autd3/tests/sync/link/mod.rs create mode 100644 autd3/tests/sync/link/nop.rs create mode 100644 autd3/tests/sync/mod.rs create mode 100644 examples/src/async.rs diff --git a/LICENSE b/LICENSE index 22af6cce..254d85fe 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Shun Suzuki +Copyright (c) 2022-2025 Shun Suzuki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 386f7eb9..e3bbc7de 100644 --- a/README.md +++ b/README.md @@ -14,4 +14,4 @@ # Author -Shun Suzuki, 2022-2024 +Shun Suzuki, 2022-2025 diff --git a/autd3-driver/Cargo.toml b/autd3-driver/Cargo.toml index dffa5699..b9ab1e18 100644 --- a/autd3-driver/Cargo.toml +++ b/autd3-driver/Cargo.toml @@ -42,6 +42,7 @@ criterion = { workspace = true } [features] default = ["derive"] +async = [] lightweight = [] async-trait = ["dep:async-trait"] use_meter = [] @@ -59,5 +60,5 @@ path = "benches/gain.rs" harness = false [package.metadata.docs.rs] -features = ["lightweight", "async-trait", "serde", "derive"] +features = ["lightweight", "async", "serde", "derive"] rustdoc-args = ["--cfg", "docsrs"] diff --git a/autd3-driver/README.md b/autd3-driver/README.md index c3a88e2c..7f5e2e49 100644 --- a/autd3-driver/README.md +++ b/autd3-driver/README.md @@ -4,4 +4,4 @@ This crate provides driver of AUTD3 device. # Author -Shun Suzuki, 2022-2024 +Shun Suzuki, 2022-2025 diff --git a/autd3-driver/src/lib.rs b/autd3-driver/src/lib.rs index 53606b68..0acde69a 100644 --- a/autd3-driver/src/lib.rs +++ b/autd3-driver/src/lib.rs @@ -28,7 +28,6 @@ pub mod link; #[doc(hidden)] pub mod utils; -#[cfg_attr(docsrs, doc(cfg(feature = "async-trait")))] #[cfg(feature = "async-trait")] pub use async_trait::async_trait; diff --git a/autd3-driver/src/link/mod.rs b/autd3-driver/src/link/mod.rs index acbd6cf2..a384e709 100644 --- a/autd3-driver/src/link/mod.rs +++ b/autd3-driver/src/link/mod.rs @@ -1,3 +1,10 @@ +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg(feature = "async")] mod r#async; +mod sync; +#[cfg(feature = "async")] +#[doc(inline)] pub use r#async::*; +#[doc(inline)] +pub use sync::*; diff --git a/autd3-driver/src/link/sync.rs b/autd3-driver/src/link/sync.rs new file mode 100644 index 00000000..9fa16282 --- /dev/null +++ b/autd3-driver/src/link/sync.rs @@ -0,0 +1,66 @@ +use std::time::Duration; + +use crate::{ + derive::Geometry, + error::AUTDDriverError, + firmware::cpu::{RxMessage, TxMessage}, +}; + +/// A trait that provides the interface with the device. +pub trait Link: Send { + /// Closes the link. + fn close(&mut self) -> Result<(), AUTDDriverError>; + + #[doc(hidden)] + fn update(&mut self, _geometry: &Geometry) -> Result<(), AUTDDriverError> { + Ok(()) + } + + /// Sends a message to the device. + fn send(&mut self, tx: &[TxMessage]) -> Result; + + /// Receives a message from the device. + fn receive(&mut self, rx: &mut [RxMessage]) -> Result; + + /// Checks if the link is open. + #[must_use] + fn is_open(&self) -> bool; + + #[doc(hidden)] + fn trace(&mut self, _: Option, _: Option) {} +} + +/// A trait to build a link. +pub trait LinkBuilder: Send + Sync { + /// The link type. + type L: Link; + + /// Opens a link. + fn open(self, geometry: &Geometry) -> Result; +} + +impl Link for Box { + fn close(&mut self) -> Result<(), AUTDDriverError> { + self.as_mut().close() + } + + fn update(&mut self, geometry: &Geometry) -> Result<(), AUTDDriverError> { + self.as_mut().update(geometry) + } + + fn send(&mut self, tx: &[TxMessage]) -> Result { + self.as_mut().send(tx) + } + + fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + self.as_mut().receive(rx) + } + + fn is_open(&self) -> bool { + self.as_ref().is_open() + } + + fn trace(&mut self, timeout: Option, parallel_threshold: Option) { + self.as_mut().trace(timeout, parallel_threshold) + } +} diff --git a/autd3-link-simulator/Cargo.toml b/autd3-link-simulator/Cargo.toml index 235842a1..258aa104 100644 --- a/autd3-link-simulator/Cargo.toml +++ b/autd3-link-simulator/Cargo.toml @@ -11,10 +11,16 @@ repository = { workspace = true } [dependencies] autd3-protobuf = { workspace = true } -autd3-driver = { workspace = true, features = ["derive"] } +autd3-driver = { workspace = true, features = ["derive", "async"] } tonic = { workspace = true } tracing = { workspace = true } +tokio = { workspace = true, features = ["rt", "rt-multi-thread"], optional = true } [features] default = [] +blocking = ["tokio"] async-trait = ["autd3-driver/async-trait", "autd3-protobuf/async-trait"] + +[package.metadata.docs.rs] +features = ["blocking"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/autd3-link-simulator/README.md b/autd3-link-simulator/README.md index 4f1051f5..96516f8c 100644 --- a/autd3-link-simulator/README.md +++ b/autd3-link-simulator/README.md @@ -4,4 +4,4 @@ This crate provides a link for [AUTD3 Simulator](https://github.com/shinolab/aut # Author -Shun Suzuki, 2022-2024 +Shun Suzuki, 2022-2025 diff --git a/autd3-link-simulator/src/lib.rs b/autd3-link-simulator/src/lib.rs index 2e02405c..778309bd 100644 --- a/autd3-link-simulator/src/lib.rs +++ b/autd3-link-simulator/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] #![warn(missing_docs)] #![warn(rustdoc::missing_crate_level_docs)] #![warn(rustdoc::unescaped_backticks)] @@ -16,7 +17,7 @@ use autd3_driver::{ link::{AsyncLink, AsyncLinkBuilder}, }; -/// A [`Link`] for [`AUTD3 Simulator`]. +/// A [`AsyncLink`] for [`AUTD3 Simulator`]. /// /// [`AUTD3 Simulator`]: https://github.com/shinolab/autd3-server pub struct Simulator { @@ -137,3 +138,61 @@ impl AsyncLink for Simulator { self.is_open } } + +#[cfg(feature = "blocking")] +use autd3_driver::link::{Link, LinkBuilder}; + +/// A [`Link`] for [`AUTD3 Simulator`]. +/// +/// [`AUTD3 Simulator`]: https://github.com/shinolab/autd3-server +#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))] +#[cfg(feature = "blocking")] +pub struct SimulatorBlocking { + runtime: tokio::runtime::Runtime, + inner: Simulator, +} + +#[cfg(feature = "blocking")] +impl Link for SimulatorBlocking { + fn close(&mut self) -> Result<(), AUTDDriverError> { + self.runtime.block_on(self.inner.close()) + } + + fn update( + &mut self, + geometry: &autd3_driver::geometry::Geometry, + ) -> Result<(), AUTDDriverError> { + self.runtime.block_on(self.inner.update(geometry)) + } + + fn send(&mut self, tx: &[TxMessage]) -> Result { + self.runtime.block_on(self.inner.send(tx)) + } + + fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + self.runtime.block_on(self.inner.receive(rx)) + } + + fn is_open(&self) -> bool { + self.inner.is_open() + } + + fn trace(&mut self, timeout: Option, parallel_threshold: Option) { + self.inner.trace(timeout, parallel_threshold) + } +} + +#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))] +#[cfg(feature = "blocking")] +impl LinkBuilder for SimulatorBuilder { + type L = SimulatorBlocking; + + fn open(self, geometry: &autd3_driver::geometry::Geometry) -> Result { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed to create runtime"); + let inner = runtime.block_on(::open(self, geometry))?; + Ok(Self::L { runtime, inner }) + } +} diff --git a/autd3-link-twincat/Cargo.toml b/autd3-link-twincat/Cargo.toml index 8ebb39e8..05c91be1 100644 --- a/autd3-link-twincat/Cargo.toml +++ b/autd3-link-twincat/Cargo.toml @@ -24,9 +24,10 @@ cc = { workspace = true, optional = true } local = ["libloading"] remote = ["itertools", "cc", "tracing"] default = ["local"] +async = ["autd3-driver/async"] +async-trait = ["async", "autd3-driver/async-trait"] all = ["local", "remote"] -async-trait = ["autd3-driver/async-trait"] [package.metadata.docs.rs] -all-features = true +features = ["local", "remote", "async"] rustdoc-args = ["--cfg", "docsrs"] diff --git a/autd3-link-twincat/README.md b/autd3-link-twincat/README.md index 5675c7b2..182d6533 100644 --- a/autd3-link-twincat/README.md +++ b/autd3-link-twincat/README.md @@ -4,4 +4,4 @@ This crate provides a link to AUTD using TwinCAT. # Author -Shun Suzuki, 2022-2024 +Shun Suzuki, 2022-2025 diff --git a/autd3-link-twincat/src/local/mod.rs b/autd3-link-twincat/src/local/mod.rs index 5281df51..dc64262c 100644 --- a/autd3-link-twincat/src/local/mod.rs +++ b/autd3-link-twincat/src/local/mod.rs @@ -9,7 +9,7 @@ use zerocopy::IntoBytes; use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{AsyncLink, AsyncLinkBuilder}, + link::{Link, LinkBuilder}, }; #[repr(C)] @@ -46,11 +46,10 @@ pub struct TwinCAT { #[derive(Builder)] pub struct TwinCATBuilder {} -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl AsyncLinkBuilder for TwinCATBuilder { +impl LinkBuilder for TwinCATBuilder { type L = TwinCAT; - async fn open(self, _: &Geometry) -> Result { + fn open(self, _: &Geometry) -> Result { let dll = unsafe { lib::Library::new("TcAdsDll") }.map_err(|_| { AUTDDriverError::LinkError("TcAdsDll not found. Please install TwinCAT3".to_owned()) })?; @@ -92,9 +91,8 @@ impl TwinCAT { } } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl AsyncLink for TwinCAT { - async fn close(&mut self) -> Result<(), AUTDDriverError> { +impl Link for TwinCAT { + fn close(&mut self) -> Result<(), AUTDDriverError> { unsafe { self.dll .get:: i32>(b"AdsPortCloseEx") @@ -106,7 +104,7 @@ impl AsyncLink for TwinCAT { Ok(()) } - async fn send(&mut self, tx: &[TxMessage]) -> Result { + fn send(&mut self, tx: &[TxMessage]) -> Result { unsafe { let n_err = self.dll.get:: Result { + fn receive(&mut self, rx: &mut [RxMessage]) -> Result { let mut read_bytes: u32 = 0; unsafe { let n_err = self @@ -175,3 +173,38 @@ impl AsyncLink for TwinCAT { self.port > 0 } } + +#[cfg(feature = "async")] +use autd3_driver::link::{AsyncLink, AsyncLinkBuilder}; + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] +impl AsyncLinkBuilder for TwinCATBuilder { + type L = TwinCAT; + + async fn open(self, geometry: &Geometry) -> Result { + ::open(self, geometry) + } +} + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] +impl AsyncLink for TwinCAT { + async fn close(&mut self) -> Result<(), AUTDDriverError> { + ::close(self) + } + + async fn send(&mut self, tx: &[TxMessage]) -> Result { + ::send(self, tx) + } + + async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + ::receive(self, rx) + } + + fn is_open(&self) -> bool { + ::is_open(self) + } +} diff --git a/autd3-link-twincat/src/remote/remote_twincat_link.rs b/autd3-link-twincat/src/remote/remote_twincat_link.rs index 86ef5bee..8d4e8f88 100644 --- a/autd3-link-twincat/src/remote/remote_twincat_link.rs +++ b/autd3-link-twincat/src/remote/remote_twincat_link.rs @@ -7,7 +7,7 @@ use zerocopy::IntoBytes; use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{AsyncLink, AsyncLinkBuilder}, + link::{Link, LinkBuilder}, }; use crate::{error::AdsError, remote::native_methods::*}; @@ -43,12 +43,11 @@ pub struct RemoteTwinCATBuilder { client_ams_net_id: String, } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl AsyncLinkBuilder for RemoteTwinCATBuilder { +impl LinkBuilder for RemoteTwinCATBuilder { type L = RemoteTwinCAT; #[tracing::instrument(level = "debug", skip(_geometry))] - async fn open(self, _geometry: &Geometry) -> Result { + fn open(self, _geometry: &Geometry) -> Result { tracing::info!("Connecting to TwinCAT3"); let RemoteTwinCATBuilder { @@ -134,9 +133,8 @@ impl RemoteTwinCAT { } } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl AsyncLink for RemoteTwinCAT { - async fn close(&mut self) -> Result<(), AUTDDriverError> { +impl Link for RemoteTwinCAT { + fn close(&mut self) -> Result<(), AUTDDriverError> { if self.port == 0 { return Ok(()); } @@ -152,7 +150,7 @@ impl AsyncLink for RemoteTwinCAT { Ok(()) } - async fn send(&mut self, tx: &[TxMessage]) -> Result { + fn send(&mut self, tx: &[TxMessage]) -> Result { let addr = AmsAddr { net_id: self.net_id, port: PORT, @@ -180,7 +178,7 @@ impl AsyncLink for RemoteTwinCAT { Err(AdsError::SendData(res as _).into()) } - async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + fn receive(&mut self, rx: &mut [RxMessage]) -> Result { let addr = AmsAddr { net_id: self.net_id, port: PORT, @@ -210,3 +208,42 @@ impl AsyncLink for RemoteTwinCAT { self.port > 0 } } + +#[cfg(feature = "async")] +use autd3_driver::link::{AsyncLink, AsyncLinkBuilder}; + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] +impl AsyncLinkBuilder for RemoteTwinCATBuilder { + type L = RemoteTwinCAT; + + async fn open(self, geometry: &Geometry) -> Result { + ::open(self, geometry) + } +} + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] +impl AsyncLink for RemoteTwinCAT { + async fn close(&mut self) -> Result<(), AUTDDriverError> { + ::close(self) + } + + async fn send(&mut self, tx: &[TxMessage]) -> Result { + ::send(self, tx) + } + + async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + ::receive(self, rx) + } + + fn is_open(&self) -> bool { + ::is_open(self) + } + + fn trace(&mut self, timeout: Option, parallel_threshold: Option) { + ::trace(self, timeout, parallel_threshold) + } +} diff --git a/autd3/Cargo.toml b/autd3/Cargo.toml index b80d954a..702bc7af 100644 --- a/autd3/Cargo.toml +++ b/autd3/Cargo.toml @@ -14,7 +14,7 @@ autd3-firmware-emulator = { workspace = true } autd3-driver = { workspace = true, features = ["derive"] } num = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "time"] } +tokio = { workspace = true, features = ["rt-multi-thread", "time"], optional = true } derive_more = { workspace = true } tynm = { workspace = true } itertools = { workspace = true } @@ -30,7 +30,8 @@ windows = { workspace = true, features = ["Win32_Security"] } [features] default = [] -async-trait = ["autd3-driver/async-trait"] +async = ["tokio", "autd3-driver/async"] +async-trait = ["async", "autd3-driver/async-trait"] dynamic_freq = ["autd3-driver/dynamic_freq", "autd3-firmware-emulator/dynamic_freq"] [dev-dependencies] @@ -41,5 +42,5 @@ rstest = { workspace = true } tokio-test = { workspace = true } [package.metadata.docs.rs] -features = ["async-trait"] +features = ["async"] rustdoc-args = ["--cfg", "docsrs"] diff --git a/autd3/README.md b/autd3/README.md index a4935010..0d471945 100644 --- a/autd3/README.md +++ b/autd3/README.md @@ -4,4 +4,4 @@ # Author -Shun Suzuki, 2022-2024 +Shun Suzuki, 2022-2025 diff --git a/autd3/src/async/controller/builder.rs b/autd3/src/async/controller/builder.rs new file mode 100644 index 00000000..38f9046c --- /dev/null +++ b/autd3/src/async/controller/builder.rs @@ -0,0 +1,136 @@ +use std::time::Duration; + +use autd3_driver::{ + derive::*, + firmware::cpu::{RxMessage, TxMessage}, + geometry::{Device, Geometry, IntoDevice}, + link::AsyncLinkBuilder, +}; + +use derive_more::Debug; +use spin_sleep::SpinSleeper; +use zerocopy::FromZeros; + +use super::{ + timer::{Timer, TimerStrategy}, + Controller, +}; +use crate::{error::AUTDError, link::Nop}; + +/// A builder for creating a [`Controller`] instance. +#[derive(Builder, Debug)] +pub struct ControllerBuilder { + #[debug(skip)] + #[get(take)] + /// Takes the devices out of the builder. + devices: Vec, + #[get] + #[set] + /// The default parallel threshold when no threshold is specified for the [`Datagram`] to be sent. The default value is 4. + default_parallel_threshold: usize, + #[set] + #[get] + /// The default timeout when no timeout is specified for the [`Datagram`] to be sent. The default value is 20ms. + default_timeout: Duration, + #[get] + #[set] + /// The duration between sending operations. The default value is 1ms. + send_interval: Duration, + #[get] + #[set] + /// The duration between receiving operations. The default value is 1ms. + receive_interval: Duration, + #[get(ref)] + #[set] + /// The strategy used for timing operations. The default value is [`TimerStrategy::Spin`] with the default [`SpinSleeper`]. + timer_strategy: TimerStrategy, +} + +impl ControllerBuilder { + #[must_use] + pub(crate) fn new>(iter: F) -> ControllerBuilder { + Self { + devices: iter + .into_iter() + .enumerate() + .map(|(i, d)| d.into_device(i as _)) + .collect(), + default_parallel_threshold: 4, + default_timeout: Duration::from_millis(20), + send_interval: Duration::from_millis(1), + receive_interval: Duration::from_millis(1), + timer_strategy: TimerStrategy::Spin(SpinSleeper::default()), + } + } + + /// Equivalent to [`Self::open_with_timeout`] with a timeout of [`DEFAULT_TIMEOUT`]. + pub async fn open( + self, + link_builder: B, + ) -> Result, AUTDError> { + self.open_with_timeout(link_builder, DEFAULT_TIMEOUT).await + } + + /// Opens a controller with a timeout. + /// + /// Opens link, and then initialize and synchronize the devices. The `timeout` is used to send data for initialization and synchronization. + pub async fn open_with_timeout( + self, + link_builder: B, + timeout: Duration, + ) -> Result, AUTDError> { + tracing::debug!("Opening a controller: {:?} (timeout = {:?})", self, timeout); + let geometry = Geometry::new(self.devices, self.default_parallel_threshold); + Controller { + link: link_builder.open(&geometry).await?, + tx_buf: vec![TxMessage::new_zeroed(); geometry.len()], // Do not use `num_devices` here because the devices may be disabled. + rx_buf: vec![RxMessage::new(0, 0); geometry.len()], + geometry, + timer: Timer { + send_interval: self.send_interval, + receive_interval: self.receive_interval, + strategy: self.timer_strategy, + default_timeout: self.default_timeout, + }, + } + .open_impl(timeout) + .await + } +} + +impl Controller { + /// Creates a new [`ControllerBuilder`] with the given devices. + #[must_use] + pub fn builder>(iter: F) -> ControllerBuilder { + ControllerBuilder::new(iter) + } +} + +#[cfg(test)] +mod tests { + use autd3_driver::{autd3_device::AUTD3, geometry::Point3}; + + use super::*; + + #[tokio::test] + async fn geometry() -> anyhow::Result<()> { + let autd = + ControllerBuilder::new([AUTD3::new(Point3::origin()), AUTD3::new(Point3::origin())]) + .open(crate::link::Nop::builder()) + .await?; + + assert_eq!(0, autd[0].idx()); + autd[0].iter().enumerate().for_each(|(i, tr)| { + assert_eq!(i, tr.idx()); + assert_eq!(0, tr.dev_idx()); + }); + + assert_eq!(1, autd[1].idx()); + autd[1].iter().enumerate().for_each(|(i, tr)| { + assert_eq!(i, tr.idx()); + assert_eq!(1, tr.dev_idx()); + }); + + Ok(()) + } +} diff --git a/autd3/src/async/controller/group.rs b/autd3/src/async/controller/group.rs new file mode 100644 index 00000000..4175e75c --- /dev/null +++ b/autd3/src/async/controller/group.rs @@ -0,0 +1,449 @@ +use std::{fmt::Debug, hash::Hash, time::Duration}; + +use autd3_driver::{ + datagram::Datagram, + error::AUTDDriverError, + firmware::operation::{Operation, OperationGenerator}, + geometry::Device, +}; +use itertools::Itertools; + +use super::{AsyncLink, Controller}; + +use tracing; + +/// A struct for grouping devices and sending different data to each group. See also [`Controller::group`]. +#[allow(clippy::type_complexity)] +pub struct Group<'a, K: PartialEq + Debug, L: AsyncLink> { + pub(crate) cnt: &'a mut Controller, + pub(crate) keys: Vec>, + pub(crate) done: Vec, + pub(crate) timeout: Option, + pub(crate) parallel_threshold: Option, + pub(crate) operations: Vec, Box)>>, +} + +impl<'a, K: PartialEq + Debug, L: AsyncLink> Group<'a, K, L> { + #[must_use] + pub(crate) fn new(cnt: &'a mut Controller, f: impl Fn(&Device) -> Option) -> Self { + let keys = cnt.geometry.devices().map(f).collect::>(); + let done = keys.iter().map(Option::is_none).collect(); + Self { + operations: cnt.geometry.devices().map(|_| None).collect(), + keys, + done, + cnt, + timeout: None, + parallel_threshold: None, + } + } + + /// Set the `data` to be sent to the devices corresponding to the `key`. + /// + /// # Errors + /// + /// - Returns [`AUTDDriverError::UnkownKey`] if the `key` is not specified in the [`Controller::group`]. + /// - Returns [`AUTDDriverError::KeyIsAlreadyUsed`] if the `key` is already used previous [`Group::set`]. + #[tracing::instrument(level = "debug", skip(self))] + pub fn set(self, key: K, data: D) -> Result + where + <::G as OperationGenerator>::O1: 'static, + <::G as OperationGenerator>::O2: 'static, + { + let Self { + keys, + mut done, + mut operations, + cnt, + timeout, + parallel_threshold, + } = self; + + if !keys + .iter() + .any(|k| k.as_ref().map(|kk| kk == &key).unwrap_or(false)) + { + return Err(AUTDDriverError::UnkownKey(format!("{:?}", key))); + } + + let timeout = timeout.into_iter().chain(data.timeout()).max(); + let parallel_threshold = parallel_threshold + .into_iter() + .chain(data.parallel_threshold()) + .min(); + + // set enable flag for each device + // This is not required for the operation except `Gain`s which cannot be calculated independently for each device, such as `autd3-gain-holo`. + let enable_store = cnt + .geometry + .iter() + .map(|dev| dev.enable) + .collect::>(); + cnt.geometry + .devices_mut() + .zip(keys.iter()) + .for_each(|(dev, k)| { + dev.enable = k.as_ref().is_some_and(|kk| kk == &key); + }); + let mut generator = data.operation_generator(&cnt.geometry)?; + cnt.geometry + .iter_mut() + .zip(enable_store) + .for_each(|(dev, enable)| { + dev.enable = enable; + }); + + operations + .iter_mut() + .zip(keys.iter()) + .zip(cnt.geometry.devices()) + .zip(done.iter_mut()) + .filter(|(((_, k), _), _)| k.as_ref().is_some_and(|kk| kk == &key)) + .try_for_each(|(((op, _), dev), done)| { + if *done { + return Err(AUTDDriverError::KeyIsAlreadyUsed(format!("{:?}", key))); + } + *done = true; + tracing::debug!("Generate operation for device {}", dev.idx()); + let (op1, op2) = generator.generate(dev); + *op = Some((Box::new(op1) as Box<_>, Box::new(op2) as Box<_>)); + Ok(()) + })?; + + Ok(Self { + cnt, + keys, + done, + timeout, + parallel_threshold, + operations, + }) + } + + /// Send the data to the devices. + /// + /// # Errors + /// + /// Returns [`AUTDDriverError::UnusedKey`] if the data is not specified for the key by [`Group::set`]. + #[tracing::instrument(level = "debug", skip(self))] + pub async fn send(self) -> Result<(), AUTDDriverError> { + let Self { + operations, + cnt, + keys, + done, + timeout, + parallel_threshold, + .. + } = self; + + if !done.iter().all(|&d| d) { + return Err(AUTDDriverError::UnusedKey( + keys.into_iter() + .zip(done.into_iter()) + .filter(|(_, d)| !*d) + .map(|(k, _)| format!("{:?}", k.unwrap())) + .join(", "), + )); + } + + cnt.link.trace(timeout, parallel_threshold); + cnt.timer + .send( + &cnt.geometry, + &mut cnt.tx_buf, + &mut cnt.rx_buf, + &mut cnt.link, + operations + .into_iter() + .map(|op| op.unwrap_or_default()) + .collect::>(), + timeout, + parallel_threshold, + ) + .await + } +} + +impl Controller { + /// Group the devices by given function and send different data to each group. + /// + /// If the key is `None`, nothing is done for the devices corresponding to the key. + /// + /// # Example + /// + /// ``` + /// # use autd3::prelude::*; + /// # tokio_test::block_on(async { + /// let mut autd = Controller::builder((0..3).map(|_| AUTD3::new(Point3::origin()))).open(Nop::builder()).await?; + /// + /// autd.group(|dev| match dev.idx() { + /// 0 => Some("static"), + /// 2 => Some("sine"), + /// _ => None, + /// }) + /// .set("static", Static::new())? + /// .set("sine", Sine::new(150 * Hz))? + /// .send().await?; + /// # Result::<(), AUTDError>::Ok(()) + /// # }); + /// ``` + #[must_use] + pub fn group Option>( + &mut self, + f: F, + ) -> Group { + Group::new(self, f) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + + use autd3_driver::{ + datagram::{GainSTM, SwapSegment}, + defined::Hz, + derive::*, + error::AUTDDriverError, + firmware::fpga::{Drive, EmitIntensity, Phase}, + }; + + use crate::{ + gain::{Null, Uniform}, + modulation::{Sine, Static}, + r#async::controller::tests::create_controller, + }; + + #[tokio::test] + async fn test_group() -> anyhow::Result<()> { + let mut autd = create_controller(4).await?; + + autd.send(Uniform::new(EmitIntensity::new(0xFF))).await?; + + autd.group(|dev| match dev.idx() { + 0 | 1 | 3 => Some(dev.idx()), + _ => None, + }) + .set(0, Null::new())? + .set(1, (Static::with_intensity(0x80), Null::new()))? + .set( + 3, + ( + Sine::new(150. * Hz), + GainSTM::new( + 1. * Hz, + [ + Uniform::new(EmitIntensity::new(0x80)), + Uniform::new(EmitIntensity::new(0x81)), + ] + .into_iter(), + )?, + ), + )? + .send() + .await?; + + assert_eq!( + vec![Drive::NULL; autd.geometry[0].num_transducers()], + autd.link[0].fpga().drives_at(Segment::S0, 0) + ); + + assert_eq!( + vec![Drive::NULL; autd.geometry[1].num_transducers()], + autd.link[1].fpga().drives_at(Segment::S0, 0) + ); + assert_eq!( + vec![0x80, 0x80], + autd.link[1].fpga().modulation_buffer(Segment::S0) + ); + + assert_eq!( + vec![ + Drive::new(Phase::ZERO, EmitIntensity::new(0xFF)); + autd.geometry[2].num_transducers() + ], + autd.link[2].fpga().drives_at(Segment::S0, 0) + ); + + assert_eq!( + *Sine::new(150. * Hz).calc()?, + autd.link[3].fpga().modulation_buffer(Segment::S0) + ); + assert_eq!( + vec![ + Drive::new(Phase::ZERO, EmitIntensity::new(0x80)); + autd.geometry[3].num_transducers() + ], + autd.link[3].fpga().drives_at(Segment::S0, 0) + ); + assert_eq!( + vec![ + Drive::new(Phase::ZERO, EmitIntensity::new(0x81)); + autd.geometry[3].num_transducers() + ], + autd.link[3].fpga().drives_at(Segment::S0, 1) + ); + + Ok(()) + } + + #[tokio::test] + async fn test_send_failed() -> anyhow::Result<()> { + let mut autd = create_controller(1).await?; + assert_eq!( + Ok(()), + autd.group(|dev| Some(dev.idx())) + .set(0, Null::new())? + .send() + .await + ); + + autd.link_mut().down(); + assert_eq!( + Err(AUTDDriverError::SendDataFailed), + autd.group(|dev| Some(dev.idx())) + .set(0, Null::new())? + .send() + .await + ); + + Ok(()) + } + + #[tokio::test] + async fn test_send_err() -> anyhow::Result<()> { + let mut autd = create_controller(2).await?; + + assert_eq!( + Err(AUTDDriverError::InvalidSegmentTransition), + autd.group(|dev| Some(dev.idx())) + .set(0, Null::new())? + .set( + 1, + SwapSegment::FociSTM(Segment::S1, TransitionMode::SyncIdx), + )? + .send() + .await + ); + + Ok(()) + } + + #[tokio::test] + async fn test_group_only_for_enabled() -> anyhow::Result<()> { + let mut autd = create_controller(2).await?; + + autd.geometry[0].enable = false; + + let check = Arc::new(Mutex::new([false; 2])); + autd.group(|dev| { + check.lock().unwrap()[dev.idx()] = true; + Some(dev.idx()) + }) + .set(1, Static::with_intensity(0x80))? + .send() + .await?; + + assert!(!check.lock().unwrap()[0]); + assert!(check.lock().unwrap()[1]); + + assert_eq!( + vec![0xFF, 0xFF], + autd.link[0].fpga().modulation_buffer(Segment::S0) + ); + assert_eq!( + vec![0x80, 0x80], + autd.link[1].fpga().modulation_buffer(Segment::S0) + ); + + Ok(()) + } + + #[derive(Gain, Debug)] + pub struct TestGain { + test: Arc>>, + } + + impl Gain for TestGain { + type G = Null; + + fn init( + self, + geometry: &Geometry, + _filter: Option<&HashMap>>, + ) -> Result { + geometry.iter().for_each(|dev| { + self.test.lock().unwrap()[dev.idx()] = dev.enable; + }); + Ok(Null::new()) + } + } + + #[tokio::test] + async fn test_group_only_for_enabled_gain() -> anyhow::Result<()> { + let mut autd = create_controller(3).await?; + + let test = Arc::new(Mutex::new(vec![false; 3])); + autd.group(|dev| match dev.idx() { + 0 | 2 => Some(0), + _ => None, + }) + .set(0, TestGain { test: test.clone() })? + .send() + .await?; + + assert!(test.lock().unwrap()[0]); + assert!(!test.lock().unwrap()[1]); + assert!(test.lock().unwrap()[2]); + + Ok(()) + } + + #[tokio::test] + async fn unknown_key() -> anyhow::Result<()> { + let mut autd = create_controller(2).await?; + + assert_eq!( + Some(AUTDDriverError::UnkownKey("2".to_owned())), + autd.group(|dev| Some(dev.idx())) + .set(0, Null::new())? + .set(2, Null::new()) + .err() + ); + + Ok(()) + } + + #[tokio::test] + async fn already_used_key() -> anyhow::Result<()> { + let mut autd = create_controller(2).await?; + + assert_eq!( + Some(AUTDDriverError::KeyIsAlreadyUsed("1".to_owned())), + autd.group(|dev| Some(dev.idx())) + .set(0, Null::new())? + .set(1, Null::new())? + .set(1, Null::new()) + .err() + ); + + Ok(()) + } + + #[tokio::test] + async fn unused_key() -> anyhow::Result<()> { + let mut autd = create_controller(3).await?; + + assert_eq!( + Some(AUTDDriverError::UnusedKey("0, 2".to_owned())), + autd.group(|dev| Some(dev.idx())) + .set(1, Null::new())? + .send() + .await + .err() + ); + + Ok(()) + } +} diff --git a/autd3/src/async/controller/mod.rs b/autd3/src/async/controller/mod.rs new file mode 100644 index 00000000..ac1eb5bb --- /dev/null +++ b/autd3/src/async/controller/mod.rs @@ -0,0 +1,541 @@ +mod builder; +mod group; +/// Utilities for periodic operations. +pub mod timer; + +use crate::{error::AUTDError, gain::Null, prelude::Static}; + +use std::time::Duration; + +use autd3_driver::{ + datagram::{Clear, Datagram, ForceFan, IntoDatagramWithTimeout, Silencer, Synchronize}, + derive::Builder, + error::AUTDDriverError, + firmware::{ + cpu::{check_if_msg_is_processed, RxMessage, TxMessage}, + fpga::FPGAState, + operation::{FirmwareVersionType, OperationHandler}, + version::FirmwareVersion, + }, + geometry::{Device, Geometry}, + link::AsyncLink, +}; + +use timer::Timer; +use tracing; + +pub use builder::ControllerBuilder; +pub use group::Group; + +use derive_more::{Deref, DerefMut}; + +/// A controller for the AUTD devices. +/// +/// All operations to the devices are done through this struct. +#[derive(Builder, Deref, DerefMut)] +pub struct Controller { + #[get(ref, ref_mut, no_doc)] + link: L, + #[get(ref, ref_mut, no_doc)] + #[deref] + #[deref_mut] + geometry: Geometry, + tx_buf: Vec, + rx_buf: Vec, + #[get(ref, no_doc)] + timer: Timer, +} + +impl Controller { + /// Sends a data to the devices. + /// + /// If the [`Datagram::timeout`] value is + /// - greater than 0, this function waits until the sent data is processed by the device or the specified timeout time elapses. If it cannot be confirmed that the sent data has been processed by the device, [`AUTDDriverError::ConfirmResponseFailed`] is returned. + /// - 0, the `send` function does not check whether the sent data has been processed by the device. + /// + /// The calculation of each [`Datagram`] is executed in parallel for each device if the number of enabled devices is greater than the [`Datagram::parallel_threshold`]. + #[tracing::instrument(level = "debug", skip(self))] + pub async fn send(&mut self, s: impl Datagram) -> Result<(), AUTDDriverError> { + let timeout = s.timeout(); + let parallel_threshold = s.parallel_threshold(); + self.link.trace(timeout, parallel_threshold); + let generator = s.operation_generator(&self.geometry)?; + self.timer + .send( + &self.geometry, + &mut self.tx_buf, + &mut self.rx_buf, + &mut self.link, + OperationHandler::generate(generator, &self.geometry), + timeout, + parallel_threshold, + ) + .await + } + + pub(crate) async fn open_impl(mut self, timeout: Duration) -> Result { + let timeout = Some(timeout); + + #[cfg(feature = "dynamic_freq")] + { + tracing::debug!( + "Configuring ultrasound frequency to {:?}", + autd3_driver::defined::ultrasound_freq() + ); + self.send(autd3_driver::datagram::ConfigureFPGAClock::new().with_timeout(timeout)) + .await?; + } + + // If the device is used continuously without powering off, the first data may be ignored because the first msg_id equals to the remaining msg_id in the device. + // Therefore, send a meaningless data (here, we use `ForceFan` because it is the lightest). + let _ = self + .send(ForceFan::new(|_| false).with_timeout(timeout)) + .await; + + self.send((Clear::new(), Synchronize::new()).with_timeout(timeout)) + .await?; + Ok(self) + } + + async fn close_impl(&mut self) -> Result<(), AUTDDriverError> { + tracing::info!("Closing controller"); + + if !self.link.is_open() { + tracing::warn!("Link is already closed"); + return Ok(()); + } + + self.geometry.iter_mut().for_each(|dev| dev.enable = true); + [ + self.send(Silencer::default().with_strict_mode(false)).await, + self.send((Static::new(), Null::default())).await, + self.send(Clear::new()).await, + self.link.close().await, + ] + .into_iter() + .try_fold((), |_, x| x) + } + + /// Closes the controller. + #[tracing::instrument(level = "debug", skip(self))] + pub async fn close(mut self) -> Result<(), AUTDDriverError> { + self.close_impl().await + } + + async fn fetch_firminfo(&mut self, ty: FirmwareVersionType) -> Result, AUTDError> { + self.send(ty).await.map_err(|e| { + tracing::error!("Fetch firmware info failed: {:?}", e); + AUTDError::ReadFirmwareVersionFailed( + check_if_msg_is_processed(&self.tx_buf, &self.rx_buf).collect(), + ) + })?; + Ok(self.rx_buf.iter().map(|rx| rx.data()).collect()) + } + + /// Returns the firmware version of the devices. + pub async fn firmware_version(&mut self) -> Result, AUTDError> { + use autd3_driver::firmware::version::{CPUVersion, FPGAVersion, Major, Minor}; + use FirmwareVersionType::*; + + let cpu_major = self.fetch_firminfo(CPUMajor).await?; + let cpu_minor = self.fetch_firminfo(CPUMinor).await?; + let fpga_major = self.fetch_firminfo(FPGAMajor).await?; + let fpga_minor = self.fetch_firminfo(FPGAMinor).await?; + let fpga_functions = self.fetch_firminfo(FPGAFunctions).await?; + self.fetch_firminfo(Clear).await?; + + Ok(self + .geometry + .devices() + .map(|dev| { + FirmwareVersion::new( + dev.idx(), + CPUVersion::new(Major(cpu_major[dev.idx()]), Minor(cpu_minor[dev.idx()])), + FPGAVersion::new( + Major(fpga_major[dev.idx()]), + Minor(fpga_minor[dev.idx()]), + fpga_functions[dev.idx()], + ), + ) + }) + .collect()) + } + + /// Returns the FPGA state of the devices. + /// + /// To get the state of devices, enable reads FPGA state mode by [`ReadsFPGAState`] before calling this method. + /// The returned value is [`None`] if the reads FPGA state mode is disabled for the device. + /// + /// # Examples + /// + /// ``` + /// # use autd3::prelude::*; + /// # tokio_test::block_on(async { + /// let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(Nop::builder()).await?; + /// + /// autd.send(ReadsFPGAState::new(|_| true)).await?; + /// + /// let states = autd.fpga_state().await?; + /// # Result::<(), AUTDError>::Ok(()) + /// # }); + /// ``` + /// + /// [`ReadsFPGAState`]: autd3_driver::datagram::ReadsFPGAState + pub async fn fpga_state(&mut self) -> Result>, AUTDError> { + if !self.link.is_open() { + return Err(AUTDError::Driver( + autd3_driver::error::AUTDDriverError::LinkClosed, + )); + } + if self.link.receive(&mut self.rx_buf).await? { + Ok(self.rx_buf.iter().map(Option::from).collect()) + } else { + Err(AUTDError::ReadFPGAStateFailed) + } + } +} + +impl<'a, L: AsyncLink> IntoIterator for &'a Controller { + type Item = &'a Device; + type IntoIter = std::slice::Iter<'a, Device>; + + fn into_iter(self) -> Self::IntoIter { + self.geometry.iter() + } +} + +impl<'a, L: AsyncLink> IntoIterator for &'a mut Controller { + type Item = &'a mut Device; + type IntoIter = std::slice::IterMut<'a, Device>; + + fn into_iter(self) -> Self::IntoIter { + self.geometry.iter_mut() + } +} + +#[cfg(feature = "async-trait")] +impl Controller { + /// Converts `Controller` into a `Controller>`. + pub fn into_boxed_link(self) -> Controller> { + let cnt = std::mem::ManuallyDrop::new(self); + let link = unsafe { std::ptr::read(&cnt.link) }; + let geometry = unsafe { std::ptr::read(&cnt.geometry) }; + let tx_buf = unsafe { std::ptr::read(&cnt.tx_buf) }; + let rx_buf = unsafe { std::ptr::read(&cnt.rx_buf) }; + let timer = unsafe { std::ptr::read(&cnt.timer) }; + Controller { + link: Box::new(link) as _, + geometry, + tx_buf, + rx_buf, + timer, + } + } + + /// Converts `Controller>` into a `Controller`. + /// + /// # Safety + /// + /// This function must be used only when converting an instance created by [`Controller::into_boxed_link`] back to the original [`Controller`]. + /// + pub unsafe fn from_boxed_link(cnt: Controller>) -> Controller { + let cnt = std::mem::ManuallyDrop::new(cnt); + let link = unsafe { std::ptr::read(&cnt.link) }; + let geometry = unsafe { std::ptr::read(&cnt.geometry) }; + let tx_buf = unsafe { std::ptr::read(&cnt.tx_buf) }; + let rx_buf = unsafe { std::ptr::read(&cnt.rx_buf) }; + let timer = unsafe { std::ptr::read(&cnt.timer) }; + Controller { + link: unsafe { *Box::from_raw(Box::into_raw(link) as *mut L) }, + geometry, + tx_buf, + rx_buf, + timer, + } + } +} + +impl Drop for Controller { + fn drop(&mut self) { + if !self.link.is_open() { + return; + } + match tokio::runtime::Handle::current().runtime_flavor() { + tokio::runtime::RuntimeFlavor::CurrentThread => {} + tokio::runtime::RuntimeFlavor::MultiThread => tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + let _ = self.close_impl().await; + }); + }), + _ => unimplemented!(), + } + } +} + +#[cfg(test)] +mod tests { + use autd3_driver::{ + autd3_device::AUTD3, + datagram::{GainSTM, ReadsFPGAState}, + defined::{mm, Hz}, + derive::{EmitIntensity, Gain, GainContext, GainContextGenerator, Modulation, Segment}, + geometry::Point3, + }; + + use spin_sleep::SpinSleeper; + use timer::*; + + use crate::{gain::Uniform, link::Audit, prelude::Sine}; + + use super::*; + + // GRCOV_EXCL_START + pub async fn create_controller(dev_num: usize) -> anyhow::Result> { + Ok( + Controller::builder((0..dev_num).map(|_| AUTD3::new(Point3::origin()))) + .open(Audit::builder()) + .await?, + ) + } + // GRCOV_EXCL_STOP + + #[rstest::rstest] + #[case(TimerStrategy::Std(StdSleeper::default()))] + #[case(TimerStrategy::Spin(SpinSleeper::default()))] + #[case(TimerStrategy::Async(AsyncSleeper::default()))] + #[cfg_attr(target_os = "windows", case(TimerStrategy::Waitable(WaitableSleeper::new().unwrap())))] + #[tokio::test(flavor = "multi_thread")] + async fn open_with_timer(#[case] strategy: TimerStrategy) { + assert!(Controller::builder([AUTD3::new(Point3::origin())]) + .with_timer_strategy(strategy) + .open(Audit::builder()) + .await + .is_ok()); + } + + #[tokio::test(flavor = "multi_thread")] + async fn open_failed() { + assert_eq!( + Some(AUTDError::Driver(AUTDDriverError::SendDataFailed)), + Controller::builder([AUTD3::new(Point3::origin())]) + .open(Audit::builder().with_down(true)) + .await + .err() + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn send() -> anyhow::Result<()> { + let mut autd = create_controller(1).await?; + autd.send(( + Sine::new(150. * Hz), + GainSTM::new( + 1. * Hz, + [ + Uniform::new(EmitIntensity::new(0x80)), + Uniform::new(EmitIntensity::new(0x81)), + ] + .into_iter(), + )?, + )) + .await?; + + autd.iter().try_for_each(|dev| { + assert_eq!( + *Sine::new(150. * Hz).calc()?, + autd.link[dev.idx()].fpga().modulation_buffer(Segment::S0) + ); + let f = Uniform::new(EmitIntensity::new(0x80)) + .init(&autd.geometry, None)? + .generate(dev); + assert_eq!( + dev.iter().map(|tr| f.calc(tr)).collect::>(), + autd.link[dev.idx()].fpga().drives_at(Segment::S0, 0) + ); + let f = Uniform::new(EmitIntensity::new(0x81)) + .init(&autd.geometry, None)? + .generate(dev); + assert_eq!( + dev.iter().map(|tr| f.calc(tr)).collect::>(), + autd.link[dev.idx()].fpga().drives_at(Segment::S0, 1) + ); + anyhow::Ok(()) + })?; + + autd.close().await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn firmware_version() -> anyhow::Result<()> { + use autd3_driver::firmware::version::{CPUVersion, FPGAVersion}; + + let mut autd = create_controller(1).await?; + assert_eq!( + vec![FirmwareVersion::new( + 0, + CPUVersion::new( + FirmwareVersion::LATEST_VERSION_NUM_MAJOR, + FirmwareVersion::LATEST_VERSION_NUM_MINOR + ), + FPGAVersion::new( + FirmwareVersion::LATEST_VERSION_NUM_MAJOR, + FirmwareVersion::LATEST_VERSION_NUM_MINOR, + FPGAVersion::ENABLED_EMULATOR_BIT + ) + )], + autd.firmware_version().await? + ); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn firmware_version_err() -> anyhow::Result<()> { + let mut autd = create_controller(2).await?; + autd.link_mut().break_down(); + assert_eq!( + Err(AUTDError::ReadFirmwareVersionFailed(vec![false, false])), + autd.firmware_version().await + ); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn close() -> anyhow::Result<()> { + { + let mut autd = create_controller(1).await?; + autd.close_impl().await?; + autd.close().await?; + } + + { + let mut autd = create_controller(1).await?; + autd.link_mut().break_down(); + assert_eq!( + Err(AUTDDriverError::LinkError("broken".to_owned())), + autd.close().await + ); + } + + { + let mut autd = create_controller(1).await?; + autd.link_mut().down(); + assert_eq!(Err(AUTDDriverError::SendDataFailed), autd.close().await); + } + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn fpga_state() -> anyhow::Result<()> { + let mut autd = + Controller::builder([AUTD3::new(Point3::origin()), AUTD3::new(Point3::origin())]) + .open(Audit::builder()) + .await?; + + autd.send(ReadsFPGAState::new(|_| true)).await?; + { + autd.link_mut()[0].fpga_mut().assert_thermal_sensor(); + + let states = autd.fpga_state().await?; + assert_eq!(2, states.len()); + assert!(states[0] + .ok_or(anyhow::anyhow!("state shouldn't be None here"))? + .is_thermal_assert()); + assert!(!states[1] + .ok_or(anyhow::anyhow!("state shouldn't be None here"))? + .is_thermal_assert()); + } + + { + autd.link_mut()[0].fpga_mut().deassert_thermal_sensor(); + autd.link_mut()[1].fpga_mut().assert_thermal_sensor(); + + let states = autd.fpga_state().await?; + assert_eq!(2, states.len()); + assert!(!states[0] + .ok_or(anyhow::anyhow!("state shouldn't be None here"))? + .is_thermal_assert()); + assert!(states[1] + .ok_or(anyhow::anyhow!("state shouldn't be None here"))? + .is_thermal_assert()); + } + + autd.send(ReadsFPGAState::new(|dev| dev.idx() == 1)).await?; + { + let states = autd.fpga_state().await?; + assert_eq!(2, states.len()); + assert!(states[0].is_none()); + assert!(states[1] + .ok_or(anyhow::anyhow!("state shouldn't be None here"))? + .is_thermal_assert()); + } + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn into_iter() -> anyhow::Result<()> { + let mut autd = create_controller(1).await?; + + for dev in &mut autd { + dev.sound_speed = 300e3 * mm; + } + + for dev in &autd { + assert_eq!(300e3 * mm, dev.sound_speed); + } + + Ok(()) + } + + #[cfg(feature = "async-trait")] + #[tokio::test(flavor = "multi_thread")] + async fn into_boxed_link() -> anyhow::Result<()> { + let autd = create_controller(1).await?; + + let mut autd = autd.into_boxed_link(); + + autd.send(( + Sine::new(150. * Hz), + GainSTM::new( + 1. * Hz, + [ + Uniform::new(EmitIntensity::new(0x80)), + Uniform::new(EmitIntensity::new(0x81)), + ] + .into_iter(), + )?, + )) + .await?; + + let autd = unsafe { Controller::::from_boxed_link(autd) }; + + autd.iter().try_for_each(|dev| { + assert_eq!( + *Sine::new(150. * Hz).calc()?, + autd.link[dev.idx()].fpga().modulation_buffer(Segment::S0) + ); + let f = Uniform::new(EmitIntensity::new(0x80)) + .init(&autd.geometry, None)? + .generate(dev); + assert_eq!( + dev.iter().map(|tr| f.calc(tr)).collect::>(), + autd.link[dev.idx()].fpga().drives_at(Segment::S0, 0) + ); + let f = Uniform::new(EmitIntensity::new(0x81)) + .init(&autd.geometry, None)? + .generate(dev); + assert_eq!( + dev.iter().map(|tr| f.calc(tr)).collect::>(), + autd.link[dev.idx()].fpga().drives_at(Segment::S0, 1) + ); + anyhow::Ok(()) + })?; + + autd.close().await?; + + Ok(()) + } +} diff --git a/autd3/src/async/controller/timer/mod.rs b/autd3/src/async/controller/timer/mod.rs new file mode 100644 index 00000000..9787667b --- /dev/null +++ b/autd3/src/async/controller/timer/mod.rs @@ -0,0 +1,387 @@ +#![allow(clippy::too_many_arguments)] + +pub(crate) mod sleep; + +use sleep::Sleeper; +#[cfg(target_os = "windows")] +pub use sleep::WaitableSleeper; +pub use sleep::{AsyncSleeper, SpinSleeper, StdSleeper}; + +use std::time::{Duration, Instant}; + +use autd3_driver::{ + derive::{Builder, Geometry}, + error::AUTDDriverError, + firmware::{ + cpu::{check_if_msg_is_processed, RxMessage, TxMessage}, + operation::{Operation, OperationHandler}, + }, + link::AsyncLink, +}; + +use itertools::Itertools; + +/// Enum representing sleeping strategies for the timer. +/// +/// The [`TimerStrategy`] enum provides various strategies for implementing a timer +/// with different sleeping mechanisms. This allows for flexibility in how the timer +/// behaves depending on the target operating system and specific requirements. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub enum TimerStrategy { + /// Uses [`std::thread::sleep`]. + Std(StdSleeper), + /// Uses a [waitable timer](https://learn.microsoft.com/en-us/windows/win32/sync/waitable-timer-objects) available only on Windows. + #[cfg(target_os = "windows")] + Waitable(WaitableSleeper), + /// Uses a [spin_sleep](https://crates.io/crates/spin_sleep) crate. + Spin(SpinSleeper), + /// Uses [`tokio::time::sleep_until`]. + Async(AsyncSleeper), +} + +/// A struct managing the timing of sending and receiving operations. +// Timer can be generic, but in that case, `Controller` must also be generic. To avoid this, `TimerStrategy` is an enum. +#[derive(Builder)] +pub struct Timer { + #[get] + /// The duration between sending operations. + pub(crate) send_interval: Duration, + #[get] + /// The duration between receiving operations. + pub(crate) receive_interval: Duration, + #[get] + /// The strategy used for timing operations. + pub(crate) strategy: TimerStrategy, + #[get] + /// The default timeout when no timeout is specified for the [`Datagram`] to be sent. + /// + /// [`Datagram`]: autd3_driver::datagram::Datagram + pub(crate) default_timeout: Duration, +} + +impl Timer { + pub(crate) async fn send( + &self, + geometry: &Geometry, + tx: &mut [TxMessage], + rx: &mut [RxMessage], + link: &mut impl AsyncLink, + operations: Vec<(impl Operation, impl Operation)>, + timeout: Option, + parallel_threshold: Option, + ) -> Result<(), AUTDDriverError> { + let timeout = timeout.unwrap_or(self.default_timeout); + let parallel = geometry.parallel(parallel_threshold); + tracing::debug!("timeout: {:?}, parallel: {:?}", timeout, parallel); + + match &self.strategy { + TimerStrategy::Std(sleeper) => { + self._send( + sleeper, geometry, tx, rx, link, operations, timeout, parallel, + ) + .await + } + TimerStrategy::Spin(sleeper) => { + self._send( + sleeper, geometry, tx, rx, link, operations, timeout, parallel, + ) + .await + } + TimerStrategy::Async(sleeper) => { + self._send( + sleeper, geometry, tx, rx, link, operations, timeout, parallel, + ) + .await + } + #[cfg(target_os = "windows")] + TimerStrategy::Waitable(sleeper) => { + self._send( + sleeper, geometry, tx, rx, link, operations, timeout, parallel, + ) + .await + } + } + } + + async fn _send( + &self, + sleeper: &S, + geometry: &Geometry, + tx: &mut [TxMessage], + rx: &mut [RxMessage], + link: &mut impl AsyncLink, + mut operations: Vec<(impl Operation, impl Operation)>, + timeout: Duration, + parallel: bool, + ) -> Result<(), AUTDDriverError> { + link.update(geometry).await?; + + // We prioritize average behavior for the transmission timing. That is, not the interval from the previous transmission, but ensuring that T/`send_interval` transmissions are performed in a sufficiently long time T. + // For example, if the `send_interval` is 1ms and it takes 1.5ms to transmit due to some reason, the next transmission will be performed not 1ms later but 0.5ms later. + let mut send_timing = Instant::now(); + loop { + OperationHandler::pack(&mut operations, geometry, tx, parallel)?; + + self.send_receive(sleeper, tx, rx, link, timeout).await?; + + if OperationHandler::is_done(&operations) { + return Ok(()); + } + + send_timing += self.send_interval; + sleeper.sleep_until(send_timing).await; + } + } + + async fn send_receive( + &self, + sleeper: &impl Sleeper, + tx: &[TxMessage], + rx: &mut [RxMessage], + link: &mut impl AsyncLink, + timeout: Duration, + ) -> Result<(), AUTDDriverError> { + if !link.is_open() { + return Err(AUTDDriverError::LinkClosed); + } + + tracing::trace!("send: {}", tx.iter().join(", ")); + if !link.send(tx).await? { + return Err(AUTDDriverError::SendDataFailed); + } + self.wait_msg_processed(sleeper, tx, rx, link, timeout) + .await + } + + async fn wait_msg_processed( + &self, + sleeper: &S, + tx: &[TxMessage], + rx: &mut [RxMessage], + link: &mut impl AsyncLink, + timeout: Duration, + ) -> Result<(), AUTDDriverError> { + let start = Instant::now(); + let mut receive_timing = start; + loop { + if !link.is_open() { + return Err(AUTDDriverError::LinkClosed); + } + let res = link.receive(rx).await?; + tracing::trace!("recv: {}", rx.iter().join(", ")); + + if res && check_if_msg_is_processed(tx, rx).all(std::convert::identity) { + return Ok(()); + } + if start.elapsed() > timeout { + break; + } + receive_timing += self.receive_interval; + sleeper.sleep_until(receive_timing).await; + } + rx.iter() + .try_fold((), |_, r| Result::<(), AUTDDriverError>::from(r)) + .and_then(|e| { + if timeout == Duration::ZERO { + Ok(()) + } else { + tracing::error!("Failed to confirm the response from the device: {:?}", e); + Err(AUTDDriverError::ConfirmResponseFailed) + } + }) + } +} + +#[cfg(test)] +mod tests { + use zerocopy::FromZeros; + + use super::*; + + struct MockLink { + pub is_open: bool, + pub send_cnt: usize, + pub recv_cnt: usize, + pub down: bool, + } + + #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] + impl AsyncLink for MockLink { + async fn close(&mut self) -> Result<(), AUTDDriverError> { + self.is_open = false; + Ok(()) + } + + async fn send(&mut self, _: &[TxMessage]) -> Result { + self.send_cnt += 1; + Ok(!self.down) + } + + async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + if self.recv_cnt > 10 { + return Err(AUTDDriverError::LinkError("too many".to_owned())); + } + + self.recv_cnt += 1; + rx.iter_mut() + .for_each(|r| *r = RxMessage::new(r.data(), self.recv_cnt as u8)); + + Ok(!self.down) + } + + fn is_open(&self) -> bool { + self.is_open + } + } + + #[tokio::test] + async fn test_close() -> anyhow::Result<()> { + let mut link = MockLink { + is_open: true, + send_cnt: 0, + recv_cnt: 0, + down: false, + }; + + assert!(link.is_open()); + + link.close().await?; + + assert!(!link.is_open()); + + Ok(()) + } + + #[rstest::rstest] + #[case(TimerStrategy::Std(StdSleeper::default()), StdSleeper::default())] + #[case(TimerStrategy::Spin(SpinSleeper::default()), SpinSleeper::default())] + #[case(TimerStrategy::Async(AsyncSleeper::default()), AsyncSleeper::default())] + #[cfg_attr(target_os = "windows", case(TimerStrategy::Waitable(WaitableSleeper::new().unwrap()), WaitableSleeper::new().unwrap()))] + #[tokio::test] + async fn test_send_receive(#[case] strategy: TimerStrategy, #[case] sleeper: impl Sleeper) { + let mut link = MockLink { + is_open: true, + send_cnt: 0, + recv_cnt: 0, + down: false, + }; + + let tx = vec![]; + let mut rx = Vec::new(); + + let timer = Timer { + send_interval: Duration::from_millis(1), + receive_interval: Duration::from_millis(1), + strategy, + default_timeout: Duration::ZERO, + }; + + assert_eq!( + timer + .send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO) + .await, + Ok(()) + ); + + link.is_open = false; + assert_eq!( + timer + .send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO) + .await, + Err(AUTDDriverError::LinkClosed) + ); + + link.is_open = true; + link.down = true; + assert_eq!( + timer + .send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO) + .await, + Err(AUTDDriverError::SendDataFailed) + ); + + link.down = false; + assert_eq!( + timer + .send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(1)) + .await, + Ok(()) + ); + } + + #[rstest::rstest] + #[case(TimerStrategy::Std(StdSleeper::default()), StdSleeper::default())] + #[case(TimerStrategy::Spin(SpinSleeper::default()), SpinSleeper::default())] + #[case(TimerStrategy::Async(AsyncSleeper::default()), AsyncSleeper::default())] + #[cfg_attr(target_os = "windows", case(TimerStrategy::Waitable(WaitableSleeper::new().unwrap()), WaitableSleeper::new().unwrap()))] + #[tokio::test] + async fn test_wait_msg_processed( + #[case] strategy: TimerStrategy, + #[case] sleeper: impl Sleeper, + ) { + let mut link = MockLink { + is_open: true, + send_cnt: 0, + recv_cnt: 0, + down: false, + }; + + let mut tx = vec![TxMessage::new_zeroed(); 1]; + tx[0].header_mut().msg_id = 2; + let mut rx = vec![RxMessage::new(0, 0)]; + + let timer = Timer { + send_interval: Duration::from_millis(1), + receive_interval: Duration::from_millis(1), + strategy, + default_timeout: Duration::ZERO, + }; + + assert_eq!( + timer + .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)) + .await, + Ok(()) + ); + + link.recv_cnt = 0; + link.is_open = false; + assert_eq!( + Err(AUTDDriverError::LinkClosed), + timer + .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)) + .await + ); + + link.recv_cnt = 0; + link.is_open = true; + link.down = true; + assert_eq!( + Err(AUTDDriverError::ConfirmResponseFailed), + timer + .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)) + .await, + ); + + link.recv_cnt = 0; + link.is_open = true; + link.down = true; + assert_eq!( + Ok(()), + timer + .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO) + .await, + ); + + link.down = false; + link.recv_cnt = 0; + tx[0].header_mut().msg_id = 20; + assert_eq!( + Err(AUTDDriverError::LinkError("too many".to_owned())), + timer + .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_secs(10)) + .await + ); + } +} diff --git a/autd3/src/async/controller/timer/sleep.rs b/autd3/src/async/controller/timer/sleep.rs new file mode 100644 index 00000000..39b3a027 --- /dev/null +++ b/autd3/src/async/controller/timer/sleep.rs @@ -0,0 +1,152 @@ +use std::time::Instant; + +use autd3_driver::utils::timer::TimerResolutionGurad; +pub use spin_sleep::SpinSleeper; + +pub(crate) trait Sleeper { + fn sleep_until(&self, deadline: Instant) -> impl std::future::Future; +} + +/// See [`TimerStrategy`] for more details. +/// +/// [`TimerStrategy`]: super::TimerStrategy +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct StdSleeper { + /// An optional timer resolution in milliseconds for Windows. The default is `Some(1)`. + pub timer_resolution: Option, +} + +impl Default for StdSleeper { + fn default() -> Self { + Self { + timer_resolution: Some(std::num::NonZeroU32::MIN), + } + } +} + +impl Sleeper for StdSleeper { + async fn sleep_until(&self, deadline: Instant) { + let _timer_guard = TimerResolutionGurad::new(self.timer_resolution); + std::thread::sleep(deadline - Instant::now()); + } +} + +impl Sleeper for SpinSleeper { + async fn sleep_until(&self, deadline: Instant) { + self.sleep(deadline - Instant::now()); + } +} + +/// See [`TimerStrategy`] for more details. +/// +/// [`TimerStrategy`]: super::TimerStrategy +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AsyncSleeper { + /// An optional timer resolution in milliseconds for Windows. The default is `Some(1)`. + pub timer_resolution: Option, +} + +impl Default for AsyncSleeper { + fn default() -> Self { + Self { + timer_resolution: Some(std::num::NonZeroU32::MIN), + } + } +} + +impl Sleeper for AsyncSleeper { + async fn sleep_until(&self, deadline: Instant) { + let _timer_guard = TimerResolutionGurad::new(self.timer_resolution); + tokio::time::sleep_until(tokio::time::Instant::from_std(deadline)).await; + } +} + +#[cfg(target_os = "windows")] +pub use win::WaitableSleeper; + +#[cfg(target_os = "windows")] +mod win { + use super::*; + + /// See [`TimerStrategy`] for more details. + /// + /// [`TimerStrategy`]: super::super::TimerStrategy + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub struct WaitableSleeper { + handle: windows::Win32::Foundation::HANDLE, + } + + unsafe impl Send for WaitableSleeper {} + unsafe impl Sync for WaitableSleeper {} + + impl WaitableSleeper { + /// Creates a new [`WaitableSleeper`]. + /// + /// # Errors + /// + /// See [`CreateWaitableTimerExW`] for more details. + /// + /// [`CreateWaitableTimerExW`]: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createwaitabletimerexw + pub fn new() -> windows::core::Result { + Ok(Self { + handle: unsafe { + windows::Win32::System::Threading::CreateWaitableTimerExW( + None, + None, + windows::Win32::System::Threading::CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + windows::Win32::System::Threading::TIMER_ALL_ACCESS.0, + )? + }, + }) + } + } + + impl Sleeper for WaitableSleeper { + async fn sleep_until(&self, deadline: Instant) { + unsafe { + let time = deadline - Instant::now(); + if time.is_zero() { + return; + } + // The unit of SetWaitableTimer is 100ns and negative value means relative time. + // See [SetWaitableTimer](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setwaitabletimer) for more details. + let duetime = (time.as_nanos() / 100) as i64; + let duetime = -duetime; + let set_and_wait = || { + if let Err(e) = windows::Win32::System::Threading::SetWaitableTimer( + self.handle, + &duetime, + 0, + None, + None, + false, + ) { + tracing::warn!( + "SetWaitableTimer failed: {:?}, fallback to std::thread::sleep...", + e + ); + return false; + } + if windows::Win32::System::Threading::WaitForSingleObject( + self.handle, + windows::Win32::System::Threading::INFINITE, + ) == windows::Win32::Foundation::WAIT_FAILED + { + tracing::warn!( + "WaitForSingleObject failed: {:?}, fallback to std::thread::sleep...", + windows::Win32::Foundation::GetLastError() + ); + return false; + } + true + }; + if !set_and_wait() { + let _timer_guard = super::TimerResolutionGurad::new(Some( + std::num::NonZeroU32::new(1).unwrap(), + )); + std::thread::sleep(time); + } + } + } + } +} diff --git a/autd3/src/async/mod.rs b/autd3/src/async/mod.rs new file mode 100644 index 00000000..472e5a7f --- /dev/null +++ b/autd3/src/async/mod.rs @@ -0,0 +1,4 @@ +/// Asyncronous [`Controller`] module. +pub mod controller; + +pub use controller::Controller; diff --git a/autd3/src/controller/builder.rs b/autd3/src/controller/builder.rs index 38f9046c..d4d93bb2 100644 --- a/autd3/src/controller/builder.rs +++ b/autd3/src/controller/builder.rs @@ -4,7 +4,7 @@ use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, geometry::{Device, Geometry, IntoDevice}, - link::AsyncLinkBuilder, + link::LinkBuilder, }; use derive_more::Debug; @@ -64,17 +64,14 @@ impl ControllerBuilder { } /// Equivalent to [`Self::open_with_timeout`] with a timeout of [`DEFAULT_TIMEOUT`]. - pub async fn open( - self, - link_builder: B, - ) -> Result, AUTDError> { - self.open_with_timeout(link_builder, DEFAULT_TIMEOUT).await + pub fn open(self, link_builder: B) -> Result, AUTDError> { + self.open_with_timeout(link_builder, DEFAULT_TIMEOUT) } /// Opens a controller with a timeout. /// /// Opens link, and then initialize and synchronize the devices. The `timeout` is used to send data for initialization and synchronization. - pub async fn open_with_timeout( + pub fn open_with_timeout( self, link_builder: B, timeout: Duration, @@ -82,7 +79,7 @@ impl ControllerBuilder { tracing::debug!("Opening a controller: {:?} (timeout = {:?})", self, timeout); let geometry = Geometry::new(self.devices, self.default_parallel_threshold); Controller { - link: link_builder.open(&geometry).await?, + link: link_builder.open(&geometry)?, tx_buf: vec![TxMessage::new_zeroed(); geometry.len()], // Do not use `num_devices` here because the devices may be disabled. rx_buf: vec![RxMessage::new(0, 0); geometry.len()], geometry, @@ -94,7 +91,6 @@ impl ControllerBuilder { }, } .open_impl(timeout) - .await } } @@ -112,12 +108,11 @@ mod tests { use super::*; - #[tokio::test] - async fn geometry() -> anyhow::Result<()> { + #[test] + fn geometry() -> anyhow::Result<()> { let autd = ControllerBuilder::new([AUTD3::new(Point3::origin()), AUTD3::new(Point3::origin())]) - .open(crate::link::Nop::builder()) - .await?; + .open(crate::link::Nop::builder())?; assert_eq!(0, autd[0].idx()); autd[0].iter().enumerate().for_each(|(i, tr)| { diff --git a/autd3/src/controller/group.rs b/autd3/src/controller/group.rs index 29f24710..23659d12 100644 --- a/autd3/src/controller/group.rs +++ b/autd3/src/controller/group.rs @@ -8,13 +8,13 @@ use autd3_driver::{ }; use itertools::Itertools; -use super::{AsyncLink, Controller}; +use super::{Controller, Link}; use tracing; /// A struct for grouping devices and sending different data to each group. See also [`Controller::group`]. #[allow(clippy::type_complexity)] -pub struct Group<'a, K: PartialEq + Debug, L: AsyncLink> { +pub struct Group<'a, K: PartialEq + Debug, L: Link> { pub(crate) cnt: &'a mut Controller, pub(crate) keys: Vec>, pub(crate) done: Vec, @@ -23,7 +23,7 @@ pub struct Group<'a, K: PartialEq + Debug, L: AsyncLink> { pub(crate) operations: Vec, Box)>>, } -impl<'a, K: PartialEq + Debug, L: AsyncLink> Group<'a, K, L> { +impl<'a, K: PartialEq + Debug, L: Link> Group<'a, K, L> { #[must_use] pub(crate) fn new(cnt: &'a mut Controller, f: impl Fn(&Device) -> Option) -> Self { let keys = cnt.geometry.devices().map(f).collect::>(); @@ -126,7 +126,7 @@ impl<'a, K: PartialEq + Debug, L: AsyncLink> Group<'a, K, L> { /// /// Returns [`AUTDDriverError::UnusedKey`] if the data is not specified for the key by [`Group::set`]. #[tracing::instrument(level = "debug", skip(self))] - pub async fn send(self) -> Result<(), AUTDDriverError> { + pub fn send(self) -> Result<(), AUTDDriverError> { let Self { operations, cnt, @@ -148,24 +148,22 @@ impl<'a, K: PartialEq + Debug, L: AsyncLink> Group<'a, K, L> { } cnt.link.trace(timeout, parallel_threshold); - cnt.timer - .send( - &cnt.geometry, - &mut cnt.tx_buf, - &mut cnt.rx_buf, - &mut cnt.link, - operations - .into_iter() - .map(|op| op.unwrap_or_default()) - .collect::>(), - timeout, - parallel_threshold, - ) - .await + cnt.timer.send( + &cnt.geometry, + &mut cnt.tx_buf, + &mut cnt.rx_buf, + &mut cnt.link, + operations + .into_iter() + .map(|op| op.unwrap_or_default()) + .collect::>(), + timeout, + parallel_threshold, + ) } } -impl Controller { +impl Controller { /// Group the devices by given function and send different data to each group. /// /// If the key is `None`, nothing is done for the devices corresponding to the key. @@ -175,7 +173,7 @@ impl Controller { /// ``` /// # use autd3::prelude::*; /// # tokio_test::block_on(async { - /// let mut autd = Controller::builder((0..3).map(|_| AUTD3::new(Point3::origin()))).open(Nop::builder()).await?; + /// let mut autd = Controller::builder((0..3).map(|_| AUTD3::new(Point3::origin()))).open(Nop::builder())?; /// /// autd.group(|dev| match dev.idx() { /// 0 => Some("static"), @@ -184,7 +182,7 @@ impl Controller { /// }) /// .set("static", Static::new())? /// .set("sine", Sine::new(150 * Hz))? - /// .send().await?; + /// .send()?; /// # Result::<(), AUTDError>::Ok(()) /// # }); /// ``` @@ -215,11 +213,11 @@ mod tests { modulation::{Sine, Static}, }; - #[tokio::test] - async fn test_group() -> anyhow::Result<()> { - let mut autd = create_controller(4).await?; + #[test] + fn test_group() -> anyhow::Result<()> { + let mut autd = create_controller(4)?; - autd.send(Uniform::new(EmitIntensity::new(0xFF))).await?; + autd.send(Uniform::new(EmitIntensity::new(0xFF)))?; autd.group(|dev| match dev.idx() { 0 | 1 | 3 => Some(dev.idx()), @@ -241,8 +239,7 @@ mod tests { )?, ), )? - .send() - .await?; + .send()?; assert_eq!( vec![Drive::NULL; autd.geometry[0].num_transducers()], @@ -288,15 +285,14 @@ mod tests { Ok(()) } - #[tokio::test] - async fn test_send_failed() -> anyhow::Result<()> { - let mut autd = create_controller(1).await?; + #[test] + fn test_send_failed() -> anyhow::Result<()> { + let mut autd = create_controller(1)?; assert_eq!( Ok(()), autd.group(|dev| Some(dev.idx())) .set(0, Null::new())? .send() - .await ); autd.link_mut().down(); @@ -305,15 +301,14 @@ mod tests { autd.group(|dev| Some(dev.idx())) .set(0, Null::new())? .send() - .await ); Ok(()) } - #[tokio::test] - async fn test_send_err() -> anyhow::Result<()> { - let mut autd = create_controller(2).await?; + #[test] + fn test_send_err() -> anyhow::Result<()> { + let mut autd = create_controller(2)?; assert_eq!( Err(AUTDDriverError::InvalidSegmentTransition), @@ -324,15 +319,14 @@ mod tests { SwapSegment::FociSTM(Segment::S1, TransitionMode::SyncIdx), )? .send() - .await ); Ok(()) } - #[tokio::test] - async fn test_group_only_for_enabled() -> anyhow::Result<()> { - let mut autd = create_controller(2).await?; + #[test] + fn test_group_only_for_enabled() -> anyhow::Result<()> { + let mut autd = create_controller(2)?; autd.geometry[0].enable = false; @@ -342,8 +336,7 @@ mod tests { Some(dev.idx()) }) .set(1, Static::with_intensity(0x80))? - .send() - .await?; + .send()?; assert!(!check.lock().unwrap()[0]); assert!(check.lock().unwrap()[1]); @@ -380,9 +373,9 @@ mod tests { } } - #[tokio::test] - async fn test_group_only_for_enabled_gain() -> anyhow::Result<()> { - let mut autd = create_controller(3).await?; + #[test] + fn test_group_only_for_enabled_gain() -> anyhow::Result<()> { + let mut autd = create_controller(3)?; let test = Arc::new(Mutex::new(vec![false; 3])); autd.group(|dev| match dev.idx() { @@ -390,8 +383,7 @@ mod tests { _ => None, }) .set(0, TestGain { test: test.clone() })? - .send() - .await?; + .send()?; assert!(test.lock().unwrap()[0]); assert!(!test.lock().unwrap()[1]); @@ -400,9 +392,9 @@ mod tests { Ok(()) } - #[tokio::test] - async fn unknown_key() -> anyhow::Result<()> { - let mut autd = create_controller(2).await?; + #[test] + fn unknown_key() -> anyhow::Result<()> { + let mut autd = create_controller(2)?; assert_eq!( Some(AUTDDriverError::UnkownKey("2".to_owned())), @@ -415,9 +407,9 @@ mod tests { Ok(()) } - #[tokio::test] - async fn already_used_key() -> anyhow::Result<()> { - let mut autd = create_controller(2).await?; + #[test] + fn already_used_key() -> anyhow::Result<()> { + let mut autd = create_controller(2)?; assert_eq!( Some(AUTDDriverError::KeyIsAlreadyUsed("1".to_owned())), @@ -431,16 +423,15 @@ mod tests { Ok(()) } - #[tokio::test] - async fn unused_key() -> anyhow::Result<()> { - let mut autd = create_controller(3).await?; + #[test] + fn unused_key() -> anyhow::Result<()> { + let mut autd = create_controller(3)?; assert_eq!( Some(AUTDDriverError::UnusedKey("0, 2".to_owned())), autd.group(|dev| Some(dev.idx())) .set(1, Null::new())? .send() - .await .err() ); diff --git a/autd3/src/controller/mod.rs b/autd3/src/controller/mod.rs index 96f77535..3d9343ef 100644 --- a/autd3/src/controller/mod.rs +++ b/autd3/src/controller/mod.rs @@ -18,7 +18,7 @@ use autd3_driver::{ version::FirmwareVersion, }, geometry::{Device, Geometry}, - link::AsyncLink, + link::Link, }; use timer::Timer; @@ -33,7 +33,7 @@ use derive_more::{Deref, DerefMut}; /// /// All operations to the devices are done through this struct. #[derive(Builder, Deref, DerefMut)] -pub struct Controller { +pub struct Controller { #[get(ref, ref_mut, no_doc)] link: L, #[get(ref, ref_mut, no_doc)] @@ -46,7 +46,7 @@ pub struct Controller { timer: Timer, } -impl Controller { +impl Controller { /// Sends a data to the devices. /// /// If the [`Datagram::timeout`] value is @@ -55,25 +55,23 @@ impl Controller { /// /// The calculation of each [`Datagram`] is executed in parallel for each device if the number of enabled devices is greater than the [`Datagram::parallel_threshold`]. #[tracing::instrument(level = "debug", skip(self))] - pub async fn send(&mut self, s: impl Datagram) -> Result<(), AUTDDriverError> { + pub fn send(&mut self, s: impl Datagram) -> Result<(), AUTDDriverError> { let timeout = s.timeout(); let parallel_threshold = s.parallel_threshold(); self.link.trace(timeout, parallel_threshold); let generator = s.operation_generator(&self.geometry)?; - self.timer - .send( - &self.geometry, - &mut self.tx_buf, - &mut self.rx_buf, - &mut self.link, - OperationHandler::generate(generator, &self.geometry), - timeout, - parallel_threshold, - ) - .await + self.timer.send( + &self.geometry, + &mut self.tx_buf, + &mut self.rx_buf, + &mut self.link, + OperationHandler::generate(generator, &self.geometry), + timeout, + parallel_threshold, + ) } - pub(crate) async fn open_impl(mut self, timeout: Duration) -> Result { + pub(crate) fn open_impl(mut self, timeout: Duration) -> Result { let timeout = Some(timeout); #[cfg(feature = "dynamic_freq")] @@ -82,22 +80,18 @@ impl Controller { "Configuring ultrasound frequency to {:?}", autd3_driver::defined::ultrasound_freq() ); - self.send(autd3_driver::datagram::ConfigureFPGAClock::new().with_timeout(timeout)) - .await?; + self.send(autd3_driver::datagram::ConfigureFPGAClock::new().with_timeout(timeout))?; } // If the device is used continuously without powering off, the first data may be ignored because the first msg_id equals to the remaining msg_id in the device. // Therefore, send a meaningless data (here, we use `ForceFan` because it is the lightest). - let _ = self - .send(ForceFan::new(|_| false).with_timeout(timeout)) - .await; + let _ = self.send(ForceFan::new(|_| false).with_timeout(timeout)); - self.send((Clear::new(), Synchronize::new()).with_timeout(timeout)) - .await?; + self.send((Clear::new(), Synchronize::new()).with_timeout(timeout))?; Ok(self) } - async fn close_impl(&mut self) -> Result<(), AUTDDriverError> { + fn close_impl(&mut self) -> Result<(), AUTDDriverError> { tracing::info!("Closing controller"); if !self.link.is_open() { @@ -107,10 +101,10 @@ impl Controller { self.geometry.iter_mut().for_each(|dev| dev.enable = true); [ - self.send(Silencer::default().with_strict_mode(false)).await, - self.send((Static::new(), Null::default())).await, - self.send(Clear::new()).await, - self.link.close().await, + self.send(Silencer::default().with_strict_mode(false)), + self.send((Static::new(), Null::default())), + self.send(Clear::new()), + self.link.close(), ] .into_iter() .try_fold((), |_, x| x) @@ -118,12 +112,12 @@ impl Controller { /// Closes the controller. #[tracing::instrument(level = "debug", skip(self))] - pub async fn close(mut self) -> Result<(), AUTDDriverError> { - self.close_impl().await + pub fn close(mut self) -> Result<(), AUTDDriverError> { + self.close_impl() } - async fn fetch_firminfo(&mut self, ty: FirmwareVersionType) -> Result, AUTDError> { - self.send(ty).await.map_err(|e| { + fn fetch_firminfo(&mut self, ty: FirmwareVersionType) -> Result, AUTDError> { + self.send(ty).map_err(|e| { tracing::error!("Fetch firmware info failed: {:?}", e); AUTDError::ReadFirmwareVersionFailed( check_if_msg_is_processed(&self.tx_buf, &self.rx_buf).collect(), @@ -133,16 +127,16 @@ impl Controller { } /// Returns the firmware version of the devices. - pub async fn firmware_version(&mut self) -> Result, AUTDError> { + pub fn firmware_version(&mut self) -> Result, AUTDError> { use autd3_driver::firmware::version::{CPUVersion, FPGAVersion, Major, Minor}; use FirmwareVersionType::*; - let cpu_major = self.fetch_firminfo(CPUMajor).await?; - let cpu_minor = self.fetch_firminfo(CPUMinor).await?; - let fpga_major = self.fetch_firminfo(FPGAMajor).await?; - let fpga_minor = self.fetch_firminfo(FPGAMinor).await?; - let fpga_functions = self.fetch_firminfo(FPGAFunctions).await?; - self.fetch_firminfo(Clear).await?; + let cpu_major = self.fetch_firminfo(CPUMajor)?; + let cpu_minor = self.fetch_firminfo(CPUMinor)?; + let fpga_major = self.fetch_firminfo(FPGAMajor)?; + let fpga_minor = self.fetch_firminfo(FPGAMinor)?; + let fpga_functions = self.fetch_firminfo(FPGAFunctions)?; + self.fetch_firminfo(Clear)?; Ok(self .geometry @@ -171,23 +165,23 @@ impl Controller { /// ``` /// # use autd3::prelude::*; /// # tokio_test::block_on(async { - /// let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(Nop::builder()).await?; + /// let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(Nop::builder())?; /// - /// autd.send(ReadsFPGAState::new(|_| true)).await?; + /// autd.send(ReadsFPGAState::new(|_| true))?; /// - /// let states = autd.fpga_state().await?; + /// let states = autd.fpga_state()?; /// # Result::<(), AUTDError>::Ok(()) /// # }); /// ``` /// /// [`ReadsFPGAState`]: autd3_driver::datagram::ReadsFPGAState - pub async fn fpga_state(&mut self) -> Result>, AUTDError> { + pub fn fpga_state(&mut self) -> Result>, AUTDError> { if !self.link.is_open() { return Err(AUTDError::Driver( autd3_driver::error::AUTDDriverError::LinkClosed, )); } - if self.link.receive(&mut self.rx_buf).await? { + if self.link.receive(&mut self.rx_buf)? { Ok(self.rx_buf.iter().map(Option::from).collect()) } else { Err(AUTDError::ReadFPGAStateFailed) @@ -195,7 +189,7 @@ impl Controller { } } -impl<'a, L: AsyncLink> IntoIterator for &'a Controller { +impl<'a, L: Link> IntoIterator for &'a Controller { type Item = &'a Device; type IntoIter = std::slice::Iter<'a, Device>; @@ -204,7 +198,7 @@ impl<'a, L: AsyncLink> IntoIterator for &'a Controller { } } -impl<'a, L: AsyncLink> IntoIterator for &'a mut Controller { +impl<'a, L: Link> IntoIterator for &'a mut Controller { type Item = &'a mut Device; type IntoIter = std::slice::IterMut<'a, Device>; @@ -213,11 +207,9 @@ impl<'a, L: AsyncLink> IntoIterator for &'a mut Controller { } } -#[cfg_attr(docsrs, doc(cfg(feature = "async-trait")))] -#[cfg(feature = "async-trait")] -impl Controller { +impl Controller { /// Converts `Controller` into a `Controller>`. - pub fn into_boxed_link(self) -> Controller> { + pub fn into_boxed_link(self) -> Controller> { let cnt = std::mem::ManuallyDrop::new(self); let link = unsafe { std::ptr::read(&cnt.link) }; let geometry = unsafe { std::ptr::read(&cnt.geometry) }; @@ -239,7 +231,7 @@ impl Controller { /// /// This function must be used only when converting an instance created by [`Controller::into_boxed_link`] back to the original [`Controller`]. /// - pub unsafe fn from_boxed_link(cnt: Controller>) -> Controller { + pub unsafe fn from_boxed_link(cnt: Controller>) -> Controller { let cnt = std::mem::ManuallyDrop::new(cnt); let link = unsafe { std::ptr::read(&cnt.link) }; let geometry = unsafe { std::ptr::read(&cnt.geometry) }; @@ -256,20 +248,12 @@ impl Controller { } } -impl Drop for Controller { +impl Drop for Controller { fn drop(&mut self) { if !self.link.is_open() { return; } - match tokio::runtime::Handle::current().runtime_flavor() { - tokio::runtime::RuntimeFlavor::CurrentThread => {} - tokio::runtime::RuntimeFlavor::MultiThread => tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on(async { - let _ = self.close_impl().await; - }); - }), - _ => unimplemented!(), - } + let _ = self.close_impl(); } } @@ -282,19 +266,15 @@ mod tests { geometry::Point3, }; - use spin_sleep::SpinSleeper; - use timer::*; - - use crate::{link::Audit, prelude::*}; + use crate::{controller::timer::*, link::Audit, prelude::*}; use super::*; // GRCOV_EXCL_START - pub async fn create_controller(dev_num: usize) -> anyhow::Result> { + pub fn create_controller(dev_num: usize) -> anyhow::Result> { Ok( Controller::builder((0..dev_num).map(|_| AUTD3::new(Point3::origin()))) - .open(Audit::builder()) - .await?, + .open(Audit::builder())?, ) } // GRCOV_EXCL_STOP @@ -302,31 +282,28 @@ mod tests { #[rstest::rstest] #[case(TimerStrategy::Std(StdSleeper::default()))] #[case(TimerStrategy::Spin(SpinSleeper::default()))] - #[case(TimerStrategy::Async(AsyncSleeper::default()))] #[cfg_attr(target_os = "windows", case(TimerStrategy::Waitable(WaitableSleeper::new().unwrap())))] - #[tokio::test(flavor = "multi_thread")] - async fn open_with_timer(#[case] strategy: TimerStrategy) { + #[test] + fn open_with_timer(#[case] strategy: TimerStrategy) { assert!(Controller::builder([AUTD3::new(Point3::origin())]) .with_timer_strategy(strategy) .open(Audit::builder()) - .await .is_ok()); } - #[tokio::test(flavor = "multi_thread")] - async fn open_failed() { + #[test] + fn open_failed() { assert_eq!( Some(AUTDError::Driver(AUTDDriverError::SendDataFailed)), Controller::builder([AUTD3::new(Point3::origin())]) .open(Audit::builder().with_down(true)) - .await .err() ); } - #[tokio::test(flavor = "multi_thread")] - async fn send() -> anyhow::Result<()> { - let mut autd = create_controller(1).await?; + #[test] + fn send() -> anyhow::Result<()> { + let mut autd = create_controller(1)?; autd.send(( Sine::new(150. * Hz), GainSTM::new( @@ -337,8 +314,7 @@ mod tests { ] .into_iter(), )?, - )) - .await?; + ))?; autd.iter().try_for_each(|dev| { assert_eq!( @@ -362,16 +338,16 @@ mod tests { anyhow::Ok(()) })?; - autd.close().await?; + autd.close()?; Ok(()) } - #[tokio::test(flavor = "multi_thread")] - async fn firmware_version() -> anyhow::Result<()> { + #[test] + fn firmware_version() -> anyhow::Result<()> { use autd3_driver::firmware::version::{CPUVersion, FPGAVersion}; - let mut autd = create_controller(1).await?; + let mut autd = create_controller(1)?; assert_eq!( vec![FirmwareVersion::new( 0, @@ -385,60 +361,59 @@ mod tests { FPGAVersion::ENABLED_EMULATOR_BIT ) )], - autd.firmware_version().await? + autd.firmware_version()? ); Ok(()) } - #[tokio::test(flavor = "multi_thread")] - async fn firmware_version_err() -> anyhow::Result<()> { - let mut autd = create_controller(2).await?; + #[test] + fn firmware_version_err() -> anyhow::Result<()> { + let mut autd = create_controller(2)?; autd.link_mut().break_down(); assert_eq!( Err(AUTDError::ReadFirmwareVersionFailed(vec![false, false])), - autd.firmware_version().await + autd.firmware_version() ); Ok(()) } - #[tokio::test(flavor = "multi_thread")] - async fn close() -> anyhow::Result<()> { + #[test] + fn close() -> anyhow::Result<()> { { - let mut autd = create_controller(1).await?; - autd.close_impl().await?; - autd.close().await?; + let mut autd = create_controller(1)?; + autd.close_impl()?; + autd.close()?; } { - let mut autd = create_controller(1).await?; + let mut autd = create_controller(1)?; autd.link_mut().break_down(); assert_eq!( Err(AUTDDriverError::LinkError("broken".to_owned())), - autd.close().await + autd.close() ); } { - let mut autd = create_controller(1).await?; + let mut autd = create_controller(1)?; autd.link_mut().down(); - assert_eq!(Err(AUTDDriverError::SendDataFailed), autd.close().await); + assert_eq!(Err(AUTDDriverError::SendDataFailed), autd.close()); } Ok(()) } - #[tokio::test(flavor = "multi_thread")] - async fn fpga_state() -> anyhow::Result<()> { + #[test] + fn fpga_state() -> anyhow::Result<()> { let mut autd = Controller::builder([AUTD3::new(Point3::origin()), AUTD3::new(Point3::origin())]) - .open(Audit::builder()) - .await?; + .open(Audit::builder())?; - autd.send(ReadsFPGAState::new(|_| true)).await?; + autd.send(ReadsFPGAState::new(|_| true))?; { autd.link_mut()[0].fpga_mut().assert_thermal_sensor(); - let states = autd.fpga_state().await?; + let states = autd.fpga_state()?; assert_eq!(2, states.len()); assert!(states[0] .ok_or(anyhow::anyhow!("state shouldn't be None here"))? @@ -452,7 +427,7 @@ mod tests { autd.link_mut()[0].fpga_mut().deassert_thermal_sensor(); autd.link_mut()[1].fpga_mut().assert_thermal_sensor(); - let states = autd.fpga_state().await?; + let states = autd.fpga_state()?; assert_eq!(2, states.len()); assert!(!states[0] .ok_or(anyhow::anyhow!("state shouldn't be None here"))? @@ -462,9 +437,9 @@ mod tests { .is_thermal_assert()); } - autd.send(ReadsFPGAState::new(|dev| dev.idx() == 1)).await?; + autd.send(ReadsFPGAState::new(|dev| dev.idx() == 1))?; { - let states = autd.fpga_state().await?; + let states = autd.fpga_state()?; assert_eq!(2, states.len()); assert!(states[0].is_none()); assert!(states[1] @@ -475,9 +450,9 @@ mod tests { Ok(()) } - #[tokio::test(flavor = "multi_thread")] - async fn into_iter() -> anyhow::Result<()> { - let mut autd = create_controller(1).await?; + #[test] + fn into_iter() -> anyhow::Result<()> { + let mut autd = create_controller(1)?; for dev in &mut autd { dev.sound_speed = 300e3 * mm; @@ -490,10 +465,9 @@ mod tests { Ok(()) } - #[cfg(feature = "async-trait")] - #[tokio::test(flavor = "multi_thread")] - async fn into_boxed_link() -> anyhow::Result<()> { - let autd = create_controller(1).await?; + #[test] + fn into_boxed_link() -> anyhow::Result<()> { + let autd = create_controller(1)?; let mut autd = autd.into_boxed_link(); @@ -507,8 +481,7 @@ mod tests { ] .into_iter(), )?, - )) - .await?; + ))?; let autd = unsafe { Controller::::from_boxed_link(autd) }; @@ -534,7 +507,7 @@ mod tests { anyhow::Ok(()) })?; - autd.close().await?; + autd.close()?; Ok(()) } diff --git a/autd3/src/controller/timer/mod.rs b/autd3/src/controller/timer/mod.rs index d43c38cb..50107590 100644 --- a/autd3/src/controller/timer/mod.rs +++ b/autd3/src/controller/timer/mod.rs @@ -1,6 +1,11 @@ #![allow(clippy::too_many_arguments)] -mod sleep; +pub(crate) mod sleep; + +use sleep::Sleeper; +#[cfg(target_os = "windows")] +pub use sleep::WaitableSleeper; +pub use sleep::{SpinSleeper, StdSleeper}; use std::time::{Duration, Instant}; @@ -11,15 +16,10 @@ use autd3_driver::{ cpu::{check_if_msg_is_processed, RxMessage, TxMessage}, operation::{Operation, OperationHandler}, }, - link::AsyncLink, + link::Link, }; use itertools::Itertools; -use sleep::Sleeper; -#[cfg(target_os = "windows")] -pub use sleep::WaitableSleeper; -pub use sleep::{AsyncSleeper, StdSleeper}; -pub use spin_sleep::{SpinSleeper, SpinStrategy}; /// Enum representing sleeping strategies for the timer. /// @@ -36,8 +36,6 @@ pub enum TimerStrategy { Waitable(WaitableSleeper), /// Uses a [spin_sleep](https://crates.io/crates/spin_sleep) crate. Spin(SpinSleeper), - /// Uses [`tokio::time::sleep_until`]. - Async(AsyncSleeper), } /// A struct managing the timing of sending and receiving operations. @@ -61,12 +59,12 @@ pub struct Timer { } impl Timer { - pub(crate) async fn send( + pub(crate) fn send( &self, geometry: &Geometry, tx: &mut [TxMessage], rx: &mut [RxMessage], - link: &mut impl AsyncLink, + link: &mut impl Link, operations: Vec<(impl Operation, impl Operation)>, timeout: Option, parallel_threshold: Option, @@ -76,46 +74,31 @@ impl Timer { tracing::debug!("timeout: {:?}, parallel: {:?}", timeout, parallel); match &self.strategy { - TimerStrategy::Std(sleeper) => { - self._send( - sleeper, geometry, tx, rx, link, operations, timeout, parallel, - ) - .await - } - TimerStrategy::Spin(sleeper) => { - self._send( - sleeper, geometry, tx, rx, link, operations, timeout, parallel, - ) - .await - } - TimerStrategy::Async(sleeper) => { - self._send( - sleeper, geometry, tx, rx, link, operations, timeout, parallel, - ) - .await - } + TimerStrategy::Std(sleeper) => self._send( + sleeper, geometry, tx, rx, link, operations, timeout, parallel, + ), + TimerStrategy::Spin(sleeper) => self._send( + sleeper, geometry, tx, rx, link, operations, timeout, parallel, + ), #[cfg(target_os = "windows")] - TimerStrategy::Waitable(sleeper) => { - self._send( - sleeper, geometry, tx, rx, link, operations, timeout, parallel, - ) - .await - } + TimerStrategy::Waitable(sleeper) => self._send( + sleeper, geometry, tx, rx, link, operations, timeout, parallel, + ), } } - async fn _send( + fn _send( &self, sleeper: &S, geometry: &Geometry, tx: &mut [TxMessage], rx: &mut [RxMessage], - link: &mut impl AsyncLink, + link: &mut impl Link, mut operations: Vec<(impl Operation, impl Operation)>, timeout: Duration, parallel: bool, ) -> Result<(), AUTDDriverError> { - link.update(geometry).await?; + link.update(geometry)?; // We prioritize average behavior for the transmission timing. That is, not the interval from the previous transmission, but ensuring that T/`send_interval` transmissions are performed in a sufficiently long time T. // For example, if the `send_interval` is 1ms and it takes 1.5ms to transmit due to some reason, the next transmission will be performed not 1ms later but 0.5ms later. @@ -123,23 +106,23 @@ impl Timer { loop { OperationHandler::pack(&mut operations, geometry, tx, parallel)?; - self.send_receive(sleeper, tx, rx, link, timeout).await?; + self.send_receive(sleeper, tx, rx, link, timeout)?; if OperationHandler::is_done(&operations) { return Ok(()); } send_timing += self.send_interval; - sleeper.sleep_until(send_timing).await; + sleeper.sleep_until(send_timing); } } - async fn send_receive( + fn send_receive( &self, sleeper: &impl Sleeper, tx: &[TxMessage], rx: &mut [RxMessage], - link: &mut impl AsyncLink, + link: &mut impl Link, timeout: Duration, ) -> Result<(), AUTDDriverError> { if !link.is_open() { @@ -147,19 +130,18 @@ impl Timer { } tracing::trace!("send: {}", tx.iter().join(", ")); - if !link.send(tx).await? { + if !link.send(tx)? { return Err(AUTDDriverError::SendDataFailed); } self.wait_msg_processed(sleeper, tx, rx, link, timeout) - .await } - async fn wait_msg_processed( + fn wait_msg_processed( &self, sleeper: &S, tx: &[TxMessage], rx: &mut [RxMessage], - link: &mut impl AsyncLink, + link: &mut impl Link, timeout: Duration, ) -> Result<(), AUTDDriverError> { let start = Instant::now(); @@ -168,7 +150,7 @@ impl Timer { if !link.is_open() { return Err(AUTDDriverError::LinkClosed); } - let res = link.receive(rx).await?; + let res = link.receive(rx)?; tracing::trace!("recv: {}", rx.iter().join(", ")); if res && check_if_msg_is_processed(tx, rx).all(std::convert::identity) { @@ -178,7 +160,7 @@ impl Timer { break; } receive_timing += self.receive_interval; - sleeper.sleep_until(receive_timing).await; + sleeper.sleep_until(receive_timing); } rx.iter() .try_fold((), |_, r| Result::<(), AUTDDriverError>::from(r)) @@ -197,6 +179,10 @@ impl Timer { mod tests { use zerocopy::FromZeros; + #[cfg(target_os = "windows")] + use crate::controller::timer::WaitableSleeper; + use crate::controller::timer::{SpinSleeper, StdSleeper}; + use super::*; struct MockLink { @@ -206,19 +192,18 @@ mod tests { pub down: bool, } - #[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] - impl AsyncLink for MockLink { - async fn close(&mut self) -> Result<(), AUTDDriverError> { + impl Link for MockLink { + fn close(&mut self) -> Result<(), AUTDDriverError> { self.is_open = false; Ok(()) } - async fn send(&mut self, _: &[TxMessage]) -> Result { + fn send(&mut self, _: &[TxMessage]) -> Result { self.send_cnt += 1; Ok(!self.down) } - async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + fn receive(&mut self, rx: &mut [RxMessage]) -> Result { if self.recv_cnt > 10 { return Err(AUTDDriverError::LinkError("too many".to_owned())); } @@ -235,8 +220,8 @@ mod tests { } } - #[tokio::test] - async fn test_close() -> anyhow::Result<()> { + #[test] + fn test_close() -> anyhow::Result<()> { let mut link = MockLink { is_open: true, send_cnt: 0, @@ -246,7 +231,7 @@ mod tests { assert!(link.is_open()); - link.close().await?; + link.close()?; assert!(!link.is_open()); @@ -256,10 +241,9 @@ mod tests { #[rstest::rstest] #[case(TimerStrategy::Std(StdSleeper::default()), StdSleeper::default())] #[case(TimerStrategy::Spin(SpinSleeper::default()), SpinSleeper::default())] - #[case(TimerStrategy::Async(AsyncSleeper::default()), AsyncSleeper::default())] #[cfg_attr(target_os = "windows", case(TimerStrategy::Waitable(WaitableSleeper::new().unwrap()), WaitableSleeper::new().unwrap()))] - #[tokio::test] - async fn test_send_receive(#[case] strategy: TimerStrategy, #[case] sleeper: impl Sleeper) { + #[test] + fn test_send_receive(#[case] strategy: TimerStrategy, #[case] sleeper: impl Sleeper) { let mut link = MockLink { is_open: true, send_cnt: 0, @@ -278,34 +262,26 @@ mod tests { }; assert_eq!( - timer - .send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO) - .await, + timer.send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO), Ok(()) ); link.is_open = false; assert_eq!( - timer - .send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO) - .await, + timer.send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO), Err(AUTDDriverError::LinkClosed) ); link.is_open = true; link.down = true; assert_eq!( - timer - .send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO) - .await, + timer.send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO), Err(AUTDDriverError::SendDataFailed) ); link.down = false; assert_eq!( - timer - .send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(1)) - .await, + timer.send_receive(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(1)), Ok(()) ); } @@ -313,13 +289,9 @@ mod tests { #[rstest::rstest] #[case(TimerStrategy::Std(StdSleeper::default()), StdSleeper::default())] #[case(TimerStrategy::Spin(SpinSleeper::default()), SpinSleeper::default())] - #[case(TimerStrategy::Async(AsyncSleeper::default()), AsyncSleeper::default())] #[cfg_attr(target_os = "windows", case(TimerStrategy::Waitable(WaitableSleeper::new().unwrap()), WaitableSleeper::new().unwrap()))] - #[tokio::test] - async fn test_wait_msg_processed( - #[case] strategy: TimerStrategy, - #[case] sleeper: impl Sleeper, - ) { + #[test] + fn test_wait_msg_processed(#[case] strategy: TimerStrategy, #[case] sleeper: impl Sleeper) { let mut link = MockLink { is_open: true, send_cnt: 0, @@ -339,9 +311,7 @@ mod tests { }; assert_eq!( - timer - .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)) - .await, + timer.wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)), Ok(()) ); @@ -349,9 +319,7 @@ mod tests { link.is_open = false; assert_eq!( Err(AUTDDriverError::LinkClosed), - timer - .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)) - .await + timer.wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)) ); link.recv_cnt = 0; @@ -359,9 +327,7 @@ mod tests { link.down = true; assert_eq!( Err(AUTDDriverError::ConfirmResponseFailed), - timer - .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)) - .await, + timer.wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_millis(10)), ); link.recv_cnt = 0; @@ -369,9 +335,7 @@ mod tests { link.down = true; assert_eq!( Ok(()), - timer - .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO) - .await, + timer.wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::ZERO), ); link.down = false; @@ -379,9 +343,7 @@ mod tests { tx[0].header_mut().msg_id = 20; assert_eq!( Err(AUTDDriverError::LinkError("too many".to_owned())), - timer - .wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_secs(10)) - .await + timer.wait_msg_processed(&sleeper, &tx, &mut rx, &mut link, Duration::from_secs(10)) ); } } diff --git a/autd3/src/controller/timer/sleep.rs b/autd3/src/controller/timer/sleep.rs index 4e775ca6..1d15e297 100644 --- a/autd3/src/controller/timer/sleep.rs +++ b/autd3/src/controller/timer/sleep.rs @@ -1,10 +1,10 @@ use std::time::Instant; use autd3_driver::utils::timer::TimerResolutionGurad; -use spin_sleep::SpinSleeper; +pub use spin_sleep::SpinSleeper; pub(crate) trait Sleeper { - fn sleep_until(&self, deadline: Instant) -> impl std::future::Future; + fn sleep_until(&self, deadline: Instant); } /// See [`TimerStrategy`] for more details. @@ -25,42 +25,18 @@ impl Default for StdSleeper { } impl Sleeper for StdSleeper { - async fn sleep_until(&self, deadline: Instant) { + fn sleep_until(&self, deadline: Instant) { let _timer_guard = TimerResolutionGurad::new(self.timer_resolution); std::thread::sleep(deadline - Instant::now()); } } impl Sleeper for SpinSleeper { - async fn sleep_until(&self, deadline: Instant) { + fn sleep_until(&self, deadline: Instant) { self.sleep(deadline - Instant::now()); } } -/// See [`TimerStrategy`] for more details. -/// -/// [`TimerStrategy`]: super::TimerStrategy -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct AsyncSleeper { - /// An optional timer resolution in milliseconds for Windows. The default is `Some(1)`. - pub timer_resolution: Option, -} - -impl Default for AsyncSleeper { - fn default() -> Self { - Self { - timer_resolution: Some(std::num::NonZeroU32::MIN), - } - } -} - -impl Sleeper for AsyncSleeper { - async fn sleep_until(&self, deadline: Instant) { - let _timer_guard = TimerResolutionGurad::new(self.timer_resolution); - tokio::time::sleep_until(tokio::time::Instant::from_std(deadline)).await; - } -} - #[cfg(target_os = "windows")] pub use win::WaitableSleeper; @@ -102,7 +78,7 @@ mod win { } impl Sleeper for WaitableSleeper { - async fn sleep_until(&self, deadline: Instant) { + fn sleep_until(&self, deadline: Instant) { unsafe { let time = deadline - Instant::now(); if time.is_zero() { diff --git a/autd3/src/lib.rs b/autd3/src/lib.rs index 17b1aa2a..cecfd30f 100644 --- a/autd3/src/lib.rs +++ b/autd3/src/lib.rs @@ -27,6 +27,11 @@ pub mod link; /// Prelude module. pub mod prelude; +/// Asynchronous module. +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg(feature = "async")] +pub mod r#async; + pub use autd3_driver as driver; pub use autd3_driver::derive; pub use datagram::gain; diff --git a/autd3/src/link/audit.rs b/autd3/src/link/audit.rs index ea9ebed3..82841909 100644 --- a/autd3/src/link/audit.rs +++ b/autd3/src/link/audit.rs @@ -1,7 +1,7 @@ use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{AsyncLink, AsyncLinkBuilder}, + link::{Link, LinkBuilder}, }; use autd3_firmware_emulator::CPUEmulator; @@ -41,14 +41,10 @@ pub struct AuditBuilder { down: bool, } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl AsyncLinkBuilder for AuditBuilder { +impl LinkBuilder for AuditBuilder { type L = Audit; - async fn open( - self, - geometry: &autd3_driver::geometry::Geometry, - ) -> Result { + fn open(self, geometry: &autd3_driver::geometry::Geometry) -> Result { Ok(Audit { is_open: true, cpus: geometry @@ -115,14 +111,13 @@ impl Audit { } } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl AsyncLink for Audit { - async fn close(&mut self) -> Result<(), AUTDDriverError> { +impl Link for Audit { + fn close(&mut self) -> Result<(), AUTDDriverError> { self.is_open = false; Ok(()) } - async fn send(&mut self, tx: &[TxMessage]) -> Result { + fn send(&mut self, tx: &[TxMessage]) -> Result { if self.broken { return Err(AUTDDriverError::LinkError("broken".to_owned())); } @@ -138,7 +133,7 @@ impl AsyncLink for Audit { Ok(true) } - async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + fn receive(&mut self, rx: &mut [RxMessage]) -> Result { if self.broken { return Err(AUTDDriverError::LinkError("broken".to_owned())); } @@ -164,3 +159,42 @@ impl AsyncLink for Audit { self.last_parallel_threshold = parallel_threshold; } } + +#[cfg(feature = "async")] +use autd3_driver::link::{AsyncLink, AsyncLinkBuilder}; + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] +impl AsyncLinkBuilder for AuditBuilder { + type L = Audit; + + async fn open(self, geometry: &Geometry) -> Result { + ::open(self, geometry) + } +} + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] +impl AsyncLink for Audit { + async fn close(&mut self) -> Result<(), AUTDDriverError> { + ::close(self) + } + + async fn send(&mut self, tx: &[TxMessage]) -> Result { + ::send(self, tx) + } + + async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + ::receive(self, rx) + } + + fn is_open(&self) -> bool { + ::is_open(self) + } + + fn trace(&mut self, timeout: Option, parallel_threshold: Option) { + ::trace(self, timeout, parallel_threshold) + } +} diff --git a/autd3/src/link/nop.rs b/autd3/src/link/nop.rs index 9946dcad..95bf71f2 100644 --- a/autd3/src/link/nop.rs +++ b/autd3/src/link/nop.rs @@ -1,7 +1,7 @@ use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{AsyncLink, AsyncLinkBuilder}, + link::{Link, LinkBuilder}, }; use autd3_firmware_emulator::CPUEmulator; @@ -17,11 +17,10 @@ pub struct Nop { #[derive(Builder)] pub struct NopBuilder {} -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl AsyncLinkBuilder for NopBuilder { +impl LinkBuilder for NopBuilder { type L = Nop; - async fn open(self, geometry: &Geometry) -> Result { + fn open(self, geometry: &Geometry) -> Result { Ok(Nop { is_open: true, cpus: geometry @@ -33,14 +32,13 @@ impl AsyncLinkBuilder for NopBuilder { } } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] -impl AsyncLink for Nop { - async fn close(&mut self) -> Result<(), AUTDDriverError> { +impl Link for Nop { + fn close(&mut self) -> Result<(), AUTDDriverError> { self.is_open = false; Ok(()) } - async fn send(&mut self, tx: &[TxMessage]) -> Result { + fn send(&mut self, tx: &[TxMessage]) -> Result { self.cpus.iter_mut().for_each(|cpu| { cpu.send(tx); }); @@ -48,7 +46,7 @@ impl AsyncLink for Nop { Ok(true) } - async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + fn receive(&mut self, rx: &mut [RxMessage]) -> Result { self.cpus.iter_mut().for_each(|cpu| { cpu.update(); rx[cpu.idx()] = cpu.rx(); @@ -62,6 +60,41 @@ impl AsyncLink for Nop { } } +#[cfg(feature = "async")] +use autd3_driver::link::{AsyncLink, AsyncLinkBuilder}; + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] +impl AsyncLinkBuilder for NopBuilder { + type L = Nop; + + async fn open(self, geometry: &Geometry) -> Result { + ::open(self, geometry) + } +} + +#[cfg(feature = "async")] +#[cfg_attr(docsrs, doc(cfg(feature = "async")))] +#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] +impl AsyncLink for Nop { + async fn close(&mut self) -> Result<(), AUTDDriverError> { + ::close(self) + } + + async fn send(&mut self, tx: &[TxMessage]) -> Result { + ::send(self, tx) + } + + async fn receive(&mut self, rx: &mut [RxMessage]) -> Result { + ::receive(self, rx) + } + + fn is_open(&self) -> bool { + ::is_open(self) + } +} + impl Nop { /// Create a new [`NopBuilder`]. pub const fn builder() -> NopBuilder { diff --git a/autd3/tests/datagram/gain/group.rs b/autd3/tests/async/datagram/gain/group.rs similarity index 94% rename from autd3/tests/datagram/gain/group.rs rename to autd3/tests/async/datagram/gain/group.rs index 9e6c694b..e11b83eb 100644 --- a/autd3/tests/datagram/gain/group.rs +++ b/autd3/tests/async/datagram/gain/group.rs @@ -1,4 +1,4 @@ -use autd3::{link::Audit, prelude::*}; +use autd3::{link::Audit, prelude::*, r#async::Controller}; #[tokio::test] async fn only_for_enabled() -> anyhow::Result<()> { diff --git a/autd3/tests/datagram/gain/mod.rs b/autd3/tests/async/datagram/gain/mod.rs similarity index 100% rename from autd3/tests/datagram/gain/mod.rs rename to autd3/tests/async/datagram/gain/mod.rs diff --git a/autd3/tests/datagram/mod.rs b/autd3/tests/async/datagram/mod.rs similarity index 100% rename from autd3/tests/datagram/mod.rs rename to autd3/tests/async/datagram/mod.rs diff --git a/autd3/tests/link/audit.rs b/autd3/tests/async/link/audit.rs similarity index 98% rename from autd3/tests/link/audit.rs rename to autd3/tests/async/link/audit.rs index 0d7bd193..03ca40a2 100644 --- a/autd3/tests/link/audit.rs +++ b/autd3/tests/async/link/audit.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use autd3::{link::Audit, prelude::*}; +use autd3::{link::Audit, prelude::*, r#async::Controller}; use autd3_driver::firmware::{cpu::RxMessage, fpga::FPGAState}; #[tokio::test] diff --git a/autd3/tests/link/mod.rs b/autd3/tests/async/link/mod.rs similarity index 100% rename from autd3/tests/link/mod.rs rename to autd3/tests/async/link/mod.rs diff --git a/autd3/tests/link/nop.rs b/autd3/tests/async/link/nop.rs similarity index 85% rename from autd3/tests/link/nop.rs rename to autd3/tests/async/link/nop.rs index 416e6b8e..67c3ec1d 100644 --- a/autd3/tests/link/nop.rs +++ b/autd3/tests/async/link/nop.rs @@ -1,4 +1,4 @@ -use autd3::{driver::link::AsyncLink, prelude::*}; +use autd3::{driver::link::AsyncLink, prelude::*, r#async::Controller}; #[tokio::test] async fn nop_test() -> anyhow::Result<()> { diff --git a/autd3/tests/async/mod.rs b/autd3/tests/async/mod.rs new file mode 100644 index 00000000..bce3880c --- /dev/null +++ b/autd3/tests/async/mod.rs @@ -0,0 +1,27 @@ +use autd3::{ + link::Audit, + prelude::{Point3, AUTD3}, + r#async::Controller, +}; + +mod datagram; +mod link; + +#[tokio::test] +async fn initial_msg_id() -> anyhow::Result<()> { + let cnt = Controller::builder([AUTD3::new(Point3::origin())]) + .open( + Audit::builder() + .with_initial_msg_id(Some(0x01)) + .with_initial_phase_corr(Some(0xFF)), + ) + .await?; + + assert!(cnt.link()[0] + .fpga() + .phase_correction() + .iter() + .all(|v| v.value() == 0x00)); + + Ok(()) +} diff --git a/autd3/tests/sync/datagram/gain/group.rs b/autd3/tests/sync/datagram/gain/group.rs new file mode 100644 index 00000000..fbe93547 --- /dev/null +++ b/autd3/tests/sync/datagram/gain/group.rs @@ -0,0 +1,39 @@ +use autd3::{link::Audit, prelude::*}; + +#[test] +fn only_for_enabled() -> anyhow::Result<()> { + let mut autd = + Controller::builder([AUTD3::new(Point3::origin()), AUTD3::new(Point3::origin())]) + .open(Audit::builder())?; + + let check = std::sync::Arc::new(std::sync::Mutex::new(vec![false; autd.num_devices()])); + + autd[0].enable = false; + + autd.send( + Group::new(|dev| { + check.lock().unwrap()[dev.idx()] = true; + move |_| Some(0) + }) + .set( + 0, + Uniform::new(Drive::new(Phase::new(0x90), EmitIntensity::new(0x80))), + )?, + )?; + + assert!(!check.lock().unwrap()[0]); + assert!(check.lock().unwrap()[1]); + + assert!(autd.link()[0] + .fpga() + .drives_at(Segment::S0, 0) + .into_iter() + .all(|d| Drive::NULL == d)); + assert!(autd.link()[1] + .fpga() + .drives_at(Segment::S0, 0) + .into_iter() + .all(|d| Drive::new(Phase::new(0x90), EmitIntensity::new(0x80)) == d)); + + Ok(()) +} diff --git a/autd3/tests/sync/datagram/gain/mod.rs b/autd3/tests/sync/datagram/gain/mod.rs new file mode 100644 index 00000000..3cf3067b --- /dev/null +++ b/autd3/tests/sync/datagram/gain/mod.rs @@ -0,0 +1 @@ +mod group; diff --git a/autd3/tests/sync/datagram/mod.rs b/autd3/tests/sync/datagram/mod.rs new file mode 100644 index 00000000..6ee55dbf --- /dev/null +++ b/autd3/tests/sync/datagram/mod.rs @@ -0,0 +1 @@ +mod gain; diff --git a/autd3/tests/sync/link/audit.rs b/autd3/tests/sync/link/audit.rs new file mode 100644 index 00000000..995e6e54 --- /dev/null +++ b/autd3/tests/sync/link/audit.rs @@ -0,0 +1,81 @@ +use std::time::Duration; + +use autd3::{link::Audit, prelude::*}; +use autd3_driver::firmware::{cpu::RxMessage, fpga::FPGAState}; + +#[test] +fn audit_test() -> anyhow::Result<()> { + let mut autd = Controller::builder([AUTD3::new(Point3::origin())]) + .with_default_timeout(Duration::from_millis(100)) + .open_with_timeout(Audit::builder(), Duration::from_millis(10))?; + assert_eq!(Some(Duration::from_millis(10)), autd.link().last_timeout()); + assert_eq!(Some(usize::MAX), autd.link().last_parallel_threshold()); + assert_eq!(Duration::from_millis(100), autd.timer().default_timeout()); + assert_eq!(0, autd.link()[0].idx()); + + { + autd.send( + Null::new() + .with_parallel_threshold(Some(1)) + .with_timeout(Some(Duration::from_millis(20))), + )?; + assert_eq!(Some(Duration::from_millis(20)), autd.link().last_timeout()); + assert_eq!(Some(1), autd.link().last_parallel_threshold()); + + autd.send(Null::new().with_parallel_threshold(None).with_timeout(None))?; + assert_eq!(None, autd.link().last_timeout()); + assert_eq!(None, autd.link().last_parallel_threshold()); + } + + { + assert_eq!(vec![None], autd.fpga_state()?); + assert!(autd.send(ReadsFPGAState::new(|_| true)).is_ok()); + autd.link_mut()[0].update(); + assert_eq!( + vec![Option::::from(&RxMessage::new(0x88, 0x00))], + autd.fpga_state()? + ); + autd.link_mut()[0].fpga_mut().assert_thermal_sensor(); + autd.link_mut()[0].update(); + assert_eq!( + vec![Option::::from(&RxMessage::new(0x89, 0x00))], + autd.fpga_state()? + ); + } + + { + autd.link_mut().down(); + assert_eq!( + Err(AUTDDriverError::SendDataFailed), + autd.send(Static::new()) + ); + assert_eq!(Err(AUTDError::ReadFPGAStateFailed), autd.fpga_state()); + autd.link_mut().up(); + assert!(autd.send(Static::new()).is_ok()); + autd.link_mut().break_down(); + assert_eq!( + Err(AUTDDriverError::LinkError("broken".to_string())), + autd.send(Static::new()) + ); + assert_eq!( + Err(AUTDError::Driver(AUTDDriverError::LinkError( + "broken".to_string() + ))), + autd.fpga_state() + ); + autd.link_mut().repair(); + assert!(autd.send(Static::new()).is_ok()); + } + + { + use autd3_driver::link::Link; + assert!(autd.link_mut().close().is_ok()); + assert_eq!(Err(AUTDDriverError::LinkClosed), autd.send(Static::new())); + assert_eq!( + Err(AUTDError::Driver(AUTDDriverError::LinkClosed)), + autd.fpga_state() + ); + } + + Ok(()) +} diff --git a/autd3/tests/sync/link/mod.rs b/autd3/tests/sync/link/mod.rs new file mode 100644 index 00000000..e98808d1 --- /dev/null +++ b/autd3/tests/sync/link/mod.rs @@ -0,0 +1,2 @@ +mod audit; +mod nop; diff --git a/autd3/tests/sync/link/nop.rs b/autd3/tests/sync/link/nop.rs new file mode 100644 index 00000000..55975742 --- /dev/null +++ b/autd3/tests/sync/link/nop.rs @@ -0,0 +1,14 @@ +use autd3::{driver::link::Link, prelude::*}; + +#[test] +fn nop_test() -> anyhow::Result<()> { + let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(Nop::builder())?; + + assert!(autd.send(Static::new()).is_ok()); + + assert!(autd.link_mut().close().is_ok()); + + assert_eq!(Err(AUTDDriverError::LinkClosed), autd.send(Static::new())); + + Ok(()) +} diff --git a/autd3/tests/sync/mod.rs b/autd3/tests/sync/mod.rs new file mode 100644 index 00000000..833a58a0 --- /dev/null +++ b/autd3/tests/sync/mod.rs @@ -0,0 +1,25 @@ +use autd3::{ + link::Audit, + prelude::{Point3, AUTD3}, + Controller, +}; + +mod datagram; +mod link; + +#[test] +fn initial_msg_id() -> anyhow::Result<()> { + let cnt = Controller::builder([AUTD3::new(Point3::origin())]).open( + Audit::builder() + .with_initial_msg_id(Some(0x01)) + .with_initial_phase_corr(Some(0xFF)), + )?; + + assert!(cnt.link()[0] + .fpga() + .phase_correction() + .iter() + .all(|v| v.value() == 0x00)); + + Ok(()) +} diff --git a/autd3/tests/test.rs b/autd3/tests/test.rs index f21bfeaa..a105e804 100644 --- a/autd3/tests/test.rs +++ b/autd3/tests/test.rs @@ -1,27 +1,3 @@ -use autd3::{ - link::Audit, - prelude::{Point3, AUTD3}, - Controller, -}; - -mod datagram; -mod link; - -#[tokio::test] -async fn initial_msg_id() -> anyhow::Result<()> { - let cnt = Controller::builder([AUTD3::new(Point3::origin())]) - .open( - Audit::builder() - .with_initial_msg_id(Some(0x01)) - .with_initial_phase_corr(Some(0xFF)), - ) - .await?; - - assert!(cnt.link()[0] - .fpga() - .phase_correction() - .iter() - .all(|v| v.value() == 0x00)); - - Ok(()) -} +#[cfg(feature = "async")] +mod r#async; +mod sync; diff --git a/build.py b/build.py index b7629f58..7dcece8b 100644 --- a/build.py +++ b/build.py @@ -130,6 +130,7 @@ def rust_test(args) -> None: # noqa: ANN001 def rust_run(args) -> None: # noqa: ANN001 examples = [ + "async", "nop", "twincat", "remote_twincat", @@ -143,6 +144,8 @@ def rust_run(args) -> None: # noqa: ANN001 return sys.exit(-1) features: str match args.target: + case "async": + features = "async" case "twincat": features = "twincat" case "remote_twincat": diff --git a/examples/Cargo.toml b/examples/Cargo.toml index eab0013a..a420cd24 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -26,6 +26,11 @@ name = "simulator" path = "src/simulator.rs" required-features = ["simulator"] +[[bin]] +name = "async" +path = "src/async.rs" +required-features = ["async"] + [[bin]] name = "lightweight" path = "src/lightweight.rs" @@ -41,23 +46,24 @@ anyhow = { workspace = true } autd3 = { workspace = true } autd3-driver = { workspace = true } autd3-gain-holo = { workspace = true } -autd3-link-simulator = { workspace = true, optional = true } +autd3-link-simulator = { workspace = true, features = ["blocking"], optional = true } autd3-link-twincat = { workspace = true, optional = true } autd3-modulation-audio-file = { workspace = true } autd3-protobuf = { workspace = true, optional = true } color-print = { workspace = true } -tokio = { workspace = true, features = ["rt-multi-thread", "macros", "sync", "io-std", "io-util"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"], optional = true } tonic = { workspace = true, optional = true } tracing-subscriber = { workspace = true } tracing = { workspace = true } [features] default = ["all"] +async = ["autd3/async", "tokio"] simulator = ["autd3-link-simulator"] twincat = ["autd3-link-twincat/local"] remote_twincat = ["autd3-link-twincat/remote"] lightweight = ["autd3-protobuf/lightweight", "autd3-driver/async-trait", "autd3-driver/lightweight"] lightweight-server = ["autd3-protobuf/lightweight", "autd3-link-twincat/local", "autd3-link-twincat/async-trait", "autd3-driver/async-trait", "autd3-driver/lightweight", "tonic", "tokio/signal"] -all = ["twincat", "remote_twincat", "simulator"] +all = ["async", "twincat", "remote_twincat", "simulator"] unity = ["autd3-driver/use_meter", "autd3-driver/left_handed"] dynamic_freq = ["autd3-driver/dynamic_freq"] diff --git a/examples/README.md b/examples/README.md index d60ac4c3..359d1e60 100644 --- a/examples/README.md +++ b/examples/README.md @@ -14,4 +14,4 @@ cargo run --release --features simulator --bin simulator # Author -Shun Suzuki, 2022-2024 +Shun Suzuki, 2022-2025 diff --git a/examples/src/async.rs b/examples/src/async.rs new file mode 100644 index 00000000..452c4f15 --- /dev/null +++ b/examples/src/async.rs @@ -0,0 +1,36 @@ +use anyhow::Result; + +use autd3::prelude::*; +use autd3::r#async::Controller; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .init(); + + let mut autd = + Controller::builder([AUTD3::new(Point3::origin()), AUTD3::new(Point3::origin())]) + .open(Nop::builder()) + .await?; + + println!("======== AUTD3 firmware information ========"); + autd.firmware_version().await?.iter().for_each(|firm_info| { + println!("{}", firm_info); + }); + println!("============================================"); + + autd.send(( + Sine::new(150. * Hz), + Focus::new(Point3::new(90., 70., 150.)), + )) + .await?; + + println!("Press enter to exit..."); + let mut _s = String::new(); + std::io::stdin().read_line(&mut _s)?; + + autd.close().await?; + + Ok(()) +} diff --git a/examples/src/nop.rs b/examples/src/nop.rs index 684db6ec..c4ed0af8 100644 --- a/examples/src/nop.rs +++ b/examples/src/nop.rs @@ -4,15 +4,13 @@ use anyhow::Result; use autd3::prelude::*; -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<()> { tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .init(); let autd = Controller::builder([AUTD3::new(Point3::origin()), AUTD3::new(Point3::origin())]) - .open(Nop::builder()) - .await?; + .open(Nop::builder())?; - tests::run(autd).await + tests::run(autd) } diff --git a/examples/src/remote_twincat.rs b/examples/src/remote_twincat.rs index 1022e470..dc3ebd5f 100644 --- a/examples/src/remote_twincat.rs +++ b/examples/src/remote_twincat.rs @@ -5,11 +5,9 @@ use anyhow::Result; use autd3::prelude::*; use autd3_link_twincat::RemoteTwinCAT; -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<()> { let autd = Controller::builder([AUTD3::new(Point3::origin())]) - .open(RemoteTwinCAT::builder("0.0.0.0.0.0")) - .await?; + .open(RemoteTwinCAT::builder("0.0.0.0.0.0"))?; - tests::run(autd).await + tests::run(autd) } diff --git a/examples/src/simulator.rs b/examples/src/simulator.rs index 8e418242..eacc48a3 100644 --- a/examples/src/simulator.rs +++ b/examples/src/simulator.rs @@ -5,14 +5,12 @@ use anyhow::Result; use autd3::prelude::*; use autd3_link_simulator::Simulator; -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<()> { let autd = Controller::builder([ AUTD3::new(Point3::origin()), AUTD3::new(Point3::new(AUTD3::DEVICE_WIDTH, 0.0, 0.0)), ]) - .open(Simulator::builder("127.0.0.1:8080".parse()?)) - .await?; + .open(Simulator::builder("127.0.0.1:8080".parse()?))?; - tests::run(autd).await + tests::run(autd) } diff --git a/examples/src/tests/audio_file.rs b/examples/src/tests/audio_file.rs index d2a64929..c0bd2b45 100644 --- a/examples/src/tests/audio_file.rs +++ b/examples/src/tests/audio_file.rs @@ -1,7 +1,7 @@ -use autd3::{driver::link::AsyncLink, prelude::*}; +use autd3::{driver::link::Link, prelude::*}; -pub async fn audio_file(autd: &mut Controller) -> anyhow::Result { - autd.send(Silencer::default()).await?; +pub fn audio_file(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::default())?; let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); @@ -9,7 +9,7 @@ pub async fn audio_file(autd: &mut Controller) -> anyhow::Result const WAV_FILE: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/src/resources/sin150.wav"); let m = autd3_modulation_audio_file::Wav::new(WAV_FILE)?; - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/tests/bessel.rs b/examples/src/tests/bessel.rs index d2ae2648..3dd501a2 100644 --- a/examples/src/tests/bessel.rs +++ b/examples/src/tests/bessel.rs @@ -1,7 +1,7 @@ -use autd3::{driver::link::AsyncLink, prelude::*}; +use autd3::{driver::link::Link, prelude::*}; -pub async fn bessel(autd: &mut Controller) -> anyhow::Result { - autd.send(Silencer::default()).await?; +pub fn bessel(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::default())?; let center = autd.center(); let dir = Vector3::z_axis(); @@ -9,7 +9,7 @@ pub async fn bessel(autd: &mut Controller) -> anyhow::Result) -> anyhow::Result { - autd.send(Silencer::disable()).await?; +pub fn custom(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::disable())?; let m = autd3::modulation::Custom::new([0, 255], 4 * kHz)?; let g = autd3::gain::Custom::new(|dev| { @@ -12,7 +12,7 @@ pub async fn custom(autd: &mut Controller) -> anyhow::Result) -> anyhow::Result { - autd.send(Silencer::disable()).await?; +pub fn fir(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::disable())?; let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); @@ -37,7 +37,7 @@ pub async fn fir(autd: &mut Controller) -> anyhow::Result .with_sampling_config(20 * kHz)? .with_fir(filt); - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/tests/flag.rs b/examples/src/tests/flag.rs index de6ccd5d..49fd1d2c 100644 --- a/examples/src/tests/flag.rs +++ b/examples/src/tests/flag.rs @@ -1,25 +1,21 @@ -use tokio::io::AsyncBufReadExt; +use autd3::{driver::link::Link, prelude::*}; -use autd3::{driver::link::AsyncLink, prelude::*}; - -pub async fn flag(autd: &mut Controller) -> anyhow::Result { - autd.send(ReadsFPGAState::new(|_dev| true)).await?; +pub fn flag(autd: &mut Controller) -> anyhow::Result { + autd.send(ReadsFPGAState::new(|_dev| true))?; println!("press any key to force fan..."); let mut _s = String::new(); std::io::stdin().read_line(&mut _s)?; - autd.send(ForceFan::new(|_dev| true)).await?; + autd.send(ForceFan::new(|_dev| true))?; - let (tx, mut rx) = tokio::sync::oneshot::channel(); + let (tx, rx) = std::sync::mpsc::channel(); println!("press any key to stop checking FPGA status..."); - let fin_signal = tokio::spawn(async move { + let fin_signal = std::thread::spawn(move || { let mut _s = String::new(); - tokio::io::BufReader::new(tokio::io::stdin()) - .read_line(&mut _s) - .await?; + std::io::stdin().read_line(&mut _s)?; _ = tx.send(()); - tokio::io::Result::Ok(()) + std::io::Result::Ok(()) }); let prompts = ['-', '/', '|', '\\']; @@ -28,7 +24,7 @@ pub async fn flag(autd: &mut Controller) -> anyhow::Result if rx.try_recv().is_ok() { break; } - let states = autd.fpga_state().await?; + let states = autd.fpga_state()?; println!("{} FPGA Status...", prompts[idx / 1000 % prompts.len()]); idx += 1; states.iter().enumerate().for_each(|(i, state)| { @@ -42,13 +38,12 @@ pub async fn flag(autd: &mut Controller) -> anyhow::Result } print!("\x1b[1F\x1b[0J"); - fin_signal.await??; + let _ = fin_signal.join(); autd.send(( ForceFan::new(|_dev| false), ReadsFPGAState::new(|_dev| false), - )) - .await?; + ))?; Ok(true) } diff --git a/examples/src/tests/focus.rs b/examples/src/tests/focus.rs index f2513e77..8a01224a 100644 --- a/examples/src/tests/focus.rs +++ b/examples/src/tests/focus.rs @@ -1,14 +1,14 @@ -use autd3::{driver::link::AsyncLink, prelude::*}; +use autd3::{driver::link::Link, prelude::*}; -pub async fn focus(autd: &mut Controller) -> anyhow::Result { - autd.send(Silencer::default()).await?; +pub fn focus(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::default())?; let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); let g = Focus::new(center); let m = Sine::new(150. * Hz); - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/tests/group.rs b/examples/src/tests/group.rs index 9c16cd7f..6c07d443 100644 --- a/examples/src/tests/group.rs +++ b/examples/src/tests/group.rs @@ -1,6 +1,6 @@ -use autd3::{driver::link::AsyncLink, prelude::*}; +use autd3::{driver::link::Link, prelude::*}; -pub async fn group_by_device(autd: &mut Controller) -> anyhow::Result { +pub fn group_by_device(autd: &mut Controller) -> anyhow::Result { let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); autd.group(|dev| match dev.idx() { @@ -10,13 +10,12 @@ pub async fn group_by_device(autd: &mut Controller) -> anyhow::R }) .set("null", (Static::new(), Null::new()))? .set("focus", (Sine::new(150. * Hz), Focus::new(center)))? - .send() - .await?; + .send()?; Ok(true) } -pub async fn group_by_transducer(autd: &mut Controller) -> anyhow::Result { +pub fn group_by_transducer(autd: &mut Controller) -> anyhow::Result { let cx = autd.center().x; let g1 = Focus::new(autd[0].center() + Vector3::new(0., 0., 150.0 * mm)); let g2 = Null::new(); @@ -33,7 +32,7 @@ pub async fn group_by_transducer(autd: &mut Controller) -> anyho .set("null", g2)?; let m = Sine::new(150. * Hz); - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/tests/holo.rs b/examples/src/tests/holo.rs index ad154377..ca75eabe 100644 --- a/examples/src/tests/holo.rs +++ b/examples/src/tests/holo.rs @@ -1,7 +1,7 @@ use autd3::{ driver::{ datagram::{BoxedGain, IntoBoxedGain}, - link::AsyncLink, + link::Link, }, prelude::*, }; @@ -9,8 +9,8 @@ use autd3_gain_holo::*; use std::io::{self, Write}; -pub async fn holo(autd: &mut Controller) -> anyhow::Result { - autd.send(Silencer::default()).await?; +pub fn holo(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::default())?; let center = autd.center() + Vector3::new(0., 0., 150.0 * mm); let p = Vector3::new(30. * mm, 0., 0.); @@ -44,7 +44,7 @@ pub async fn holo(autd: &mut Controller) -> anyhow::Result let m = Sine::new(150. * Hz); let g = gains.swap_remove(idx).1; - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/tests/plane.rs b/examples/src/tests/plane.rs index 18f7e9e8..151f01a7 100644 --- a/examples/src/tests/plane.rs +++ b/examples/src/tests/plane.rs @@ -1,14 +1,14 @@ -use autd3::{driver::link::AsyncLink, prelude::*}; +use autd3::{driver::link::Link, prelude::*}; -pub async fn plane(autd: &mut Controller) -> anyhow::Result { - autd.send(Silencer::default()).await?; +pub fn plane(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::default())?; let dir = Vector3::z_axis(); let m = Sine::new(150. * Hz); let g = Plane::new(dir); - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/tests/stm.rs b/examples/src/tests/stm.rs index d60ce613..c992511c 100644 --- a/examples/src/tests/stm.rs +++ b/examples/src/tests/stm.rs @@ -1,7 +1,7 @@ -use autd3::{driver::link::AsyncLink, prelude::*}; +use autd3::{driver::link::Link, prelude::*}; -pub async fn foci_stm(autd: &mut Controller) -> anyhow::Result { - autd.send(Silencer::disable()).await?; +pub fn foci_stm(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::disable())?; let stm = FociSTM::new( 1.0 * Hz, @@ -16,13 +16,13 @@ pub async fn foci_stm(autd: &mut Controller) -> anyhow::Result) -> anyhow::Result { - autd.send(Silencer::disable()).await?; +pub fn gain_stm(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::disable())?; let stm = GainSTM::new( 1.0 * Hz, @@ -37,7 +37,7 @@ pub async fn gain_stm(autd: &mut Controller) -> anyhow::Result(mut autd: Controller) -> anyhow::Result<()> { +pub fn run(mut autd: Controller) -> anyhow::Result<()> { type Test = ( &'static str, - fn( - &'_ mut Controller, - ) - -> std::pin::Pin> + '_>>, + fn(&'_ mut Controller) -> anyhow::Result, ); println!("======== AUTD3 firmware information ========"); - autd.firmware_version().await?.iter().for_each(|firm_info| { + autd.firmware_version()?.iter().for_each(|firm_info| { println!("{}", firm_info); }); println!("============================================"); let mut examples: Vec> = vec![ - ("Single focus test", |autd| Box::pin(focus(autd))), - ("Bessel beam test", |autd| Box::pin(bessel(autd))), - ("Plane wave test", |autd| Box::pin(plane(autd))), - ("Wav modulation test", |autd| Box::pin(audio_file(autd))), - ("FociSTM test", |autd| Box::pin(foci_stm(autd))), - ("GainSTM test", |autd| Box::pin(gain_stm(autd))), - ("Multiple foci test", |autd| Box::pin(holo(autd))), - ("FIR test", |autd| Box::pin(fir(autd))), + ("Single focus test", |autd| focus(autd)), + ("Bessel beam test", |autd| bessel(autd)), + ("Plane wave test", |autd| plane(autd)), + ("Wav modulation test", |autd| audio_file(autd)), + ("FociSTM test", |autd| foci_stm(autd)), + ("GainSTM test", |autd| gain_stm(autd)), + ("Multiple foci test", |autd| holo(autd)), + ("FIR test", |autd| fir(autd)), ("User-defined Gain & Modulation test", |autd| { - Box::pin(user_defined(autd)) + user_defined(autd) }), - ("Flag test", |autd| Box::pin(flag(autd))), - ("Custom Gain test", |autd| Box::pin(custom(autd))), + ("Flag test", |autd| flag(autd)), + ("Custom Gain test", |autd| custom(autd)), ("Group (by Transducer) test", |autd| { - Box::pin(group_by_transducer(autd)) + group_by_transducer(autd) }), ]; if autd.num_devices() >= 2 { - examples.push(("Group (by Device) test", |autd| { - Box::pin(group_by_device(autd)) - })); + examples.push(("Group (by Device) test", |autd| (group_by_device(autd)))); } loop { @@ -58,7 +53,7 @@ pub async fn run(mut autd: Controller) -> anyhow::Result<()> { io::stdin().read_line(&mut s)?; match s.trim().parse::() { Ok(i) if i < examples.len() => { - if !(examples[i].1)(&mut autd).await? { + if !(examples[i].1)(&mut autd)? { eprintln!("Failed to send data"); } } @@ -69,11 +64,11 @@ pub async fn run(mut autd: Controller) -> anyhow::Result<()> { let mut _s = String::new(); io::stdin().read_line(&mut _s)?; - autd.send((Static::new(), Null::default())).await?; - autd.send(Silencer::default()).await?; + autd.send((Static::new(), Null::default()))?; + autd.send(Silencer::default())?; } - autd.close().await?; + autd.close()?; Ok(()) } diff --git a/examples/src/tests/user_defined_gain_modulation.rs b/examples/src/tests/user_defined_gain_modulation.rs index e9b5328b..6bcab514 100644 --- a/examples/src/tests/user_defined_gain_modulation.rs +++ b/examples/src/tests/user_defined_gain_modulation.rs @@ -1,4 +1,4 @@ -use autd3::{driver::link::AsyncLink, prelude::*}; +use autd3::{driver::link::Link, prelude::*}; use autd3_driver::derive::*; #[derive(Gain, Clone, Copy, Debug)] @@ -59,13 +59,13 @@ impl Modulation for Burst { } } -pub async fn user_defined(autd: &mut Controller) -> anyhow::Result { - autd.send(Silencer::disable()).await?; +pub fn user_defined(autd: &mut Controller) -> anyhow::Result { + autd.send(Silencer::disable())?; let g = MyUniform::new(); let m = Burst::new(); - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/twincat.rs b/examples/src/twincat.rs index 9c6dcc7e..2a5d9e02 100644 --- a/examples/src/twincat.rs +++ b/examples/src/twincat.rs @@ -5,11 +5,8 @@ use anyhow::Result; use autd3::prelude::*; use autd3_link_twincat::TwinCAT; -#[tokio::main] -async fn main() -> Result<()> { - let autd = Controller::builder([AUTD3::new(Point3::origin())]) - .open(TwinCAT::builder()) - .await?; +fn main() -> Result<()> { + let autd = Controller::builder([AUTD3::new(Point3::origin())]).open(TwinCAT::builder())?; - tests::run(autd).await + tests::run(autd) } From aa512241b69aff35786c6d66223a69e4cf13bae0 Mon Sep 17 00:00:00 2001 From: shun suzuki Date: Fri, 10 Jan 2025 17:18:22 +0900 Subject: [PATCH 3/8] speed up github actions build --- .github/workflows/build.yml | 16 +++++++++++----- .github/workflows/pr.yml | 16 +++++++++++----- CHANGELOG.md | 1 + build.py | 2 ++ 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c24a26bb..f7ebb184 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,10 +21,16 @@ jobs: matrix: include: - os: windows-latest + no-example: true lint: true - - os: ubuntu-latest - lint: true + - os: windows-latest + arch: aarch64 + no-example: true + no-test: true - os: macos-latest + no-example: true + lint: true + - os: ubuntu-latest lint: true - os: ubuntu-latest arch: arm32 @@ -34,12 +40,12 @@ jobs: arch: aarch64 no-example: true no-test: true - - os: windows-latest - arch: aarch64 - no-test: true - os: ubuntu-latest features: "use_meter left_handed" lint: true + - os: ubuntu-latest + features: "async" + lint: true - os: ubuntu-latest features: "lightweight async-trait" lint: true diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 52a31276..8cedf212 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -56,10 +56,16 @@ jobs: matrix: include: - os: windows-latest + no-example: true lint: true - - os: ubuntu-latest - lint: true + - os: windows-latest + arch: aarch64 + no-example: true + no-test: true - os: macos-latest + no-example: true + lint: true + - os: ubuntu-latest lint: true - os: ubuntu-latest arch: arm32 @@ -69,12 +75,12 @@ jobs: arch: aarch64 no-example: true no-test: true - - os: windows-latest - arch: aarch64 - no-test: true - os: ubuntu-latest features: "use_meter left_handed" lint: true + - os: ubuntu-latest + features: "async" + lint: true - os: ubuntu-latest features: "lightweight async-trait" lint: true diff --git a/CHANGELOG.md b/CHANGELOG.md index ff429be6..4c99db8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Update firmware to v10.0.1 - phase correction bram and pulse width encoder table reset to default in clear op - support for `dynamic_freq` version +- Make `async` optional - Remove `Deref` and `DerefMut` for `Controller` - Impl `Deref` and `DerefMut` for `Controller` instead - Make `Transducer::new` public diff --git a/build.py b/build.py index 7dcece8b..5d5552e6 100644 --- a/build.py +++ b/build.py @@ -74,6 +74,8 @@ def cargo_command(self, subcommands: list[str], additional_features: str | None features = self.features if additional_features is not None: features += " " + additional_features + if "async" not in features: + command.extend(["--exclude", "autd3-protobuf", "--exclude", "autd3-link-simulator"]) command.extend(["--features", features]) return command From 2c0bc7a6123248dbcc2e0affaf5ecf6f5fc5d665 Mon Sep 17 00:00:00 2001 From: shun suzuki Date: Fri, 10 Jan 2025 18:10:57 +0900 Subject: [PATCH 4/8] fix doc examples --- autd3/src/async/controller/group.rs | 1 + autd3/src/async/controller/mod.rs | 1 + autd3/src/controller/group.rs | 6 +++--- autd3/src/controller/mod.rs | 6 +++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/autd3/src/async/controller/group.rs b/autd3/src/async/controller/group.rs index 4175e75c..c527604c 100644 --- a/autd3/src/async/controller/group.rs +++ b/autd3/src/async/controller/group.rs @@ -174,6 +174,7 @@ impl Controller { /// /// ``` /// # use autd3::prelude::*; + /// # use autd3::r#async::controller::Controller; /// # tokio_test::block_on(async { /// let mut autd = Controller::builder((0..3).map(|_| AUTD3::new(Point3::origin()))).open(Nop::builder()).await?; /// diff --git a/autd3/src/async/controller/mod.rs b/autd3/src/async/controller/mod.rs index ac1eb5bb..c213f5cf 100644 --- a/autd3/src/async/controller/mod.rs +++ b/autd3/src/async/controller/mod.rs @@ -170,6 +170,7 @@ impl Controller { /// /// ``` /// # use autd3::prelude::*; + /// # use autd3::r#async::controller::Controller; /// # tokio_test::block_on(async { /// let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(Nop::builder()).await?; /// diff --git a/autd3/src/controller/group.rs b/autd3/src/controller/group.rs index 23659d12..d6a63173 100644 --- a/autd3/src/controller/group.rs +++ b/autd3/src/controller/group.rs @@ -172,7 +172,7 @@ impl Controller { /// /// ``` /// # use autd3::prelude::*; - /// # tokio_test::block_on(async { + /// # fn main() -> Result<(), AUTDError> { /// let mut autd = Controller::builder((0..3).map(|_| AUTD3::new(Point3::origin()))).open(Nop::builder())?; /// /// autd.group(|dev| match dev.idx() { @@ -183,8 +183,8 @@ impl Controller { /// .set("static", Static::new())? /// .set("sine", Sine::new(150 * Hz))? /// .send()?; - /// # Result::<(), AUTDError>::Ok(()) - /// # }); + /// # Ok(()) + /// # } /// ``` #[must_use] pub fn group Option>( diff --git a/autd3/src/controller/mod.rs b/autd3/src/controller/mod.rs index 3d9343ef..4c7f3e9b 100644 --- a/autd3/src/controller/mod.rs +++ b/autd3/src/controller/mod.rs @@ -164,14 +164,14 @@ impl Controller { /// /// ``` /// # use autd3::prelude::*; - /// # tokio_test::block_on(async { + /// # fn main() -> Result<(), AUTDError> { /// let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(Nop::builder())?; /// /// autd.send(ReadsFPGAState::new(|_| true))?; /// /// let states = autd.fpga_state()?; - /// # Result::<(), AUTDError>::Ok(()) - /// # }); + /// Ok(()) + /// # } /// ``` /// /// [`ReadsFPGAState`]: autd3_driver::datagram::ReadsFPGAState From f07ab04af99d4743bcfcbd3adabaea4466543c5a Mon Sep 17 00:00:00 2001 From: shun suzuki Date: Fri, 10 Jan 2025 18:34:44 +0900 Subject: [PATCH 5/8] fix lightweight server --- autd3-protobuf/src/lightweight/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autd3-protobuf/src/lightweight/server.rs b/autd3-protobuf/src/lightweight/server.rs index 0d1090eb..41083df9 100644 --- a/autd3-protobuf/src/lightweight/server.rs +++ b/autd3-protobuf/src/lightweight/server.rs @@ -14,7 +14,7 @@ pub struct LightweightServer< > where L::L: Sync, { - autd: RwLock>>, + autd: RwLock>>, link: F, } @@ -178,7 +178,7 @@ where if let Some(ref geometry) = req.geometry { if let Ok(geometry) = autd3_driver::geometry::Geometry::from_msg(geometry) { #[allow(unused_mut)] - let mut builder = autd3::Controller::builder(geometry.iter().map(|d| { + let mut builder = autd3::r#async::Controller::builder(geometry.iter().map(|d| { autd3::prelude::AUTD3::new(*d[0].position()).with_rotation(*d.rotation()) })) .with_default_parallel_threshold(geometry.default_parallel_threshold() as _) From 342e02fd22ac0a8fa228dda5ca9b8e34fbf0ca55 Mon Sep 17 00:00:00 2001 From: shun suzuki Date: Fri, 10 Jan 2025 18:38:31 +0900 Subject: [PATCH 6/8] add tests --- autd3/src/controller/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/autd3/src/controller/mod.rs b/autd3/src/controller/mod.rs index 4c7f3e9b..1e7e6dd7 100644 --- a/autd3/src/controller/mod.rs +++ b/autd3/src/controller/mod.rs @@ -314,7 +314,7 @@ mod tests { ] .into_iter(), )?, - ))?; + ))?; // GRCOV_EXCL_LINE autd.iter().try_for_each(|dev| { assert_eq!( @@ -511,4 +511,14 @@ mod tests { Ok(()) } + + #[test] + fn into_boxed_link_close() -> anyhow::Result<()> { + let autd = create_controller(1)?; + let autd = autd.into_boxed_link(); + + autd.close()?; + + Ok(()) + } } From 4cc9a26e831140f320765366299c4f1ee633140b Mon Sep 17 00:00:00 2001 From: shun suzuki Date: Fri, 10 Jan 2025 18:48:52 +0900 Subject: [PATCH 7/8] add GRCOV_EXCL_LINE --- autd3/src/controller/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autd3/src/controller/mod.rs b/autd3/src/controller/mod.rs index 1e7e6dd7..aa553935 100644 --- a/autd3/src/controller/mod.rs +++ b/autd3/src/controller/mod.rs @@ -481,7 +481,7 @@ mod tests { ] .into_iter(), )?, - ))?; + ))?; // GRCOV_EXCL_LINE let autd = unsafe { Controller::::from_boxed_link(autd) }; From 58a1a29325200e09f54cd82f7da622908f2f6b30 Mon Sep 17 00:00:00 2001 From: shun suzuki Date: Fri, 10 Jan 2025 19:02:50 +0900 Subject: [PATCH 8/8] add GRCOV_EXCL_LINE --- autd3/src/controller/group.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autd3/src/controller/group.rs b/autd3/src/controller/group.rs index d6a63173..c09b005b 100644 --- a/autd3/src/controller/group.rs +++ b/autd3/src/controller/group.rs @@ -238,7 +238,7 @@ mod tests { .into_iter(), )?, ), - )? + )? // GRCOV_EXCL_LINE .send()?; assert_eq!(