use std::collections::HashMap;
use super::{TickDecision, TickStrategy};
#[derive(Debug, Clone)]
pub struct ActivePlusAdjacent {
adjacency: HashMap<String, Vec<String>>,
background_divisor: u64,
}
impl ActivePlusAdjacent {
#[must_use]
pub fn new(background_divisor: u64) -> Self {
Self {
adjacency: HashMap::new(),
background_divisor: background_divisor.max(1),
}
}
pub fn add_adjacency(&mut self, screen: &str, neighbors: &[&str]) {
self.adjacency
.entry(screen.to_owned())
.or_default()
.extend(neighbors.iter().map(|s| (*s).to_owned()));
}
#[must_use]
pub fn from_tab_order(screens: &[&str], background_divisor: u64) -> Self {
let mut s = Self::new(background_divisor);
for window in screens.windows(2) {
let (a, b) = (window[0], window[1]);
s.adjacency
.entry(a.to_owned())
.or_default()
.push(b.to_owned());
s.adjacency
.entry(b.to_owned())
.or_default()
.push(a.to_owned());
}
s
}
#[must_use]
pub const fn background_divisor(&self) -> u64 {
self.background_divisor
}
#[must_use]
pub fn adjacency(&self) -> &HashMap<String, Vec<String>> {
&self.adjacency
}
}
impl TickStrategy for ActivePlusAdjacent {
fn should_tick(
&mut self,
screen_id: &str,
tick_count: u64,
active_screen: &str,
) -> TickDecision {
if self
.adjacency
.get(active_screen)
.is_some_and(|adj| adj.iter().any(|a| a == screen_id))
{
return TickDecision::Tick;
}
if tick_count.is_multiple_of(self.background_divisor) {
TickDecision::Tick
} else {
TickDecision::Skip
}
}
fn name(&self) -> &str {
"ActivePlusAdjacent"
}
fn debug_stats(&self) -> Vec<(String, String)> {
vec![
("strategy".into(), "ActivePlusAdjacent".into()),
(
"background_divisor".into(),
self.background_divisor.to_string(),
),
("adjacency_entries".into(), self.adjacency.len().to_string()),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adjacent_screens_always_tick() {
let mut s = ActivePlusAdjacent::from_tab_order(&["A", "B", "C"], 100);
assert_eq!(s.should_tick("B", 1, "A"), TickDecision::Tick);
assert_eq!(s.should_tick("B", 7, "A"), TickDecision::Tick);
}
#[test]
fn non_adjacent_respects_divisor() {
let mut s = ActivePlusAdjacent::from_tab_order(&["A", "B", "C"], 5);
assert_eq!(s.should_tick("C", 1, "A"), TickDecision::Skip);
assert_eq!(s.should_tick("C", 5, "A"), TickDecision::Tick);
}
#[test]
fn from_tab_order_builds_bidirectional() {
let s = ActivePlusAdjacent::from_tab_order(&["X", "Y", "Z"], 10);
let adj = s.adjacency();
assert!(adj["X"].contains(&"Y".to_owned()));
assert!(adj["Y"].contains(&"X".to_owned()));
assert!(adj["Y"].contains(&"Z".to_owned()));
assert!(adj["Z"].contains(&"Y".to_owned()));
assert!(!adj["X"].contains(&"Z".to_owned()));
assert!(!adj["Z"].contains(&"X".to_owned()));
}
#[test]
fn edge_screens_have_one_neighbor() {
let s = ActivePlusAdjacent::from_tab_order(&["A", "B", "C", "D"], 10);
assert_eq!(s.adjacency()["A"].len(), 1); assert_eq!(s.adjacency()["D"].len(), 1); assert_eq!(s.adjacency()["B"].len(), 2); }
#[test]
fn unknown_screen_uses_divisor() {
let mut s = ActivePlusAdjacent::from_tab_order(&["A", "B"], 4);
assert_eq!(s.should_tick("unknown", 3, "A"), TickDecision::Skip);
assert_eq!(s.should_tick("unknown", 4, "A"), TickDecision::Tick);
}
#[test]
fn add_adjacency_appends() {
let mut s = ActivePlusAdjacent::new(10);
s.add_adjacency("home", &["settings", "profile"]);
assert_eq!(s.adjacency()["home"].len(), 2);
s.add_adjacency("home", &["help"]);
assert_eq!(s.adjacency()["home"].len(), 3);
}
#[test]
fn name_is_stable() {
let s = ActivePlusAdjacent::new(5);
assert_eq!(s.name(), "ActivePlusAdjacent");
}
#[test]
fn debug_stats_reports_fields() {
let s = ActivePlusAdjacent::from_tab_order(&["A", "B", "C"], 8);
let stats = s.debug_stats();
assert!(
stats
.iter()
.any(|(k, v)| k == "background_divisor" && v == "8")
);
assert!(
stats
.iter()
.any(|(k, v)| k == "adjacency_entries" && v == "3")
);
}
#[test]
fn divisor_0_clamped() {
let s = ActivePlusAdjacent::new(0);
assert_eq!(s.background_divisor(), 1);
}
#[test]
fn empty_tab_order() {
let s = ActivePlusAdjacent::from_tab_order(&[], 5);
assert!(s.adjacency().is_empty());
}
#[test]
fn single_screen_tab_order() {
let s = ActivePlusAdjacent::from_tab_order(&["only"], 5);
assert!(s.adjacency().is_empty());
}
}