perf(audio,hid): 减少 PCM 复制和鼠标热路径分配与轮询

This commit is contained in:
mofeng-git
2026-10-01 22:55:57 +08:00
parent 3b8a4a6f34
commit 9a1f4ff4b5
7 changed files with 270 additions and 47 deletions

View File

@@ -1291,7 +1291,7 @@ impl HidBackend for Ch9329Backend {
let mut state = self.macos_drag_state.lock();
let (buttons, reports) = state.plan(event, buttons, *self.screen_resolution.read());
self.mouse_buttons.store(buttons, Ordering::Relaxed);
for report in reports {
for report in reports.iter().copied() {
match report {
MouseReport::Absolute { buttons, x, y } => {
let x = (u32::from(x) * CH9329_MOUSE_RESOLUTION / 32768) as u16;

View File

@@ -3,7 +3,7 @@
use super::{MouseEvent, MouseEventType};
#[derive(Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) enum MouseReport {
Absolute {
buttons: u8,
@@ -25,6 +25,7 @@ pub(super) struct MacosDrag {
extent: (u32, u32),
relative_input: bool,
absolute_drag: bool,
reports: Vec<MouseReport>,
}
impl MacosDrag {
@@ -37,8 +38,13 @@ impl MacosDrag {
event: MouseEvent,
buttons: u8,
extent: (u32, u32),
) -> (u8, Vec<MouseReport>) {
let mut reports = Vec::new();
) -> (u8, &[MouseReport]) {
if self.reports.capacity() > 128 {
self.reports = Vec::new();
} else {
self.reports.clear();
}
let reports = &mut self.reports;
let mut next_buttons = buttons;
if extent != self.extent || buttons == 0 {
self.remainder = (0, 0);
@@ -56,7 +62,7 @@ impl MacosDrag {
Self::delta(position.0, self.position.0, extent.0, &mut self.remainder.0);
let dy =
Self::delta(position.1, self.position.1, extent.1, &mut self.remainder.1);
Self::motion(&mut reports, buttons, dx, dy);
Self::motion(reports, buttons, dx, dy);
} else {
reports.push(MouseReport::Absolute {
buttons: 0,
@@ -69,7 +75,7 @@ impl MacosDrag {
MouseEventType::Move => {
self.relative_input = true;
Self::motion(
&mut reports,
reports,
buttons,
i64::from(event.x.clamp(-127, 127)),
i64::from(event.y.clamp(-127, 127)),
@@ -127,7 +133,7 @@ impl MacosDrag {
});
}
}
(next_buttons, reports)
(next_buttons, reports.as_slice())
}
fn delta(current: u16, previous: u16, extent: u32, remainder: &mut i64) -> i64 {
@@ -160,13 +166,39 @@ mod tests {
use super::*;
use crate::hid::MouseButton;
#[test]
fn repeated_motion_reuses_report_storage() {
let mut state = MacosDrag::default();
let (_, reports) = state.plan(MouseEvent::move_rel(1, 1), 0, (1920, 1080));
let allocation = reports.as_ptr();
for _ in 0..1000 {
let (_, reports) = state.plan(MouseEvent::move_rel(1, 1), 0, (1920, 1080));
assert_eq!(reports.as_ptr(), allocation);
assert_eq!(reports.len(), 1);
}
}
#[test]
fn oversized_report_storage_is_not_retained_for_small_events() {
let mut state = MacosDrag::default();
state.reports = Vec::with_capacity(256);
assert!(state.reports.capacity() > 128);
state.plan(MouseEvent::move_rel(1, 1), 0, (1920, 1080));
assert!(state.reports.capacity() <= 128);
}
fn drag(steps: &[i32], extent: (u32, u32)) -> (i64, i64) {
let mut state = MacosDrag::default();
state.plan(MouseEvent::move_abs(8000, 8000), 0, extent);
state.plan(MouseEvent::button_down(MouseButton::Left), 0, extent);
let mut total = (0, 0);
for &x in steps {
for report in state.plan(MouseEvent::move_abs(x, x), 1, extent).1 {
for report in state
.plan(MouseEvent::move_abs(x, x), 1, extent)
.1
.iter()
.copied()
{
if let MouseReport::Relative { dx, dy, .. } = report {
total.0 += i64::from(dx);
total.1 += i64::from(dy);

View File

@@ -871,7 +871,7 @@ impl HidBackend for OtgBackend {
let extent = self.screen_resolution.read().unwrap_or((1920, 1080));
let (buttons, reports) = state.plan(event, buttons, extent);
self.mouse_buttons.store(buttons, Ordering::Relaxed);
for report in reports {
for report in reports.iter().copied() {
match report {
MouseReport::Absolute { buttons, x, y } => {
self.send_mouse_report_absolute(buttons, x, y, 0)?

View File

@@ -4,6 +4,8 @@ use std::fs::File;
use std::io::Write;
#[cfg(unix)]
use std::os::unix::io::AsFd;
#[cfg(unix)]
use std::time::{Duration, Instant};
#[cfg(unix)]
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
@@ -20,27 +22,142 @@ impl OtgDeviceIo {
data: &[u8],
timeout_ms: i32,
) -> std::io::Result<bool> {
let mut pollfd = [PollFd::new(file.as_fd(), PollFlags::POLLOUT)];
match poll(&mut pollfd, PollTimeout::from(timeout_ms as u16)) {
Ok(1) => {
if let Some(revents) = pollfd[0].revents() {
if revents.contains(PollFlags::POLLERR) || revents.contains(PollFlags::POLLHUP)
{
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"Device error or hangup",
));
write_with_wait(file, data, timeout_ms as u16, |file, timeout| {
let mut pollfd = [PollFd::new(file.as_fd(), PollFlags::POLLOUT)];
match poll(&mut pollfd, PollTimeout::from(timeout)) {
Ok(1) => {
if let Some(revents) = pollfd[0].revents() {
if revents.contains(PollFlags::POLLERR)
|| revents.contains(PollFlags::POLLHUP)
{
return Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"Device error or hangup",
));
}
}
Ok(true)
}
file.write_all(data)?;
Ok(true)
Ok(0) => {
trace!("HID write timeout, dropping data");
Ok(false)
}
Ok(_) => Ok(false),
Err(e) => Err(std::io::Error::other(e)),
}
Ok(0) => {
trace!("HID write timeout, dropping data");
Ok(false)
}
Ok(_) => Ok(false),
Err(e) => Err(std::io::Error::other(e)),
}
})
}
}
#[cfg(unix)]
fn write_with_wait<Writer: Write>(
writer: &mut Writer,
mut data: &[u8],
timeout_ms: u16,
mut wait: impl FnMut(&Writer, u16) -> std::io::Result<bool>,
) -> std::io::Result<bool> {
let mut deadline = None;
while !data.is_empty() {
match writer.write(data) {
Ok(0) => return Err(std::io::ErrorKind::WriteZero.into()),
Ok(written) => data = &data[written..],
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => continue,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
let deadline = deadline.get_or_insert_with(|| {
Instant::now() + Duration::from_millis(u64::from(timeout_ms))
});
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero()
|| !wait(
writer,
remaining
.as_nanos()
.div_ceil(1_000_000)
.min(u16::MAX as u128) as u16,
)?
{
return Ok(false);
}
}
Err(error) => return Err(error),
}
}
Ok(true)
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
use std::collections::VecDeque;
use std::io::{ErrorKind, Result};
struct ScriptedWriter {
steps: VecDeque<Result<usize>>,
data: Vec<u8>,
}
impl Write for ScriptedWriter {
fn write(&mut self, input: &[u8]) -> Result<usize> {
let count = self.steps.pop_front().unwrap_or(Ok(input.len()))?;
self.data.extend_from_slice(&input[..count]);
Ok(count)
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
#[test]
fn ready_device_skips_poll_and_preserves_report_bytes() {
let mut writer = ScriptedWriter {
steps: VecDeque::new(),
data: Vec::new(),
};
assert!(
write_with_wait(&mut writer, &[1, 2, 3, 4], 100, |_, _| panic!(
"unexpected poll"
))
.unwrap()
);
assert_eq!(writer.data, [1, 2, 3, 4]);
}
#[test]
fn partial_and_interrupted_writes_do_not_duplicate_bytes() {
let mut writer = ScriptedWriter {
steps: [
Ok(2),
Err(ErrorKind::Interrupted.into()),
Err(ErrorKind::WouldBlock.into()),
]
.into(),
data: Vec::new(),
};
let mut waits = 0;
assert!(write_with_wait(&mut writer, &[1, 2, 3, 4], 100, |_, _| {
waits += 1;
Ok(true)
})
.unwrap());
assert_eq!(waits, 1);
assert_eq!(writer.data, [1, 2, 3, 4]);
}
#[test]
fn blocked_device_retains_bounded_timeout_and_error_handling() {
let mut writer = ScriptedWriter {
steps: [Err(ErrorKind::WouldBlock.into())].into(),
data: Vec::new(),
};
assert!(!write_with_wait(&mut writer, &[1], 100, |_, _| Ok(false)).unwrap());
assert!(writer.data.is_empty());
writer.steps.push_back(Err(ErrorKind::BrokenPipe.into()));
assert_eq!(
write_with_wait(&mut writer, &[1], 100, |_, _| Ok(true))
.unwrap_err()
.kind(),
ErrorKind::BrokenPipe
);
}
}