Send Modes
The Frames obtained from DatagramBuilder::build may be split into multiple Frames.
There are two ways to send a Frame, with a trade-off between reliability and throughput.
Below, the two send methods are compared using an example that moves the focal point along a circle.
The focal positions to move to are prepared in advance as targets.
// Prepare 1000 focus points along a circle 150 mm above the array center.let center = geometry.center() + offset(0.0 * mm, 0.0 * mm, 150.0 * mm);let targets: Vec<Point3<f32>> = (0..NUM_POINTS) .map(|i| { let theta = 2.0 * PI * i as f32 / NUM_POINTS as f32; center + offset( RADIUS_MM * theta.cos() * mm, RADIUS_MM * theta.sin() * mm, 0.0 * mm, ) }) .collect();# Prepare 1000 focus points along a circle 150 mm above the array center.center = geometry.center() + np.array([0.0, 0.0, 150.0])targets = [ center + np.array( [ RADIUS_MM * math.cos(2.0 * math.pi * i / NUM_POINTS), RADIUS_MM * math.sin(2.0 * math.pi * i / NUM_POINTS), 0.0, ] ) for i in range(NUM_POINTS)]// Prepare 1000 focus points along a circle 150 mm above the array center.var center = geometry.Center + new Vector3(0.0f, 0.0f, 150.0f);var targets = new Vector3[NumPoints];for (var i = 0; i < NumPoints; i++){ var theta = 2.0f * MathF.PI * i / NumPoints; targets[i] = center + new Vector3(RadiusMm * MathF.Cos(theta), RadiusMm * MathF.Sin(theta), 0.0f);}stop-and-wait
Section titled “stop-and-wait”A method that waits for a response after each frame before sending the next.
It uses send_checked.
Because it proceeds to the next frame only after confirming that each update has been reliably applied, it is reliable, but throughput is low because a round-trip latency is incurred on every frame.
let mut patterns = geometry.pattern_buffer();for &target in targets { autd3_rs_pattern::focus( geometry, target, wavelength, &autd3_rs_pattern::FocusOption::default(), &mut patterns, ); let mut builder = client.datagram_builder(); builder.push(Pattern::new(&patterns)); for frame in &builder.build()? { client.send_checked(frame).await?; }}patterns = geometry.pattern_buffer()for target in targets: pattern.focus( geometry, target, wavelength, pattern.FocusOption(), patterns, ) builder = client.datagram_builder() builder.push(Pattern(patterns)) for frame in builder.build(): await client.send_checked(frame)var patterns = geometry.PatternBuffer();foreach (var target in targets){ Pattern.Focus( geometry, target, wavelength, new FocusOption(), patterns ); var builder = client.DatagramBuilder(); builder.Push(new Pattern(patterns)); foreach (var frame in builder.Build()) { await client.SendCheckedAsync(frame); }}streaming
Section titled “streaming”A method that sends one frame after another without waiting for responses, and collects the responses together later.
send returns a ResponseFuture at the point the frame is enqueued, and awaiting that ResponseFuture yields the device’s response (Response).
Response can be validated for device errors with check.
The maximum size of the send queue is MAX_INFLIGHT (127).
ResponseFutures are held in FIFO order, and when the queue is full the oldest response is collected.
Because it does not wait for a response on every frame, throughput is high.
let mut patterns = geometry.pattern_buffer();let mut pending: VecDeque<ResponseFuture> = VecDeque::with_capacity(MAX_INFLIGHT);for &target in targets { autd3_rs_pattern::focus( geometry, target, wavelength, &autd3_rs_pattern::FocusOption::default(), &mut patterns, ); let mut builder = client.datagram_builder(); builder.push(Pattern::new(&patterns)); for frame in &builder.build()? { if pending.len() >= MAX_INFLIGHT { pending.pop_front().expect("non-empty").await?.check()?; } pending.push_back(client.send(frame).await?); }}// Drain the remaining responses.while let Some(fut) = pending.pop_front() { fut.await?.check()?;}patterns = geometry.pattern_buffer()pending = collections.deque()for target in targets: pattern.focus( geometry, target, wavelength, pattern.FocusOption(), patterns, ) builder = client.datagram_builder() builder.push(Pattern(patterns)) for frame in builder.build(): if len(pending) >= MAX_INFLIGHT: (await pending.popleft()).check() pending.append(await client.send(frame))# Drain the remaining responses.while pending: (await pending.popleft()).check()var patterns = geometry.PatternBuffer();var pending = new Queue<ResponseToken>();foreach (var target in targets){ Pattern.Focus( geometry, target, wavelength, new FocusOption(), patterns ); var builder = client.DatagramBuilder(); builder.Push(new Pattern(patterns)); foreach (var frame in builder.Build()) { if (pending.Count >= Client.MaxInflight) { (await pending.Dequeue()).Check(); } pending.Enqueue(await client.SendAsync(frame)); }}// Drain the remaining responses.while (pending.Count > 0){ (await pending.Dequeue()).Check();}For commands that must be applied reliably one at a time, such as initialization and configuration, use stop-and-wait.
For situations where throughput matters, such as updating the focal point at high frequency, use streaming.
For details on send / send_checked, refer to Client.