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/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.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..a384e709 --- /dev/null +++ b/autd3-driver/src/link/mod.rs @@ -0,0 +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 992f88ed..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)] @@ -13,10 +14,10 @@ use std::net::SocketAddr; use autd3_driver::{ derive::*, firmware::cpu::{RxMessage, TxMessage}, - link::{Link, LinkBuilder}, + link::{AsyncLink, AsyncLinkBuilder}, }; -/// A [`Link`] for [`AUTD3 Simulator`]. +/// A [`AsyncLink`] for [`AUTD3 Simulator`]. /// /// [`AUTD3 Simulator`]: https://github.com/shinolab/autd3-server pub struct Simulator { @@ -34,7 +35,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 +75,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(()); @@ -137,3 +138,61 @@ impl Link 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 a2367fcd..dc64262c 100644 --- a/autd3-link-twincat/src/local/mod.rs +++ b/autd3-link-twincat/src/local/mod.rs @@ -46,11 +46,10 @@ pub struct TwinCAT { #[derive(Builder)] pub struct TwinCATBuilder {} -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] 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 Link for TwinCAT { - async fn close(&mut self) -> Result<(), AUTDDriverError> { + fn close(&mut self) -> Result<(), AUTDDriverError> { unsafe { self.dll .get:: i32>(b"AdsPortCloseEx") @@ -106,7 +104,7 @@ impl Link 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 Link 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 a8ada652..8d4e8f88 100644 --- a/autd3-link-twincat/src/remote/remote_twincat_link.rs +++ b/autd3-link-twincat/src/remote/remote_twincat_link.rs @@ -43,12 +43,11 @@ pub struct RemoteTwinCATBuilder { client_ams_net_id: String, } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] 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 Link for RemoteTwinCAT { - async fn close(&mut self) -> Result<(), AUTDDriverError> { + fn close(&mut self) -> Result<(), AUTDDriverError> { if self.port == 0 { return Ok(()); } @@ -152,7 +150,7 @@ impl Link 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 Link 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 Link 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-protobuf/src/lightweight/server.rs b/autd3-protobuf/src/lightweight/server.rs index e2cb7c00..41083df9 100644 --- a/autd3-protobuf/src/lightweight/server.rs +++ b/autd3-protobuf/src/lightweight/server.rs @@ -9,12 +9,12 @@ 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, { - autd: RwLock>>, + autd: RwLock>>, link: F, } @@ -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, @@ -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 _) 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..c527604c --- /dev/null +++ b/autd3/src/async/controller/group.rs @@ -0,0 +1,450 @@ +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::*; + /// # 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?; + /// + /// 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..c213f5cf --- /dev/null +++ b/autd3/src/async/controller/mod.rs @@ -0,0 +1,542 @@ +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::*; + /// # use autd3::r#async::controller::Controller; + /// # 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 2dad0552..d4d93bb2 100644 --- a/autd3/src/controller/builder.rs +++ b/autd3/src/controller/builder.rs @@ -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 e559a418..c09b005b 100644 --- a/autd3/src/controller/group.rs +++ b/autd3/src/controller/group.rs @@ -126,7 +126,7 @@ impl<'a, K: PartialEq + Debug, L: Link> 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,20 +148,18 @@ impl<'a, K: PartialEq + Debug, L: Link> 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, + ) } } @@ -174,8 +172,8 @@ 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?; + /// # 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() { /// 0 => Some("static"), @@ -184,9 +182,9 @@ impl Controller { /// }) /// .set("static", Static::new())? /// .set("sine", Sine::new(150 * Hz))? - /// .send().await?; - /// # Result::<(), AUTDError>::Ok(()) - /// # }); + /// .send()?; + /// # Ok(()) + /// # } /// ``` #[must_use] pub fn group Option>( @@ -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()), @@ -240,9 +238,8 @@ mod tests { .into_iter(), )?, ), - )? - .send() - .await?; + )? // GRCOV_EXCL_LINE + .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 915c44a9..aa553935 100644 --- a/autd3/src/controller/mod.rs +++ b/autd3/src/controller/mod.rs @@ -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 @@ -170,24 +164,24 @@ impl Controller { /// /// ``` /// # use autd3::prelude::*; - /// # tokio_test::block_on(async { - /// let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(Nop::builder()).await?; + /// # fn main() -> Result<(), AUTDError> { + /// 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?; - /// # Result::<(), AUTDError>::Ok(()) - /// # }); + /// let states = autd.fpga_state()?; + /// 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) @@ -213,8 +207,6 @@ impl<'a, L: Link> IntoIterator for &'a mut Controller { } } -#[cfg_attr(docsrs, doc(cfg(feature = "async-trait")))] -#[cfg(feature = "async-trait")] impl Controller { /// Converts `Controller` into a `Controller>`. pub fn into_boxed_link(self) -> Controller> { @@ -261,15 +253,7 @@ impl Drop for Controller { 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?; + ))?; // GRCOV_EXCL_LINE 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?; + ))?; // GRCOV_EXCL_LINE let autd = unsafe { Controller::::from_boxed_link(autd) }; @@ -534,7 +507,17 @@ mod tests { anyhow::Ok(()) })?; - autd.close().await?; + autd.close()?; + + Ok(()) + } + + #[test] + fn into_boxed_link_close() -> anyhow::Result<()> { + let autd = create_controller(1)?; + let autd = autd.into_boxed_link(); + + autd.close()?; Ok(()) } diff --git a/autd3/src/controller/timer/mod.rs b/autd3/src/controller/timer/mod.rs index 2402e0d4..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}; @@ -15,11 +20,6 @@ use autd3_driver::{ }; 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,7 +59,7 @@ pub struct Timer { } impl Timer { - pub(crate) async fn send( + pub(crate) fn send( &self, geometry: &Geometry, tx: &mut [TxMessage], @@ -76,35 +74,20 @@ 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, @@ -115,7 +98,7 @@ impl Timer { 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,18 +106,18 @@ 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], @@ -147,14 +130,13 @@ 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], @@ -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 Link for MockLink { - async fn close(&mut self) -> Result<(), AUTDDriverError> { + 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 efa88e81..82841909 100644 --- a/autd3/src/link/audit.rs +++ b/autd3/src/link/audit.rs @@ -41,14 +41,10 @@ pub struct AuditBuilder { down: bool, } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] 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 Link for Audit { - async fn close(&mut self) -> Result<(), AUTDDriverError> { + 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 Link 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 Link 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 1479da05..95bf71f2 100644 --- a/autd3/src/link/nop.rs +++ b/autd3/src/link/nop.rs @@ -17,11 +17,10 @@ pub struct Nop { #[derive(Builder)] pub struct NopBuilder {} -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] 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 LinkBuilder for NopBuilder { } } -#[cfg_attr(feature = "async-trait", autd3_driver::async_trait)] impl Link for Nop { - async fn close(&mut self) -> Result<(), AUTDDriverError> { + 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 Link 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 Link 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 96% rename from autd3/tests/link/audit.rs rename to autd3/tests/async/link/audit.rs index 614078e8..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] @@ -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/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 8a43d4a1..67c3ec1d 100644 --- a/autd3/tests/link/nop.rs +++ b/autd3/tests/async/link/nop.rs @@ -1,4 +1,4 @@ -use autd3::{driver::link::Link, 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..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 @@ -130,6 +132,7 @@ def rust_test(args) -> None: # noqa: ANN001 def rust_run(args) -> None: # noqa: ANN001 examples = [ + "async", "nop", "twincat", "remote_twincat", @@ -143,6 +146,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 0b22a341..c0bd2b45 100644 --- a/examples/src/tests/audio_file.rs +++ b/examples/src/tests/audio_file.rs @@ -1,7 +1,7 @@ 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) -> 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 { let g = Bessel::new(center, dir, 18. / 180. * PI * rad); let m = Sine::new(150. * Hz); - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/tests/custom.rs b/examples/src/tests/custom.rs index 6deefd0d..c0ad3511 100644 --- a/examples/src/tests/custom.rs +++ b/examples/src/tests/custom.rs @@ -1,7 +1,7 @@ use autd3::{driver::link::Link, prelude::*}; -pub async fn custom(autd: &mut Controller) -> 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 { } }); - autd.send((m, g)).await?; + autd.send((m, g))?; Ok(true) } diff --git a/examples/src/tests/fir.rs b/examples/src/tests/fir.rs index 743c259b..b6c068bd 100644 --- a/examples/src/tests/fir.rs +++ b/examples/src/tests/fir.rs @@ -1,7 +1,7 @@ use autd3::{driver::link::Link, prelude::*}; -pub async fn fir(autd: &mut Controller) -> 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 3d90a94c..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::*}; -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 ed8850ed..8a01224a 100644 --- a/examples/src/tests/focus.rs +++ b/examples/src/tests/focus.rs @@ -1,14 +1,14 @@ 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 b900955f..6c07d443 100644 --- a/examples/src/tests/group.rs +++ b/examples/src/tests/group.rs @@ -1,6 +1,6 @@ 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::Result }) .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) -> anyhow::Re .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 3959ba79..ca75eabe 100644 --- a/examples/src/tests/holo.rs +++ b/examples/src/tests/holo.rs @@ -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 8cee6450..151f01a7 100644 --- a/examples/src/tests/plane.rs +++ b/examples/src/tests/plane.rs @@ -1,14 +1,14 @@ 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 e65d3564..c992511c 100644 --- a/examples/src/tests/stm.rs +++ b/examples/src/tests/stm.rs @@ -1,7 +1,7 @@ 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 let m = Static::new(); - autd.send((m, stm)).await?; + autd.send((m, stm))?; Ok(true) } -pub async fn gain_stm(autd: &mut Controller) -> 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 let m = Static::new(); - autd.send((m, stm)).await?; + autd.send((m, stm))?; Ok(true) } diff --git a/examples/src/tests/test_runner.rs b/examples/src/tests/test_runner.rs index c6530e84..5625edc8 100644 --- a/examples/src/tests/test_runner.rs +++ b/examples/src/tests/test_runner.rs @@ -7,43 +7,38 @@ use super::{ stm::*, user_defined_gain_modulation::*, }; -pub async fn run(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 8404e1c4..6bcab514 100644 --- a/examples/src/tests/user_defined_gain_modulation.rs +++ b/examples/src/tests/user_defined_gain_modulation.rs @@ -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) }