aura-gui 0.2.2

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Estado que un elemento conserva entre frames, indexado por su id y tipo.
//!
//! Como `with_element_state` de GPUI: el árbol de elementos se reconstruye en
//! cada frame, pero lo que un elemento guardó con su id (scroll, animaciones,
//! medidas) sigue ahí en el siguiente. Hay dos generaciones; lo que nadie
//! consulta durante un frame completo se libera.

use rustc_hash::FxHashMap;
use std::any::{Any, TypeId};
use std::rc::Rc;

type Key = (Rc<str>, TypeId);

#[derive(Default)]
pub struct ElementStateMap {
    current: FxHashMap<Key, Rc<dyn Any>>,
    previous: FxHashMap<Key, Rc<dyn Any>>,
}

impl ElementStateMap {
    pub fn begin_frame(&mut self) {
        self.previous = std::mem::take(&mut self.current);
    }

    /// Estado `S` del elemento `id`; se crea con `Default` la primera vez. Se
    /// devuelve compartido, así que `S` suele ser un `Cell` o un `RefCell`.
    pub fn get_or_default<S: Default + 'static>(&mut self, id: &str) -> Rc<S> {
        let key: Key = (Rc::from(id), TypeId::of::<S>());
        let state = match self.current.get(&key) {
            Some(state) => state.clone(),
            None => {
                let state = self.previous.remove(&key).unwrap_or_else(|| Rc::new(S::default()) as Rc<dyn Any>);
                self.current.insert(key, state.clone());
                state
            }
        };
        // La clave incluye el `TypeId`, así que la conversión no puede fallar.
        state.downcast::<S>().unwrap_or_else(|_| Rc::new(S::default()))
    }

    pub fn len(&self) -> usize {
        self.current.len()
    }

    pub fn is_empty(&self) -> bool {
        self.current.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::Cell;

    #[test]
    fn state_survives_while_used_and_is_dropped_otherwise() {
        let mut map = ElementStateMap::default();
        map.get_or_default::<Cell<u32>>("lista").set(7);
        map.begin_frame();
        assert_eq!(map.get_or_default::<Cell<u32>>("lista").get(), 7, "pasa al frame siguiente");
        assert!(!map.get_or_default::<Cell<bool>>("lista").get(), "otro tipo, otro estado");
        map.begin_frame();
        map.begin_frame();
        assert_eq!(map.get_or_default::<Cell<u32>>("lista").get(), 0, "un frame sin uso lo libera");
    }
}