use super::*;
use crate::wheels::{cancellable::*, *};
use std::{
fmt::Debug,
hash::Hash,
rc::Rc,
time::{Duration, SystemTime},
};
#[derive(Debug)]
enum SimulationEntry<I, O, P>
where
I: Hash + Clone + Eq,
O: OneshotState<Id = I>,
P: PeriodicState<Id = I>,
{
OneShot { state: O },
Periodic { period: Duration, state: P },
}
impl<I, O, P> SimulationEntry<I, O, P>
where
I: Hash + Clone + Eq + Debug,
O: OneshotState<Id = I> + Debug,
P: PeriodicState<Id = I> + Debug,
{
fn execute(self) -> Option<(Self, Duration)> {
match self {
SimulationEntry::OneShot { state } => {
state.trigger();
None
}
SimulationEntry::Periodic { period, state } => match state.trigger() {
TimerReturn::Reschedule(new_state) => {
let new_entry = SimulationEntry::Periodic {
period,
state: new_state,
};
Some((new_entry, period))
}
TimerReturn::Cancel => None,
},
}
}
fn execute_unique_ref(unique_ref: Rc<Self>) -> Option<(Rc<Self>, Duration)> {
let unique = Rc::try_unwrap(unique_ref).expect("shouldn't hold on to these refs anywhere");
unique.execute().map(|t| {
let (new_unique, delay) = t;
(Rc::new(new_unique), delay)
})
}
}
impl<I, O, P> CancellableTimerEntry for SimulationEntry<I, O, P>
where
I: Hash + Clone + Eq + Debug,
O: OneshotState<Id = I> + Debug,
P: PeriodicState<Id = I> + Debug,
{
type Id = I;
fn id(&self) -> &Self::Id {
match self {
SimulationEntry::OneShot { state, .. } => state.id(),
SimulationEntry::Periodic { state, .. } => state.id(),
}
}
}
pub struct SimulationTimer<I, O, P>
where
I: Hash + Clone + Eq + Debug,
O: OneshotState<Id = I> + Debug,
P: PeriodicState<Id = I> + Debug,
{
time: u128,
timer: QuadWheelWithOverflow<SimulationEntry<I, O, P>>,
}
impl<I, O, P> SimulationTimer<I, O, P>
where
I: Hash + Clone + Eq + Debug,
O: OneshotState<Id = I> + Debug,
P: PeriodicState<Id = I> + Debug,
{
pub fn new() -> Self {
SimulationTimer {
time: 0u128,
timer: QuadWheelWithOverflow::new(),
}
}
pub fn at(now: SystemTime) -> Self {
let t = now
.duration_since(SystemTime::UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH!");
let tms = t.as_millis();
SimulationTimer {
time: tms,
timer: QuadWheelWithOverflow::new(),
}
}
pub fn current_time(&self) -> u128 {
self.time
}
pub fn next(&mut self) -> SimulationStep {
loop {
match self.timer.can_skip() {
Skip::Empty => return SimulationStep::Finished,
Skip::None => {
let res = self.timer.tick();
self.time += 1u128;
if !res.is_empty() {
for e in res {
self.trigger_entry(e);
}
return SimulationStep::Ok;
}
}
Skip::Millis(ms) => {
self.timer.skip(ms);
self.time += ms as u128;
let res = self.timer.tick();
self.time += 1u128;
if !res.is_empty() {
for e in res {
self.trigger_entry(e);
}
return SimulationStep::Ok;
}
}
}
}
}
fn trigger_entry(&mut self, e: Rc<SimulationEntry<I, O, P>>) -> () {
match SimulationEntry::execute_unique_ref(e) {
Some((new_e, delay)) => match self.timer.insert_ref_with_delay(new_e, delay) {
Ok(_) => (),
Err(TimerError::Expired(e)) => panic!(
"Trying to insert periodic timer entry with 0ms period! {:?}",
e
),
Err(f) => panic!("Could not insert timer entry! {:?}", f),
},
None => (),
}
}
}
impl<I> SimulationTimer<I, OneShotClosureState<I>, PeriodicClosureState<I>>
where
I: Hash + Clone + Eq + Debug,
{
pub fn for_closures() -> Self {
Self::new()
}
}
#[cfg(feature = "uuid-extras")]
impl
SimulationTimer<uuid::Uuid, OneShotClosureState<uuid::Uuid>, PeriodicClosureState<uuid::Uuid>>
{
pub fn for_uuid_closures() -> Self {
Self::new()
}
}
pub enum SimulationStep {
Finished,
Ok,
}
impl<I, O, P> Timer for SimulationTimer<I, O, P>
where
I: Hash + Clone + Eq + Debug,
O: OneshotState<Id = I> + Debug,
P: PeriodicState<Id = I> + Debug,
{
type Id = I;
type OneshotState = O;
type PeriodicState = P;
fn schedule_once(&mut self, timeout: Duration, state: Self::OneshotState) -> () {
let e = SimulationEntry::OneShot { state };
match self.timer.insert_ref_with_delay(Rc::new(e), timeout) {
Ok(_) => (),
Err(TimerError::Expired(e)) => {
if let None = SimulationEntry::execute_unique_ref(e) {
()
} else {
unreachable!("OneShot produced reschedule!")
}
}
Err(f) => panic!("Could not insert timer entry! {:?}", f),
}
}
fn schedule_periodic(
&mut self,
delay: Duration,
period: Duration,
state: Self::PeriodicState,
) -> () {
let e = SimulationEntry::Periodic { period, state };
match self.timer.insert_ref_with_delay(Rc::new(e), delay) {
Ok(_) => (),
Err(TimerError::Expired(e)) => match SimulationEntry::execute_unique_ref(e) {
Some((new_e, delay)) => match self.timer.insert_ref_with_delay(new_e, delay) {
Ok(_) => (),
Err(TimerError::Expired(e)) => panic!(
"Trying to insert periodic timer entry with 0ms period! {:?}",
e
),
Err(f) => panic!("Could not insert timer entry! {:?}", f),
},
None => (),
},
Err(f) => panic!("Could not insert timer entry! {:?}", f),
}
}
fn cancel(&mut self, id: &Self::Id) -> () {
match self.timer.cancel(id) {
Ok(_) => (),
Err(f) => eprintln!("Could not cancel timer with id={:?}. {:?}", id, f),
}
}
}
#[cfg(feature = "uuid-extras")]
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::*;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
#[test]
fn simple_simulation() {
let num = 10usize;
let mut barriers: Vec<Arc<Mutex<bool>>> = Vec::with_capacity(num);
let mut timer = SimulationTimer::for_uuid_closures();
for i in 0..num {
let barrier = Arc::new(Mutex::new(false));
barriers.push(barrier.clone());
let id = Uuid::new_v4();
let timeout = fib_time(i);
timer.schedule_action_once(id, timeout, move |_| {
println!("Running action {}", i);
let mut guard = barrier.lock().unwrap();
*guard = true;
});
}
let mut running = true;
while running {
match timer.next() {
SimulationStep::Ok => println!("Next!"),
SimulationStep::Finished => running = false,
}
}
println!("Simulation run done!");
for b in barriers {
let guard = b.lock().unwrap();
assert_eq!(*guard, true);
}
}
#[test]
fn rescheduling_simulation() {
let num = 10usize;
let mut barriers: Vec<Arc<Mutex<bool>>> = Vec::with_capacity(num);
let mut timer = SimulationTimer::for_uuid_closures();
for i in 1..num {
let barrier = Arc::new(Mutex::new(false));
barriers.push(barrier.clone());
let id = Uuid::new_v4();
let timeout = fib_time(i);
let mut counter: usize = 5;
timer.schedule_action_periodic(id, timeout, timeout, move |_| {
println!("Running action {}", i);
if counter > 0 {
counter -= 1;
TimerReturn::Reschedule(())
} else {
let mut guard = barrier.lock().unwrap();
*guard = true;
TimerReturn::Cancel
}
});
}
let mut running = true;
while running {
match timer.next() {
SimulationStep::Ok => println!("Next!"),
SimulationStep::Finished => running = false,
}
}
println!("Simulation run done!");
for b in barriers {
let guard = b.lock().unwrap();
assert_eq!(*guard, true);
}
}
}