#![feature(coroutines, coroutine_trait)]
use std::cmp::{Ordering, Reverse};
use std::collections::BinaryHeap;
use std::ops::{Coroutine, CoroutineState};
use std::pin::Pin;
pub mod prelude;
pub mod resources;
use resources::{Resource, Store};
pub trait SimState {
fn get_effect(&self) -> Effect;
fn set_effect(&mut self, effect: Effect);
fn should_log(&self) -> bool;
}
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub enum Effect {
TimeOut(f64),
Event {
time: f64,
process: ProcessId,
},
Request(ResourceId),
Release(ResourceId),
Push(StoreId),
Pull(StoreId),
Wait,
Trace,
}
pub type ProcessId = usize;
pub type ResourceId = usize;
pub type StoreId = usize;
pub type Process<T> = dyn Coroutine<SimContext<T>, Yield = T, Return = ()> + Unpin;
pub struct Simulation<T: SimState + Clone> {
time: f64,
steps: usize,
processes: Vec<Option<Box<Process<T>>>>,
future_events: BinaryHeap<Reverse<Event<T>>>,
processed_events: Vec<(Event<T>, T)>,
resources: Vec<Box<dyn Resource<T>>>,
stores: Vec<Box<dyn Store<T>>>,
future_events_buffer: Vec<Event<T>>,
}
#[derive(Debug, Clone)]
pub struct SimContext<T> {
time: f64,
state: T,
}
#[derive(Debug, Copy, Clone)]
pub struct Event<T> {
time: f64,
process: ProcessId,
state: T,
}
pub enum EndCondition {
Time(f64),
NoEvents,
NSteps(usize),
}
impl<T: 'static + SimState + Clone> Simulation<T> {
pub fn new() -> Simulation<T> {
Simulation::<T>::default()
}
pub fn time(&self) -> f64 {
self.time
}
pub fn processed_events(&self) -> &[(Event<T>, T)] {
self.processed_events.as_slice()
}
pub fn create_process(
&mut self,
process: Box<dyn Coroutine<SimContext<T>, Yield = T, Return = ()> + Unpin>,
) -> ProcessId {
let id = self.processes.len();
self.processes.push(Some(process));
id
}
pub fn create_resource(&mut self, resource: Box<dyn Resource<T>>) -> ResourceId {
let id = self.resources.len();
self.resources.push(resource);
id
}
pub fn create_store(&mut self, store: Box<dyn Store<T>>) -> StoreId {
let id = self.stores.len();
self.stores.push(store);
id
}
pub fn schedule_event(&mut self, time: f64, process: ProcessId, state: T) {
self.future_events
.push(Reverse(Event::new(time, process, state)));
}
fn log_processed_event(&mut self, event: &Event<T>, sim_state: T) {
if sim_state.should_log() {
self.processed_events.push((event.clone(), sim_state));
}
}
pub fn step(&mut self) {
self.steps += 1;
if let Some(Reverse(event)) = self.future_events.pop() {
self.time = event.time();
let gstatepin = Pin::new(
self.processes[event.process]
.as_mut()
.expect("ERROR. Tried to resume a completed process."),
)
.resume(SimContext {
time: self.time,
state: event.state().clone(),
});
match gstatepin.clone() {
CoroutineState::Yielded(y) => {
self.log_processed_event(&event, y);
}
CoroutineState::Complete(_) => {}
}
match gstatepin {
CoroutineState::Yielded(y) => {
let effect = y.get_effect();
match effect {
Effect::TimeOut(t) => self.future_events.push(Reverse(Event {
time: self.time + t,
process: event.process(),
state: y,
})),
Effect::Event { time, process } => {
let e = Event::new(time + self.time, process, y);
self.future_events.push(Reverse(e))
}
Effect::Request(r) => {
let res = &mut self.resources[r];
let request_event = Event::new(self.time, event.process(), y);
if let Some(e) = res.allocate_or_enqueue(request_event) {
self.future_events.push(Reverse(e))
}
}
Effect::Release(r) => {
let res = &mut self.resources[r];
let release_event = Event::new(self.time, event.process(), y);
if let Some(e) = res.release_and_schedule_next(release_event.clone()) {
self.future_events.push(Reverse(e));
}
self.future_events.push(Reverse(release_event));
}
Effect::Wait => {}
Effect::Trace => {
let e = Event::new(self.time, event.process(), y);
self.future_events.push(Reverse(e));
}
Effect::Push(s) => {
let store = &mut self.stores[s];
let request_event = Event::new(self.time, event.process(), y);
store.push_or_enqueue_and_schedule_next(
request_event,
&mut self.future_events_buffer,
);
self.future_events
.extend(self.future_events_buffer.drain(..).map(Reverse));
}
Effect::Pull(s) => {
let store = &mut self.stores[s];
let request_event = Event::new(self.time, event.process(), y);
store.pull_or_enqueue_and_schedule_next(
request_event,
&mut self.future_events_buffer,
);
self.future_events
.extend(self.future_events_buffer.drain(..).map(Reverse));
}
}
}
CoroutineState::Complete(_) => {
self.processes[event.process()].take();
}
}
}
}
pub fn run(mut self, until: EndCondition) -> Simulation<T> {
while !self.check_ending_condition(&until) {
self.step();
}
self
}
fn check_ending_condition(&self, ending_condition: &EndCondition) -> bool {
match &ending_condition {
EndCondition::Time(t) => self.time >= *t,
EndCondition::NoEvents => self.future_events.is_empty(),
EndCondition::NSteps(n) => self.steps == *n,
}
}
}
impl<T> SimContext<T> {
pub fn time(&self) -> f64 {
self.time
}
pub fn state(&self) -> &T {
&self.state
}
}
impl<T> Event<T> {
pub fn new(time: f64, process: ProcessId, state: T) -> Event<T> {
Event {
time,
process,
state,
}
}
pub fn time(&self) -> f64 {
self.time
}
pub fn set_time(&mut self, time: f64) {
self.time = time;
}
pub fn process(&self) -> ProcessId {
self.process
}
pub fn set_process(&mut self, process: ProcessId) {
self.process = process;
}
pub fn state(&self) -> &T {
&self.state
}
pub fn state_mut(&mut self) -> &mut T {
&mut self.state
}
pub fn set_state(&mut self, state: T) {
self.state = state;
}
}
impl<T: SimState> Event<T> {
pub fn effect(&self) -> Effect {
self.state.get_effect()
}
pub fn set_effect(&mut self, effect: Effect) {
self.state.set_effect(effect)
}
}
impl<T: SimState + Clone> Default for Simulation<T> {
fn default() -> Self {
Simulation::<T> {
time: 0.0,
steps: 0,
processes: Vec::default(),
future_events: BinaryHeap::default(),
processed_events: Vec::default(),
resources: Vec::default(),
stores: Vec::default(),
future_events_buffer: Vec::default(),
}
}
}
impl<T> PartialEq for Event<T> {
fn eq(&self, other: &Event<T>) -> bool {
self.time == other.time
}
}
impl<T> Eq for Event<T> {}
impl<T> PartialOrd for Event<T> {
#[allow(clippy::incorrect_partial_ord_impl_on_ord_type)]
fn partial_cmp(&self, other: &Event<T>) -> Option<Ordering> {
self.time.partial_cmp(&other.time)
}
}
impl<T> Ord for Event<T> {
fn cmp(&self, other: &Event<T>) -> Ordering {
match self.time.partial_cmp(&other.time) {
Some(o) => o,
None => panic!("Event time was uncomparable. Maybe a NaN"),
}
}
}
impl SimState for Effect {
fn get_effect(&self) -> Effect {
*self
}
fn set_effect(&mut self, e: Effect) {
*self = e;
}
fn should_log(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
use crate::{Effect, Simulation};
let mut s = Simulation::new();
let p = s.create_process(Box::new(|_| {
let mut a = 0.0;
loop {
a += 1.0;
yield Effect::TimeOut(a);
}
}));
s.schedule_event(0.0, p, Effect::TimeOut(0.));
s.step();
s.step();
assert_eq!(s.time(), 1.0);
s.step();
assert_eq!(s.time(), 3.0);
s.step();
assert_eq!(s.time(), 6.0);
}
#[test]
fn run() {
use crate::{Effect, EndCondition, Simulation};
let mut s = Simulation::new();
let p = s.create_process(Box::new(|_| {
let tik = 0.7;
loop {
println!("tik");
yield Effect::TimeOut(tik);
}
}));
s.schedule_event(0.0, p, Effect::TimeOut(0.));
let s = s.run(EndCondition::Time(10.0));
println!("{}", s.time());
assert!(s.time() >= 10.0);
}
#[test]
fn resource() {
use crate::resources::SimpleResource;
use crate::{Effect, EndCondition::NoEvents, Simulation};
let mut s = Simulation::new();
let r = s.create_resource(Box::new(SimpleResource::new(1)));
let p1 = s.create_process(Box::new(move |_| {
yield Effect::Request(r);
yield Effect::TimeOut(7.0);
yield Effect::Release(r);
}));
let p2 = s.create_process(Box::new(move |_| {
yield Effect::Request(r);
yield Effect::TimeOut(3.0);
yield Effect::Release(r);
}));
s.schedule_event(0.0, p1, Effect::TimeOut(0.));
s.schedule_event(2.0, p2, Effect::TimeOut(2.));
let s = s.run(NoEvents);
println!("{:?}", s.processed_events());
assert_eq!(s.time(), 10.0);
}
#[test]
fn store() {
use crate::resources::SimpleStore;
use crate::{Effect, EndCondition::NoEvents, Simulation};
let mut sim = Simulation::new();
let store = sim.create_store(Box::new(SimpleStore::new(1)));
let p1 = sim.create_process(Box::new(move |_| {
yield Effect::Pull(store);
yield Effect::TimeOut(7.0);
yield Effect::Pull(store);
}));
let p2 = sim.create_process(Box::new(move |_| {
yield Effect::Push(store);
yield Effect::TimeOut(3.0);
yield Effect::Push(store);
}));
sim.schedule_event(0.0, p1, Effect::TimeOut(0.));
sim.schedule_event(2.0, p2, Effect::TimeOut(2.));
let s = sim.run(NoEvents);
println!("{:?}", s.processed_events());
assert_eq!(s.time(), 9.0);
}
}