micro optimization fixes
This commit is contained in:
1 parent
0d7203f84b
commit
da60ecb719
36 files changed
+1781
-521
No files matched your search
@@ -234,6 +234,10 @@ impl ClientGeometry {
|
||||
matches_target
|
||||
}
|
||||
|
||||
pub fn waiting_for_commit(&self) -> bool {
|
||||
self.waiting_since.is_some()
|
||||
}
|
||||
|
||||
pub fn timed_out(&self, now: Duration) -> bool {
|
||||
self.waiting_since
|
||||
.is_some_and(|started| now.saturating_sub(started) >= CLIENT_COMMIT_TIMEOUT)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ferese_layout::WindowId;
|
||||
|
||||
/// Most recent focus first. Layer surfaces and temporary selections do not enter this list.
|
||||
@@ -19,19 +21,19 @@ impl FocusHistory {
|
||||
self.windows.retain(|id| *id != window);
|
||||
}
|
||||
|
||||
pub fn candidates(&self, current: Option<WindowId>, mut available: Vec<WindowId>) -> Vec<WindowId> {
|
||||
available.sort_unstable_by_key(|id| id.0);
|
||||
available.dedup();
|
||||
let mut ordered = Vec::with_capacity(available.len());
|
||||
for id in current
|
||||
.into_iter()
|
||||
.chain(self.windows.iter().copied())
|
||||
.chain(available.iter().copied())
|
||||
{
|
||||
if available.contains(&id) && !ordered.contains(&id) {
|
||||
pub fn candidates(&self, current: Option<WindowId>, available: impl AsRef<[WindowId]>) -> Vec<WindowId> {
|
||||
let mut remaining = available.as_ref().iter().copied().collect::<HashSet<_>>();
|
||||
let mut ordered = Vec::with_capacity(remaining.len());
|
||||
|
||||
for id in current.into_iter().chain(self.windows.iter().copied()) {
|
||||
if remaining.remove(&id) {
|
||||
ordered.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
let mut tail = remaining.into_iter().collect::<Vec<_>>();
|
||||
tail.sort_unstable_by_key(|id| id.0);
|
||||
ordered.extend(tail);
|
||||
ordered
|
||||
}
|
||||
}
|
||||
@@ -62,6 +64,8 @@ impl FocusCycle {
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, reverse: bool, available: &[WindowId]) -> Option<WindowId> {
|
||||
let available = available.iter().copied().collect::<HashSet<_>>();
|
||||
|
||||
// Keep the old index when the selected window disappears so forward
|
||||
// traversal selects its successor rather than skipping over it.
|
||||
let old = self
|
||||
@@ -91,6 +95,41 @@ impl FocusCycle {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn indexed_candidates_preserve_linear_order_with_duplicates_and_missing_windows() {
|
||||
let mut history = FocusHistory::default();
|
||||
|
||||
for id in [7, 2, 5, 9, 2] {
|
||||
history.record(WindowId(id));
|
||||
}
|
||||
|
||||
for mask in 0..1024 {
|
||||
let available = (0..10)
|
||||
.filter(|id| mask & (1 << id) != 0)
|
||||
.flat_map(|id| [WindowId(id), WindowId(id)])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for current in [None, Some(WindowId(2)), Some(WindowId(99))] {
|
||||
let mut sorted = available.clone();
|
||||
sorted.sort_unstable_by_key(|id| id.0);
|
||||
sorted.dedup();
|
||||
let mut expected = Vec::new();
|
||||
|
||||
for id in current
|
||||
.into_iter()
|
||||
.chain(history.windows.iter().copied())
|
||||
.chain(sorted.iter().copied())
|
||||
{
|
||||
if sorted.contains(&id) && !expected.contains(&id) {
|
||||
expected.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(history.candidates(current, &available), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn history_is_unique_filters_unavailable_and_last_focus_toggles() {
|
||||
let mut history = FocusHistory::default();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
@@ -60,7 +60,7 @@ struct OutputState {
|
||||
geometry: OutputGeometry,
|
||||
active: WorkspaceId,
|
||||
previous: Option<WorkspaceId>,
|
||||
workspaces: HashSet<WorkspaceId>,
|
||||
workspaces: BTreeSet<WorkspaceId>,
|
||||
}
|
||||
|
||||
impl OutputState {
|
||||
@@ -87,7 +87,7 @@ struct EvacuationRecord {
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct OutputWorkspaceMap {
|
||||
outputs: HashMap<OutputId, OutputState>,
|
||||
outputs: BTreeMap<OutputId, OutputState>,
|
||||
assignments: HashMap<WorkspaceId, OutputId>,
|
||||
revisions: HashMap<WorkspaceId, u64>,
|
||||
evacuations: HashMap<OutputId, EvacuationRecord>,
|
||||
@@ -95,6 +95,13 @@ pub struct OutputWorkspaceMap {
|
||||
}
|
||||
|
||||
impl OutputWorkspaceMap {
|
||||
pub fn assigned_workspaces(&self, output: OutputId) -> impl DoubleEndedIterator<Item = WorkspaceId> + '_ {
|
||||
self.outputs
|
||||
.get(&output)
|
||||
.into_iter()
|
||||
.flat_map(|state| state.workspaces.iter().copied())
|
||||
}
|
||||
|
||||
pub fn focused_output(&self) -> Option<OutputId> {
|
||||
self.focused
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use ferese_layout::{
|
||||
SizeConstraints, ViewportFocusStrategy, WindowId,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct WorkspaceId(pub u64);
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -78,11 +78,17 @@ impl WorkspaceLayout {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_ids(&self) -> Box<dyn Iterator<Item = WindowId> + '_> {
|
||||
match self {
|
||||
Self::Scrolling(layout) => Box::new(layout.window_ids()),
|
||||
Self::Tree(layout) => Box::new(layout.window_ids()),
|
||||
}
|
||||
pub fn window_ids(&self) -> impl Iterator<Item = WindowId> + '_ {
|
||||
let scrolling = match self {
|
||||
Self::Scrolling(layout) => Some(layout.window_ids()),
|
||||
Self::Tree(_) => None,
|
||||
};
|
||||
let tree = match self {
|
||||
Self::Tree(layout) => Some(layout.window_ids()),
|
||||
Self::Scrolling(_) => None,
|
||||
};
|
||||
|
||||
scrolling.into_iter().flatten().chain(tree.into_iter().flatten())
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
|
||||
@@ -15,46 +15,58 @@ pub struct WorkspaceView {
|
||||
|
||||
impl OutputWorkspaceMap {
|
||||
pub fn workspace_views(&self, workspaces: &WorkspaceSet) -> Vec<WorkspaceView> {
|
||||
let mut outputs = self.connected_outputs().collect::<Vec<_>>();
|
||||
outputs.sort_unstable();
|
||||
self.workspace_views_iter(workspaces).collect()
|
||||
}
|
||||
|
||||
outputs
|
||||
.into_iter()
|
||||
.flat_map(|output| {
|
||||
self.ordered_workspaces(workspaces, output)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(index, id)| {
|
||||
let visible = self.active_workspace(output) == Some(id);
|
||||
WorkspaceView {
|
||||
id,
|
||||
output,
|
||||
index: index as u32 + 1,
|
||||
window_count: workspaces.workspace(id).unwrap().window_count(),
|
||||
visible,
|
||||
focused: visible && self.focused_output() == Some(output),
|
||||
}
|
||||
})
|
||||
pub fn workspace_views_iter<'a>(
|
||||
&'a self,
|
||||
workspaces: &'a WorkspaceSet,
|
||||
) -> impl Iterator<Item = WorkspaceView> + 'a {
|
||||
self.connected_outputs()
|
||||
.flat_map(move |output| self.workspace_views_for_output(workspaces, output))
|
||||
}
|
||||
|
||||
pub fn workspace_views_for_output<'a>(
|
||||
&'a self,
|
||||
workspaces: &'a WorkspaceSet,
|
||||
output: OutputId,
|
||||
) -> impl Iterator<Item = WorkspaceView> + 'a {
|
||||
self.ordered_workspace_ids(workspaces, output)
|
||||
.enumerate()
|
||||
.map(move |(index, id)| {
|
||||
let visible = self.active_workspace(output) == Some(id);
|
||||
|
||||
WorkspaceView {
|
||||
id,
|
||||
output,
|
||||
index: index as u32 + 1,
|
||||
window_count: workspaces.workspace(id).unwrap().window_count(),
|
||||
visible,
|
||||
focused: visible && self.focused_output() == Some(output),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn ordered_workspace_ids<'a>(
|
||||
&'a self,
|
||||
workspaces: &'a WorkspaceSet,
|
||||
output: OutputId,
|
||||
) -> impl DoubleEndedIterator<Item = WorkspaceId> + 'a {
|
||||
self.assigned_workspaces(output)
|
||||
.filter(|id| workspaces.workspace(*id).is_some())
|
||||
}
|
||||
|
||||
pub fn ordered_workspaces(&self, workspaces: &WorkspaceSet, output: OutputId) -> Vec<WorkspaceId> {
|
||||
let mut ids = workspaces
|
||||
.iter()
|
||||
.filter(|workspace| self.output_for_workspace(workspace.id) == Some(output))
|
||||
.map(|workspace| workspace.id)
|
||||
.collect::<Vec<_>>();
|
||||
ids.sort_unstable_by_key(|id| id.0);
|
||||
ids
|
||||
self.ordered_workspace_ids(workspaces, output).collect()
|
||||
}
|
||||
|
||||
/// Out-of-range numeric selection resolves to the trailing spare. Looking
|
||||
/// up a gesture target never creates workspaces or changes history.
|
||||
pub fn workspace_at(&self, workspaces: &WorkspaceSet, output: OutputId, index: u32) -> Option<WorkspaceId> {
|
||||
let index = index.checked_sub(1)? as usize;
|
||||
let ids = self.ordered_workspaces(workspaces, output);
|
||||
ids.get(index).or_else(|| ids.last()).copied()
|
||||
self.ordered_workspace_ids(workspaces, output)
|
||||
.nth(index)
|
||||
.or_else(|| self.ordered_workspace_ids(workspaces, output).next_back())
|
||||
}
|
||||
|
||||
/// Reconcile after membership, selection, output, or transition changes.
|
||||
@@ -68,17 +80,15 @@ impl OutputWorkspaceMap {
|
||||
workspaces.activate(active).expect("output's active workspace exists");
|
||||
}
|
||||
|
||||
let mut protected = transitions.clone();
|
||||
let mut protected = HashSet::new();
|
||||
let outputs = self.connected_outputs().collect::<Vec<_>>();
|
||||
for output in outputs {
|
||||
let ordered = self.ordered_workspaces(workspaces, output);
|
||||
let last = self.ordered_workspace_ids(workspaces, output).next_back();
|
||||
if let Some(active) = self.active_workspace(output) {
|
||||
protected.insert(active);
|
||||
}
|
||||
|
||||
let spare = ordered
|
||||
.last()
|
||||
.copied()
|
||||
let spare = last
|
||||
.filter(|id| workspaces.workspace(*id).is_some_and(|w| w.is_empty()))
|
||||
.unwrap_or_else(|| {
|
||||
let id = workspaces.create_workspace();
|
||||
@@ -91,7 +101,10 @@ impl OutputWorkspaceMap {
|
||||
let removed = workspaces
|
||||
.iter()
|
||||
.filter(|workspace| {
|
||||
workspace.is_empty() && !protected.contains(&workspace.id) && workspace.id != workspaces.active_id()
|
||||
workspace.is_empty()
|
||||
&& !protected.contains(&workspace.id)
|
||||
&& !transitions.contains(&workspace.id)
|
||||
&& workspace.id != workspaces.active_id()
|
||||
})
|
||||
.map(|workspace| workspace.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Reference in new issue
Block a user