#[cfg(not(test))]
use crate::compat::atomic::{AtomicU64, AtomicUsize, Ordering};
#[cfg(test)]
use crate::compat::atomic::{AtomicU64, Ordering};
use crate::compat::{Mutex, RefCell};
use crate::widget::display_widgets::icon::IconData;
use crate::widget::icon_registry::MAX_REGISTERED_ICONS;
#[derive(Clone, Copy)]
struct Slot {
name: &'static str,
data: IconData,
density: u8,
}
#[derive(Clone, Copy)]
struct Registry {
slots: [Option<Slot>; MAX_REGISTERED_ICONS],
len: usize,
}
impl Registry {
const fn new() -> Self {
Self { slots: [const { None }; MAX_REGISTERED_ICONS], len: 0 }
}
}
#[cfg(not(test))]
static REGISTRY: Mutex<Registry> = Mutex::new(Registry::new());
static REGISTRY_REVISION: AtomicU64 = AtomicU64::new(0);
thread_local! {
static SNAPSHOT: RefCell<Snapshot> = const { RefCell::new(Snapshot { revision: u64::MAX, table: Registry::new() }) };
}
struct Snapshot {
revision: u64,
table: Registry,
}
fn bump_registry_revision() {
REGISTRY_REVISION.fetch_add(1, Ordering::Release);
}
fn with_snapshot<R>(f: impl FnOnce(&Registry) -> R) -> Option<R> {
let revision = REGISTRY_REVISION.load(Ordering::Acquire);
SNAPSHOT
.try_with(|cached| {
let mut cached = cached.borrow_mut();
if cached.revision != revision {
cached.table = *crate::compat::lock(registry());
cached.revision = revision;
}
f(&cached.table)
})
.ok()
}
#[cfg(not(test))]
static REGISTERED_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(test)]
thread_local! {
static TEST_REGISTRY: &'static Mutex<Registry> =
crate::compat::Box::leak(crate::compat::Box::new(Mutex::new(Registry::new())));
}
fn registry() -> &'static Mutex<Registry> {
#[cfg(test)]
{
TEST_REGISTRY.with(|registry| *registry)
}
#[cfg(not(test))]
{
®ISTRY
}
}
pub(crate) fn register_icon_data(
name: &'static str,
paths: &'static [&'static str],
grid: u16,
) -> bool {
register_icon_data_with_density(name, paths, grid, 1)
}
pub(crate) fn register_icon_data_with_density(
name: &'static str,
paths: &'static [&'static str],
grid: u16,
density: u8,
) -> bool {
let density = density.max(1);
let data = IconData { name, grid, paths };
let mut registry = crate::compat::lock(registry());
let len = registry.len;
for slot in registry.slots[..len].iter_mut() {
if let Some(existing) = slot {
if existing.name == name && existing.density == density {
*slot = Some(Slot { name, data, density });
bump_registry_revision();
return true;
}
}
}
if len >= MAX_REGISTERED_ICONS {
return false;
}
registry.slots[len] = Some(Slot { name, data, density });
registry.len = len + 1;
#[cfg(not(test))]
REGISTERED_COUNT.fetch_add(1, Ordering::Relaxed);
bump_registry_revision();
true
}
pub(crate) fn clear_registered_icon_data() -> usize {
let mut registry = crate::compat::lock(registry());
let removed = registry.len;
registry.slots = [const { None }; MAX_REGISTERED_ICONS];
registry.len = 0;
#[cfg(not(test))]
REGISTERED_COUNT.store(0, Ordering::Relaxed);
bump_registry_revision();
removed
}
pub(crate) fn registered_icon_data_count() -> usize {
#[cfg(not(test))]
{
REGISTERED_COUNT.load(Ordering::Relaxed)
}
#[cfg(test)]
{
crate::compat::lock(registry()).len
}
}
pub(crate) fn lookup_registered(name: &str) -> Option<IconData> {
lookup_registered_scaled(name, 1.0)
}
pub(crate) fn lookup_registered_scaled(name: &str, scale_factor: f32) -> Option<IconData> {
#[cfg(not(test))]
if REGISTERED_COUNT.load(Ordering::Relaxed) == 0 {
return None;
}
with_snapshot(|registry| lookup_in(registry, name, scale_factor)).flatten()
}
fn lookup_in(registry: &Registry, name: &str, scale_factor: f32) -> Option<IconData> {
let slots = registry.slots[..registry.len].iter().flatten();
let mut best: Option<&Slot> = None;
let high_density = scale_factor >= 2.0;
for slot in slots {
if slot.name != name {
continue;
}
let wanted = if high_density { slot.density >= 2 } else { slot.density == 1 };
if wanted && best.is_none_or(|current| slot.density > current.density) {
best = Some(slot);
}
}
if best.is_none() {
best = registry.slots[..registry.len]
.iter()
.flatten()
.filter(|slot| slot.name == name)
.min_by_key(|slot| slot.density);
}
best.map(|slot| slot.data)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_snapshot_sees_replacement_and_clear() {
clear_registered_icon_data();
let name = "g33_snapshot_probe";
static FIRST: [&str; 1] = ["M0 0L1 1"];
static SECOND: [&str; 1] = ["M0 0L2 2"];
assert!(register_icon_data(name, &FIRST, 24));
assert_eq!(lookup_registered(name).map(|data| data.paths), Some(&FIRST[..]));
assert!(register_icon_data(name, &SECOND, 24));
assert_eq!(
lookup_registered(name).map(|data| data.paths),
Some(&SECOND[..]),
"a replacement must invalidate the thread's snapshot"
);
assert_eq!(clear_registered_icon_data(), 1);
assert_eq!(lookup_registered(name), None, "a clear must invalidate the snapshot");
}
}