lgui-core 0.2.0

Platform-neutral runtime and UI primitives for LGUI
Documentation
use std::{
    collections::BTreeMap,
    fmt,
    sync::{Arc, Mutex, Weak},
};

use super::{CacheDomain, CacheScope, CacheUsage, TrimReason};

#[cfg_attr(feature = "diagnostics-serde", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct DomainInstanceId(pub u64);

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrimRequest {
    pub reason: TrimReason,
    pub scope: CacheScope,
    pub target_bytes: usize,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TrimResult {
    pub before_bytes: usize,
    pub after_bytes: usize,
}

impl TrimResult {
    pub const fn released_bytes(self) -> usize {
        self.before_bytes.saturating_sub(self.after_bytes)
    }
}

#[derive(Clone)]
pub struct CacheAdapter {
    usage: Arc<dyn Fn() -> CacheUsage + Send + Sync>,
    trim: Arc<dyn Fn(TrimRequest) -> TrimResult + Send + Sync>,
    set_budget: Arc<dyn Fn(usize) + Send + Sync>,
}

impl CacheAdapter {
    pub fn new(
        usage: impl Fn() -> CacheUsage + Send + Sync + 'static,
        trim: impl Fn(TrimRequest) -> TrimResult + Send + Sync + 'static,
    ) -> Self {
        Self {
            usage: Arc::new(usage),
            trim: Arc::new(trim),
            set_budget: Arc::new(|_| {}),
        }
    }

    pub fn managed(
        usage: impl Fn() -> CacheUsage + Send + Sync + 'static,
        trim: impl Fn(TrimRequest) -> TrimResult + Send + Sync + 'static,
        set_budget: impl Fn(usize) + Send + Sync + 'static,
    ) -> Self {
        Self {
            usage: Arc::new(usage),
            trim: Arc::new(trim),
            set_budget: Arc::new(set_budget),
        }
    }

    pub(crate) fn usage(&self) -> CacheUsage {
        (self.usage)()
    }

    pub(crate) fn trim(&self, request: TrimRequest) -> TrimResult {
        (self.trim)(request)
    }

    pub(crate) fn set_budget(&self, budget_bytes: usize) {
        (self.set_budget)(budget_bytes);
    }
}

impl fmt::Debug for CacheAdapter {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("CacheAdapter(..)")
    }
}

#[derive(Clone, Debug)]
pub struct DomainRegistration {
    pub domain: CacheDomain,
    pub instance: DomainInstanceId,
    pub owner: String,
    pub adapter: CacheAdapter,
}

impl DomainRegistration {
    pub fn new(
        domain: CacheDomain,
        instance: DomainInstanceId,
        owner: impl Into<String>,
        adapter: CacheAdapter,
    ) -> Self {
        Self {
            domain,
            instance,
            owner: owner.into(),
            adapter,
        }
    }
}

#[derive(Debug)]
pub(crate) struct RegistryState {
    pub next_registration: u64,
    pub next_instance: u64,
    pub entries: BTreeMap<u64, DomainRegistration>,
}

impl Default for RegistryState {
    fn default() -> Self {
        Self {
            next_registration: 1,
            next_instance: 1,
            entries: BTreeMap::new(),
        }
    }
}

pub struct CacheRegistration {
    id: u64,
    registry: Weak<Mutex<RegistryState>>,
    on_drop: Option<Box<dyn FnOnce() + Send + Sync>>,
}

impl CacheRegistration {
    pub(crate) fn new(
        id: u64,
        registry: Weak<Mutex<RegistryState>>,
        on_drop: impl FnOnce() + Send + Sync + 'static,
    ) -> Self {
        Self {
            id,
            registry,
            on_drop: Some(Box::new(on_drop)),
        }
    }

    pub const fn id(&self) -> u64 {
        self.id
    }
}

impl fmt::Debug for CacheRegistration {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("CacheRegistration")
            .field("id", &self.id)
            .finish()
    }
}

impl Drop for CacheRegistration {
    fn drop(&mut self) {
        let removed = self.registry.upgrade().is_some_and(|registry| {
            registry
                .lock()
                .expect("memory registry poisoned")
                .entries
                .remove(&self.id)
                .is_some()
        });
        if removed {
            if let Some(on_drop) = self.on_drop.take() {
                on_drop();
            }
        }
    }
}