rust/hedgewars-server/src/core/events.rs
author S.D.
Tue, 27 Sep 2022 14:59:03 +0300
changeset 15900 fc3cb23fd26f
parent 15840 0c21cdbc5207
permissions -rw-r--r--
Allow to see rooms of incompatible versions in the lobby For the new clients the room version is shown in a separate column. There is also a hack for previous versions clients: the room vesion specifier is prepended to the room names for rooms of incompatible versions, and the server shows 'incompatible version' error if the client tries to join them.

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]);
    }
}