Skip to content

Commit b6427df

Browse files
authored
Few optimization and heap allocation elimination (#97)
* optimize controller refresh by removing unplugged adapters in a single pass * refactor Device struct to use static string for icon and update get_icon return type * refactor notification system to use StringRef for optimized message handling and add StringRef module * refactor padding functions to use preallocated buffer and Cow for optimized string handling in requests
1 parent 3e59df0 commit b6427df

File tree

9 files changed

+180
-108
lines changed

9 files changed

+180
-108
lines changed

src/app.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -737,30 +737,27 @@ impl App {
737737
pub async fn refresh(&mut self) -> AppResult<()> {
738738
let refreshed_controllers = Controller::get_all(self.session.clone()).await?;
739739

740-
let names = {
741-
let mut names: Vec<String> = Vec::new();
740+
// Remove unplugged adapters in a single pass
741+
let mut adapter_removed = false;
742+
self.controllers.retain(|controller| {
743+
let should_retain = refreshed_controllers
744+
.iter()
745+
.any(|c| c.name == controller.name);
742746

743-
for controller in self.controllers.iter() {
744-
if !refreshed_controllers
745-
.iter()
746-
.any(|c| c.name == controller.name)
747-
{
748-
names.push(controller.name.clone());
749-
}
747+
if !should_retain {
748+
adapter_removed = true;
750749
}
751750

752-
names
753-
};
754-
755-
// Remove unplugged adapters
756-
for name in names {
757-
self.controllers.retain(|c| c.name != name);
751+
should_retain
752+
});
758753

754+
// Update selection after removal
755+
if adapter_removed {
759756
if !self.controllers.is_empty() {
760757
let i = match self.controller_state.selected() {
761758
Some(i) => {
762759
if i > 0 {
763-
i - 1
760+
(i - 1).min(self.controllers.len() - 1)
764761
} else {
765762
0
766763
}

src/bluetooth.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub struct Controller {
2323
pub struct Device {
2424
device: BTDevice,
2525
pub addr: Address,
26-
pub icon: String,
26+
pub icon: &'static str,
2727
pub alias: String,
2828
pub is_paired: bool,
2929
pub is_trusted: bool,
@@ -38,19 +38,19 @@ impl Device {
3838
}
3939

4040
// https://specifications.freedesktop.org/icon-naming/latest/
41-
pub fn get_icon(name: &str) -> String {
41+
pub fn get_icon(name: &str) -> &'static str {
4242
match name {
43-
"audio-card" => String::from("󰓃 "),
44-
"audio-input-microphone" => String::from(" "),
45-
"audio-headphones" | "audio-headset" => String::from("󰋋 "),
46-
"battery" => String::from("󰂀 "),
47-
"camera-photo" => String::from("󰻛 "),
48-
"computer" => String::from(" "),
49-
"input-keyboard" => String::from("󰌌 "),
50-
"input-mouse" => String::from("󰍽 "),
51-
"input-gaming" => String::from("󰊴 "),
52-
"phone" => String::from("󰏲 "),
53-
_ => String::from("󰾰 "),
43+
"audio-card" => "󰓃 ",
44+
"audio-input-microphone" => " ",
45+
"audio-headphones" | "audio-headset" => "󰋋 ",
46+
"battery" => "󰂀 ",
47+
"camera-photo" => "󰻛 ",
48+
"computer" => " ",
49+
"input-keyboard" => "󰌌 ",
50+
"input-mouse" => "󰍽 ",
51+
"input-gaming" => "󰊴 ",
52+
"phone" => "󰏲 ",
53+
_ => "󰾰 ",
5454
}
5555
}
5656
}

src/handler.rs

Lines changed: 45 additions & 57 deletions
Large diffs are not rendered by default.

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ pub mod notification;
1010
pub mod requests;
1111
pub mod rfkill;
1212
pub mod spinner;
13+
pub mod string_ref;
1314
pub mod tui;
1415
pub mod ui;

src/notification.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ use ratatui::{
77
};
88
use tokio::sync::mpsc::UnboundedSender;
99

10-
use crate::{app::AppResult, event::Event};
10+
use crate::{app::AppResult, event::Event, string_ref::StringRef};
1111

1212
#[derive(Debug, Clone)]
1313
pub struct Notification {
14-
pub message: String,
14+
pub message: StringRef,
1515
pub level: NotificationLevel,
1616
pub ttl: u16,
1717
}
@@ -57,7 +57,7 @@ impl Notification {
5757
frame.render_widget(block, area);
5858
}
5959
pub fn send(
60-
message: String,
60+
message: StringRef,
6161
level: NotificationLevel,
6262
sender: UnboundedSender<Event>,
6363
) -> AppResult<()> {

src/requests.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::{borrow::Cow, fmt::Write};
2+
13
use crate::requests::{
24
confirmation::Confirmation, display_passkey::DisplayPasskey, display_pin_code::DisplayPinCode,
35
enter_passkey::EnterPasskey, enter_pin_code::EnterPinCode,
@@ -36,11 +38,24 @@ impl Requests {
3638
}
3739
}
3840

39-
fn pad_string(input: &str, length: usize) -> String {
41+
fn pad_str<'a>(input: &'a str, length: usize) -> Cow<'a, str> {
42+
let current_length = input.chars().count();
43+
if current_length >= length {
44+
Cow::Borrowed(input)
45+
} else {
46+
let mut s = String::with_capacity(length);
47+
write!(&mut s, "{:<width$}", input, width = length).unwrap();
48+
Cow::Owned(s)
49+
}
50+
}
51+
52+
fn pad_string(input: String, length: usize) -> String {
4053
let current_length = input.chars().count();
4154
if current_length >= length {
42-
input.to_string()
55+
input
4356
} else {
44-
format!("{:<width$}", input, width = length)
57+
let mut s = String::with_capacity(length);
58+
write!(&mut s, "{:<width$}", input, width = length).unwrap();
59+
s
4560
}
4661
}

src/requests/enter_passkey.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ use ratatui::{
1111
use bluer::Address;
1212
use tui_input::{Input, backend::crossterm::EventHandler};
1313

14-
use crate::{agent::AuthAgent, app::AppResult, event::Event, requests::pad_string};
14+
use crate::{
15+
agent::AuthAgent,
16+
app::AppResult,
17+
event::Event,
18+
requests::{pad_str, pad_string},
19+
};
1520

1621
#[derive(Debug, Clone, PartialEq, Default)]
1722
pub enum FocusedSection {
@@ -177,15 +182,12 @@ impl EnterPasskey {
177182
}
178183
},
179184
Span::from(" "),
180-
Span::from(pad_string(
181-
format!(" {}", self.passkey.field.value()).as_str(),
182-
60,
183-
))
184-
.bg(Color::DarkGray),
185+
Span::from(pad_string(format!(" {}", self.passkey.field.value()), 60))
186+
.bg(Color::DarkGray),
185187
]),
186-
Line::from(vec![Span::from(pad_string(" ", 9)), {
188+
Line::from(vec![Span::from(pad_str(" ", 9)), {
187189
if let Some(error) = &self.passkey.error {
188-
Span::from(pad_string(error, 60))
190+
Span::from(pad_str(error, 60))
189191
} else {
190192
Span::from("")
191193
}

src/requests/enter_pin_code.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ use ratatui::{
1111
use bluer::Address;
1212
use tui_input::{Input, backend::crossterm::EventHandler};
1313

14-
use crate::{agent::AuthAgent, app::AppResult, event::Event, requests::pad_string};
14+
use crate::{
15+
agent::AuthAgent,
16+
app::AppResult,
17+
event::Event,
18+
requests::{pad_str, pad_string},
19+
};
1520

1621
#[derive(Debug, Clone, PartialEq, Default)]
1722
pub enum FocusedSection {
@@ -171,15 +176,12 @@ impl EnterPinCode {
171176
}
172177
},
173178
Span::from(" "),
174-
Span::from(pad_string(
175-
format!(" {}", self.pin_code.field.value()).as_str(),
176-
60,
177-
))
178-
.bg(Color::DarkGray),
179+
Span::from(pad_string(format!(" {}", self.pin_code.field.value()), 60))
180+
.bg(Color::DarkGray),
179181
]),
180-
Line::from(vec![Span::from(pad_string(" ", 10)), {
182+
Line::from(vec![Span::from(pad_str(" ", 10)), {
181183
if let Some(error) = &self.pin_code.error {
182-
Span::from(pad_string(error, 60))
184+
Span::from(pad_str(error, 60))
183185
} else {
184186
Span::from("")
185187
}

src/string_ref.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
pub enum StringRef {
2+
Owned(String),
3+
Static(&'static str),
4+
}
5+
6+
impl StringRef {
7+
pub fn as_str(&self) -> &str {
8+
match self {
9+
StringRef::Owned(s) => s.as_str(),
10+
StringRef::Static(s) => s,
11+
}
12+
}
13+
}
14+
15+
impl From<String> for StringRef {
16+
fn from(s: String) -> Self {
17+
StringRef::Owned(s)
18+
}
19+
}
20+
21+
impl From<&'static str> for StringRef {
22+
fn from(s: &'static str) -> Self {
23+
StringRef::Static(s)
24+
}
25+
}
26+
27+
impl From<bluer::Error> for StringRef {
28+
fn from(err: bluer::Error) -> Self {
29+
StringRef::Owned(err.to_string())
30+
}
31+
}
32+
33+
impl From<anyhow::Error> for StringRef {
34+
fn from(err: anyhow::Error) -> Self {
35+
StringRef::Owned(err.to_string())
36+
}
37+
}
38+
39+
impl AsRef<str> for StringRef {
40+
fn as_ref(&self) -> &str {
41+
self.as_str()
42+
}
43+
}
44+
45+
impl std::fmt::Display for StringRef {
46+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47+
write!(f, "{}", self.as_str())
48+
}
49+
}
50+
51+
impl std::fmt::Debug for StringRef {
52+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53+
match self {
54+
StringRef::Owned(s) => write!(f, "StringRef::Owned({:?})", s),
55+
StringRef::Static(s) => write!(f, "StringRef::Static({:?})", s),
56+
}
57+
}
58+
}
59+
60+
impl Clone for StringRef {
61+
fn clone(&self) -> Self {
62+
match self {
63+
StringRef::Owned(s) => StringRef::Owned(s.clone()),
64+
StringRef::Static(s) => StringRef::Static(s),
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)