perf(wm): clone-free locked deque insert/remove

This commit replaces my initial clumsy LockedDeque insert/remove
implementations with Clone-free implementations by @alex-ds13.
This commit is contained in:
LGUG2Z
2025-03-07 22:08:12 -08:00
parent a95e6e9644
commit 072a62c314
3 changed files with 95 additions and 146 deletions

View File

@@ -501,10 +501,10 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|| focused_window_hwnd != foreground_window
{
if ws.locked_containers().contains(&idx) {
WindowKind::UnfocusedLocked
} else {
WindowKind::Unfocused
}
WindowKind::UnfocusedLocked
} else {
WindowKind::Unfocused
}
} else if c.windows().len() > 1 {
WindowKind::Stack
} else {

View File

@@ -1,13 +1,13 @@
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::collections::VecDeque;
pub struct LockedDeque<'a, T> {
deque: &'a mut VecDeque<T>,
locked_indices: &'a mut HashSet<usize>,
locked_indices: &'a mut BTreeSet<usize>,
}
impl<'a, T: Clone + PartialEq> LockedDeque<'a, T> {
pub fn new(deque: &'a mut VecDeque<T>, locked_indices: &'a mut HashSet<usize>) -> Self {
impl<'a, T: PartialEq> LockedDeque<'a, T> {
pub fn new(deque: &'a mut VecDeque<T>, locked_indices: &'a mut BTreeSet<usize>) -> Self {
Self {
deque,
locked_indices,
@@ -23,156 +23,105 @@ impl<'a, T: Clone + PartialEq> LockedDeque<'a, T> {
}
}
fn insert_respecting_locks<T: Clone>(
pub fn insert_respecting_locks<T>(
deque: &mut VecDeque<T>,
locked_indices: &mut HashSet<usize>,
index: usize,
locked_idx: &mut BTreeSet<usize>,
idx: usize,
value: T,
) -> usize {
if deque.is_empty() {
if idx == deque.len() {
deque.push_back(value);
return 0;
return idx;
}
// Find the actual insertion point (first unlocked index >= requested index)
let mut actual_index = index;
while actual_index < deque.len() && locked_indices.contains(&actual_index) {
actual_index += 1;
}
// If we're inserting at the end, just push_back
if actual_index >= deque.len() {
deque.push_back(value);
return actual_index;
}
// Store original values at locked positions
let locked_values: Vec<(usize, T)> = locked_indices
.iter()
.filter_map(|&idx| {
if idx < deque.len() {
Some((idx, deque[idx].clone()))
} else {
None
}
})
.collect();
// Store all original values
let original_values: Vec<T> = deque.iter().cloned().collect();
// Create a new deque with the correct final size
let mut new_deque = VecDeque::with_capacity(deque.len() + 1);
for _ in 0..deque.len() + 1 {
new_deque.push_back(value.clone()); // Temporary placeholder
}
let mut temp_locked_deque = VecDeque::new();
let mut j = 0;
let mut corrected_idx = idx;
// First, place the new value at the insertion point
new_deque[actual_index] = value.clone();
// Then, place all locked values at their original positions
for (idx, val) in &locked_values {
new_deque[*idx] = val.clone();
}
// Now, fill in all remaining positions with values from the original deque,
// accounting for the shift caused by insertion
let mut orig_idx = 0;
#[allow(clippy::needless_range_loop)]
for new_idx in 0..new_deque.len() {
// Skip positions that are already filled (insertion point and locked positions)
if new_idx == actual_index || locked_indices.contains(&new_idx) {
continue;
for (i, el) in deque.drain(..).enumerate() {
if i == idx {
corrected_idx = j;
}
// Skip original elements that were at locked positions
while orig_idx < original_values.len() && locked_indices.contains(&orig_idx) {
orig_idx += 1;
}
// If we still have original elements to place
if orig_idx < original_values.len() {
new_deque[new_idx] = original_values[orig_idx].clone();
orig_idx += 1;
if locked_idx.contains(&i) {
temp_locked_deque.push_back(el);
} else {
new_deque.push_back(el);
j += 1;
}
}
new_deque.insert(corrected_idx, value);
for (locked_el, locked_idx) in temp_locked_deque.into_iter().zip(locked_idx.iter()) {
new_deque.insert(*locked_idx, locked_el);
if *locked_idx <= corrected_idx {
corrected_idx += 1;
}
}
// Update the original deque
*deque = new_deque;
actual_index
corrected_idx
}
fn remove_respecting_locks<T: Clone>(
pub fn remove_respecting_locks<T>(
deque: &mut VecDeque<T>,
locked_indices: &mut HashSet<usize>,
index: usize,
locked_idx: &mut BTreeSet<usize>,
idx: usize,
) -> Option<T> {
if index >= deque.len() {
if idx >= deque.len() {
return None;
}
let removed = deque[index].clone();
let final_size = deque.len() - 1;
// If removing a locked index, just remove it and unlock
if locked_indices.contains(&index) {
locked_indices.remove(&index);
deque.remove(index);
let mut new_deque = VecDeque::with_capacity(final_size);
let mut temp_locked_deque = VecDeque::new();
let mut removed = None;
let mut removed_locked_idx = None;
// Update locked indices after the removal point
let new_locked: HashSet<usize> = locked_indices
.iter()
.map(|&idx| if idx > index { idx - 1 } else { idx })
.collect();
*locked_indices = new_locked;
return Some(removed);
}
// Let's build a new deque with the correct order
let mut result = VecDeque::with_capacity(deque.len() - 1);
// 1. First include all elements before the removal index
#[allow(clippy::needless_range_loop)]
for i in 0..index {
result.push_back(deque[i].clone());
}
// 2. Then for each element after the removal index
#[allow(clippy::needless_range_loop)]
for i in (index + 1)..deque.len() {
// If the previous index was locked, we need to swap this element
// with the previous one in our result
if locked_indices.contains(&(i - 1)) {
// Insert this element before the locked element
if !result.is_empty() {
let locked_element = result.pop_back().unwrap();
result.push_back(deque[i].clone());
result.push_back(locked_element);
} else {
// This shouldn't happen with valid inputs
result.push_back(deque[i].clone());
}
for (i, el) in deque.drain(..).enumerate() {
if i == idx {
removed = Some(el);
removed_locked_idx = locked_idx.contains(&i).then_some(i);
} else if locked_idx.contains(&i) {
temp_locked_deque.push_back(el);
} else {
// Normal case, just add the element
result.push_back(deque[i].clone());
new_deque.push_back(el);
}
}
// Update the original deque
*deque = result;
if let Some(i) = removed_locked_idx {
let mut above = locked_idx.split_off(&i);
above.pop_first();
locked_idx.extend(above.into_iter().map(|i| i - 1));
}
// Important: Keep the same locked indices (don't update them)
// Only remove any that are now out of bounds
locked_indices.retain(|&idx| idx < deque.len());
while locked_idx.last().is_some_and(|i| *i >= final_size) {
locked_idx.pop_last();
}
Some(removed)
let extra_invalid_idx = (new_deque.len()
..(new_deque.len() + temp_locked_deque.len() - locked_idx.len()))
.collect::<Vec<_>>();
for (locked_el, locked_idx) in temp_locked_deque
.into_iter()
.zip(locked_idx.iter().chain(extra_invalid_idx.iter()))
{
new_deque.insert(*locked_idx, locked_el);
}
*deque = new_deque;
removed
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::collections::VecDeque;
#[test]
@@ -180,7 +129,7 @@ mod tests {
// Test case 1: Basic insertion with locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
// Insert at index 0, should shift elements while keeping index 2 locked
@@ -192,7 +141,7 @@ mod tests {
// Test case 2: Insert at a locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
// Try to insert at locked index 2, should insert at index 3 instead
@@ -204,7 +153,7 @@ mod tests {
// Test case 3: Multiple locked indices
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(1); // Lock index 1
locked.insert(3); // Lock index 3
@@ -217,7 +166,7 @@ mod tests {
// Test case 4: Insert at end
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
// Insert at end of deque
@@ -229,7 +178,7 @@ mod tests {
// Test case 5: Empty deque
{
let mut deque = VecDeque::new();
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
// Insert into empty deque
let actual_index = insert_respecting_locks(&mut deque, &mut locked, 0, 99);
@@ -240,7 +189,7 @@ mod tests {
// Test case 6: All indices locked
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
for i in 0..5 {
locked.insert(i); // Lock all indices
}
@@ -254,7 +203,7 @@ mod tests {
// Test case 7: Consecutive locked indices
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
locked.insert(3); // Lock index 3
@@ -270,7 +219,7 @@ mod tests {
// Test case 1: Remove a non-locked index before a locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
let removed = remove_respecting_locks(&mut deque, &mut locked, 0);
@@ -282,7 +231,7 @@ mod tests {
// Test case 2: Remove a locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
let removed = remove_respecting_locks(&mut deque, &mut locked, 2);
@@ -294,7 +243,7 @@ mod tests {
// Test case 3: Remove an index after a locked index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(1); // Lock index 1
let removed = remove_respecting_locks(&mut deque, &mut locked, 3);
@@ -306,7 +255,7 @@ mod tests {
// Test case 4: Multiple locked indices
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(1); // Lock index 1
locked.insert(3); // Lock index 3
@@ -319,7 +268,7 @@ mod tests {
// Test case 5: Remove the last element
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
let removed = remove_respecting_locks(&mut deque, &mut locked, 4);
@@ -331,7 +280,7 @@ mod tests {
// Test case 6: Invalid index
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
let removed = remove_respecting_locks(&mut deque, &mut locked, 10);
@@ -343,7 +292,7 @@ mod tests {
// Test case 7: Remove enough elements to make a locked index invalid
{
let mut deque = VecDeque::from(vec![0, 1, 2]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
remove_respecting_locks(&mut deque, &mut locked, 0);
@@ -354,7 +303,7 @@ mod tests {
// Test case 8: Removing an element before multiple locked indices
{
let mut deque = VecDeque::from(vec![0, 1, 2, 3, 4, 5]);
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
locked.insert(2); // Lock index 2
locked.insert(4); // Lock index 4

View File

@@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::collections::VecDeque;
use std::fmt::Display;
use std::fmt::Formatter;
@@ -93,7 +93,7 @@ pub struct Workspace {
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub layer: WorkspaceLayer,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub locked_containers: HashSet<usize>,
pub locked_containers: BTreeSet<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[getset(get = "pub", set = "pub")]
pub workspace_config: Option<WorkspaceConfig>,
@@ -1655,15 +1655,15 @@ mod tests {
use crate::container::Container;
use crate::Window;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;
#[test]
fn test_locked_containers_with_new_window() {
let mut ws = Workspace::default();
let mut state = HashMap::new();
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
// add 3 containers
for i in 0..4 {
@@ -1708,7 +1708,7 @@ mod tests {
fn test_locked_containers_remove_window() {
let mut ws = Workspace::default();
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
// add 4 containers
for i in 0..4 {
@@ -1733,7 +1733,7 @@ mod tests {
fn test_locked_containers_toggle_float() {
let mut ws = Workspace::default();
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
// add 4 containers
for i in 0..4 {
@@ -1778,7 +1778,7 @@ mod tests {
fn test_locked_containers_stack() {
let mut ws = Workspace::default();
let mut locked = HashSet::new();
let mut locked = BTreeSet::new();
// add 6 containers
for i in 0..6 {