use std::collections::VecDeque;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::application::time;
use crate::coordinates::{Position, Size};
pub const DEFAULT_CAPACITY: usize = 256;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Origin {
Requested,
PlatformDefault,
Fullscreen,
}
impl Origin {
pub const fn name(self) -> &'static str {
match self {
Origin::Requested => "requested",
Origin::PlatformDefault => "platform_default",
Origin::Fullscreen => "fullscreen",
}
}
}
#[derive(Clone, Debug)]
pub struct Entry {
pub id: u64,
pub origin: Origin,
pub title: String,
pub requested: Option<(Position, Size)>,
pub created_at: time::Instant,
pub closed_at: Option<time::Instant>,
pub surface_attached: bool,
pub last_observed: Option<(Size, f64, time::Instant)>,
}
impl Entry {
pub fn is_open(&self) -> bool {
self.closed_at.is_none()
}
}
struct Registry {
entries: VecDeque<Entry>,
capacity: usize,
dropped: u64,
}
impl Registry {
fn new(capacity: usize) -> Registry {
Registry {
entries: VecDeque::new(),
capacity,
dropped: 0,
}
}
fn push(&mut self, entry: Entry) {
if self.capacity == 0 {
self.dropped += 1;
return;
}
while self.entries.len() >= self.capacity {
let victim = self
.entries
.iter()
.position(|existing| !existing.is_open())
.unwrap_or(0);
self.entries.remove(victim);
self.dropped += 1;
}
self.entries.push_back(entry);
}
fn find(&mut self, id: u64) -> Option<&mut Entry> {
self.entries.iter_mut().find(|entry| entry.id == id)
}
}
fn capacity() -> usize {
std::env::var("APP_WINDOW_REGISTRY_CAPACITY")
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(DEFAULT_CAPACITY)
}
fn registry() -> &'static Mutex<Registry> {
use std::sync::OnceLock;
static REGISTRY: OnceLock<Mutex<Registry>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(Registry::new(capacity())))
}
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
pub(crate) fn opened(
origin: Origin,
title: String,
requested: Option<(Position, Size)>,
) -> Option<u64> {
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let mut registry = registry().try_lock().ok()?;
registry.push(Entry {
id,
origin,
title,
requested,
created_at: time::Instant::now(),
closed_at: None,
surface_attached: false,
last_observed: None,
});
Some(id)
}
pub(crate) fn closed(id: u64) {
if let Ok(mut registry) = registry().try_lock()
&& let Some(entry) = registry.find(id)
{
entry.closed_at = Some(time::Instant::now());
}
}
pub(crate) fn surface_attached(id: u64) {
if let Ok(mut registry) = registry().try_lock()
&& let Some(entry) = registry.find(id)
{
entry.surface_attached = true;
}
}
pub(crate) fn observed(id: u64, size: Size, scale: f64) {
if let Ok(mut registry) = registry().try_lock()
&& let Some(entry) = registry.find(id)
{
entry.last_observed = Some((size, scale, time::Instant::now()));
}
}
pub fn entries() -> Option<Vec<Entry>> {
let registry = registry().try_lock().ok()?;
Some(registry.entries.iter().cloned().collect())
}
pub fn stats() -> Option<(u64, usize)> {
let registry = registry().try_lock().ok()?;
Some((registry.dropped, registry.capacity))
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(id: u64, open: bool) -> Entry {
Entry {
id,
origin: Origin::Requested,
title: "t".to_string(),
requested: None,
created_at: time::Instant::now(),
closed_at: if open {
None
} else {
Some(time::Instant::now())
},
surface_attached: false,
last_observed: None,
}
}
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn overflow_forgets_closed_windows_before_live_ones() {
let mut registry = Registry::new(2);
registry.push(entry(1, true));
registry.push(entry(2, false));
registry.push(entry(3, true));
let ids: Vec<u64> = registry.entries.iter().map(|entry| entry.id).collect();
assert_eq!(ids, vec![1, 3], "the closed window is the one to lose");
assert_eq!(registry.dropped, 1);
}
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn overflow_of_all_live_windows_drops_the_oldest_and_counts_it() {
let mut registry = Registry::new(2);
registry.push(entry(1, true));
registry.push(entry(2, true));
registry.push(entry(3, true));
let ids: Vec<u64> = registry.entries.iter().map(|entry| entry.id).collect();
assert_eq!(ids, vec![2, 3]);
assert_eq!(
registry.dropped, 1,
"a drop is counted so the report can say so"
);
}
#[cfg_attr(target_arch = "wasm32", wasm_lite::wasm_lite_test)]
#[cfg_attr(not(target_arch = "wasm32"), test)]
fn a_capacity_of_zero_retains_nothing_and_counts_everything() {
let mut registry = Registry::new(0);
registry.push(entry(1, true));
assert!(registry.entries.is_empty());
assert_eq!(registry.dropped, 1);
}
}