Perf: allocation-free name sort, cap MIDI note buffer at the producer
sort_contents compared str::to_lowercase() allocations (~1.7M allocs per sort at
100k rows); compare lowercased char iterators instead. The MIDI note-on buffer
was capped only on GUI drain, so a stalled GUI under a dense stream could grow it
unbounded; cap at the producer too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 files changed,
+19 insertions,
-2 deletions
| 68 |
68 |
|
0x90 if velocity > 0 => {
|
| 69 |
69 |
|
// Note On
|
| 70 |
70 |
|
shared_cb.instrument.lock().note_on(note, velocity);
|
| 71 |
|
- |
shared_cb.midi_recent_notes.lock().push(MidiNoteEvent {
|
|
71 |
+ |
let mut recent = shared_cb.midi_recent_notes.lock();
|
|
72 |
+ |
recent.push(MidiNoteEvent {
|
| 72 |
73 |
|
note,
|
| 73 |
74 |
|
velocity,
|
| 74 |
75 |
|
note_name: note_name(note),
|
| 75 |
76 |
|
timestamp: Instant::now(),
|
| 76 |
77 |
|
});
|
|
78 |
+ |
// Cap at the producer so a stalled GUI (minimized/occluded,
|
|
79 |
+ |
// not draining) can't grow this unbounded under a dense MIDI
|
|
80 |
+ |
// stream. The GUI keeps only the last 8 on drain.
|
|
81 |
+ |
let len = recent.len();
|
|
82 |
+ |
if len > 64 {
|
|
83 |
+ |
recent.drain(..len - 64);
|
|
84 |
+ |
}
|
| 77 |
85 |
|
}
|
| 78 |
86 |
|
0x80 | 0x90 => {
|
| 79 |
87 |
|
// Note Off (0x80 or 0x90 with velocity 0)
|
| 2 |
2 |
|
|
| 3 |
3 |
|
use super::*;
|
| 4 |
4 |
|
|
|
5 |
+ |
/// Case-insensitive string comparison without allocating. `str::to_lowercase`
|
|
6 |
+ |
/// allocates a `String` per call, which at 100k rows means ~1.7M allocations per
|
|
7 |
+ |
/// sort; comparing lowercased char iterators is allocation-free.
|
|
8 |
+ |
fn icase_cmp(a: &str, b: &str) -> std::cmp::Ordering {
|
|
9 |
+ |
a.chars()
|
|
10 |
+ |
.flat_map(char::to_lowercase)
|
|
11 |
+ |
.cmp(b.chars().flat_map(char::to_lowercase))
|
|
12 |
+ |
}
|
|
13 |
+ |
|
| 5 |
14 |
|
impl BrowserState {
|
| 6 |
15 |
|
/// Database ID of the currently active VFS, or `None` if the list is empty.
|
| 7 |
16 |
|
pub fn current_vfs_id(&self) -> Option<VfsId> {
|
| 104 |
113 |
|
}
|
| 105 |
114 |
|
|
| 106 |
115 |
|
let cmp = match self.sort_column {
|
| 107 |
|
- |
SortColumn::Name => a.node.name.to_lowercase().cmp(&b.node.name.to_lowercase()),
|
|
116 |
+ |
SortColumn::Name => icase_cmp(&a.node.name, &b.node.name),
|
| 108 |
117 |
|
SortColumn::Bpm => a.bpm.partial_cmp(&b.bpm).unwrap_or(std::cmp::Ordering::Equal),
|
| 109 |
118 |
|
SortColumn::Key => a.musical_key.cmp(&b.musical_key),
|
| 110 |
119 |
|
SortColumn::Duration => a
|