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.
//! Temporizadores del hilo principal. La app duerme hasta `next_deadline()`, así
//! que no necesita sondeo periódico cuando no hay temporizadores pendientes.

use crate::core::entity::EntityMap;
use crate::core::tasks::CancelToken;
use std::time::{Duration, Instant};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TimerId(u64);

type TimerJob = Box<dyn FnOnce(&mut EntityMap)>;

struct Entry {
    deadline: Instant,
    id: TimerId,
    job: TimerJob,
    cancel: Option<CancelToken>,
}

impl Entry {
    fn cancelled(&self) -> bool {
        self.cancel.as_ref().is_some_and(CancelToken::is_cancelled)
    }
}

#[derive(Default)]
pub struct TimerQueue {
    entries: Vec<Entry>,
    next_id: u64,
    animation_frame: bool,
}

impl TimerQueue {
    pub fn schedule(&mut self, delay: Duration, job: impl FnOnce(&mut EntityMap) + 'static) -> TimerId {
        self.insert(delay, None, Box::new(job))
    }

    pub(crate) fn schedule_cancellable(
        &mut self,
        delay: Duration,
        cancel: CancelToken,
        job: impl FnOnce(&mut EntityMap) + 'static,
    ) -> TimerId {
        self.insert(delay, Some(cancel), Box::new(job))
    }

    fn insert(&mut self, delay: Duration, cancel: Option<CancelToken>, job: TimerJob) -> TimerId {
        self.next_id += 1;
        let id = TimerId(self.next_id);
        self.entries.push(Entry { deadline: Instant::now() + delay, id, job, cancel });
        id
    }

    pub fn cancel(&mut self, id: TimerId) {
        self.entries.retain(|entry| entry.id != id);
    }

    pub fn next_deadline(&self) -> Option<Instant> {
        self.entries.iter().filter(|entry| !entry.cancelled()).map(|entry| entry.deadline).min()
    }

    /// Extrae los trabajos vencidos; se ejecutan fuera para poder reprogramar.
    pub fn take_due(&mut self, now: Instant) -> Vec<TimerJob> {
        let mut due = Vec::new();
        let mut index = 0;
        while index < self.entries.len() {
            if self.entries[index].cancelled() {
                self.entries.swap_remove(index);
            } else if self.entries[index].deadline <= now {
                due.push(self.entries.swap_remove(index).job);
            } else {
                index += 1;
            }
        }
        due
    }

    /// Pide otro frame (animaciones). Se consume al leerlo.
    pub fn request_animation_frame(&mut self) {
        self.animation_frame = true;
    }

    pub fn take_animation_frame(&mut self) -> bool {
        std::mem::take(&mut self.animation_frame)
    }
}