rust/hedgewars-server/src/core/events.rs
author alfadur
Thu, 09 Sep 2021 21:06:45 +0300
changeset 15840 0c21cdbc5207
parent 15822 6af892a0a4b8
permissions -rw-r--r--
use array map in timer constructor

use slab::Slab;
use std::{
    convert::TryInto,
    iter,
    num::NonZeroU32,
    time::{Duration, Instant},
};

struct Event<Data> {
    event_id: u32,
    data: Data,
}

#[derive(Clone)]
pub struct Timeout {
    tick_index: u32,
    event_index: u32,
    event_id: u32,
}

pub struct TimedEvents<Data, const MAX_TIMEOUT: usize> {
    events: [Slab<Event<Data>>; MAX_TIMEOUT],
    current_time: Instant,
    current_tick_index: u32,
    next_event_id: u32,
    events_count: u32,
}

impl<Data, const MAX_TIMEOUT: usize> TimedEvents<Data, MAX_TIMEOUT> {
    pub fn new() -> Self {
        Self {
            events: [0; MAX_TIMEOUT].map(|_| Slab::new()),
            current_time: Instant::now(),
            current_tick_index: 0,
            next_event_id: 0,
            events_count: 0,
        }
    }

    pub fn set_timeout(&mut self, seconds_delay: NonZeroU32, data: Data) -> Timeout {
        let tick_index = (self.current_tick_index
            + std::cmp::min(seconds_delay.get(), MAX_TIMEOUT as u32))
            % MAX_TIMEOUT as u32;
        let event_id = self.next_event_id;
        self.next_event_id += 1;
        let event = Event { event_id, data };

        let entry = self.events[tick_index as usize].vacant_entry();
        let event_index = entry.key() as u32;
        entry.insert(event);

        self.events_count += 1;

        Timeout {
            tick_index,
            event_index,
            event_id,
        }
    }

    pub fn cancel_timeout(&mut self, timeout: Timeout) -> Option<Data> {
        let events = &mut self.events[timeout.tick_index as usize];
        if matches!(events.get(timeout.event_index as usize), Some(Event { event_id: id, ..}) if *id == timeout.event_id)
        {
            self.events_count -= 1;
            Some(events.remove(timeout.event_index as usize).data)
        } else {
            None
        }
    }

    pub fn poll(&mut self, time: Instant) -> Vec<Data> {
        let mut result = vec![];
        let second = Duration::from_secs(1);
        while time - self.current_time > second {
            self.current_time += second;
            self.current_tick_index = (self.current_tick_index + 1) % MAX_TIMEOUT as u32;
            result.extend(
                self.events[self.current_tick_index as usize]
                    .drain()
                    .map(|e| e.data),
            );
        }
        self.events_count -= result.len() as u32;
        result
    }

    pub fn is_empty(&self) -> bool {
        self.events_count == 0
    }
}

mod test {
    use super::TimedEvents;
    use std::{
        num::NonZeroU32,
        time::{Duration, Instant},
    };

    #[test]
    fn events_test() {
        let mut events = TimedEvents::<u32, 30>::new();
        let now = Instant::now();

        let timeouts = (1..=3)
            .map(|n| events.set_timeout(NonZeroU32::new(n).unwrap(), n))
            .collect::<Vec<_>>();

        let second = Duration::from_secs(1);
        assert_eq!(events.cancel_timeout(timeouts[1].clone()), Some(2));
        assert_eq!(events.poll(now + second), vec![1]);
        assert!(events.poll(now + second).is_empty());
        assert!(events.poll(now + 2 * second).is_empty());
        assert_eq!(events.poll(now + 3 * second), vec![3]);
    }
}