#simulation #event #descrete

simrs

General purpose descrete event simulation library

4 releases

new 0.2.0 Jun 21, 2022
0.1.2 Mar 28, 2021
0.1.1 Dec 4, 2020
0.1.0 Nov 9, 2020

#23 in Simulation

Download history 9/week @ 2022-03-14 9/week @ 2022-03-21 18/week @ 2022-04-25 26/week @ 2022-05-02 3/week @ 2022-05-09 52/week @ 2022-05-16 16/week @ 2022-05-23 22/week @ 2022-05-30 9/week @ 2022-06-06 2/week @ 2022-06-13 27/week @ 2022-06-20

66 downloads per month

MIT/Apache

49KB
1K SLoC

SimRs

This is an experimental discrete event simulation library I use in my research project. I'm not sure at the moment how much value it has for others but I found the things I did here interesting, so maybe someone else will as well.

Visit https://docs.rs/simrs/0.1.2/simrs/ for more information and documentation.

If you have any comments, suggestions, or criticism, don't hesitate to reach out by either contacting me directly or opening an issue.


lib.rs:

General purpose simulation library that provides the mechanisms such as: scheduler, state, queues, etc.

NOTE: This is all experimental right now.

The key public types are [State], [Scheduler], [Components], and [Simulation]. These, along with user-defined simulation components (structs implementing the [Component] trait), are the simulation building blocks. Let's first explain each of the above, and then look at examples.

State

A simulation must have the ability to mutate its state. This functionality is fully delegated to the [State] struct. It can store, remove, and modify values of arbitrary types T: 'static. It also allows us to create queues that can be used to move data between components.

Value Store

[State] exposes several simple functions to insert, access, modify, and remove values. Existing values are manipulated using special type-safe keys that are generated and returned when inserting the values.

# use simrs::State;
let mut state = State::default();
let key = state.insert(7);
assert_eq!(state.remove(key), Some(7));

Note that the following will fail to compile because of incompatible types:

# use simrs::State;
let mut state = State::default();
let int_key = state.insert(7);
let str_key = state.insert("str");
let v: i32 = state.get(str_key);

Queues

Queues work very similar to storing values but have a different user-facing interface. The access is also done through a key type. However, a different type [QueueId] is used for clarity.

# use simrs::{State, Fifo, Queue};
let mut state = State::default();
let queue_id = state.add_queue(Fifo::default());
state.send(queue_id, 1);
assert_eq!(state.len(queue_id), 1);
assert_eq!(state.recv(queue_id), Some(1));
assert_eq!(state.recv(queue_id), None);

Additionally, a bounded queue is available, which will return an error if the size reached the capacity.

# use simrs::{State, Fifo, Queue};
let mut state = State::default();
let queue_capacity = 1;
let queue_id = state.add_queue(Fifo::bounded(queue_capacity));
assert!(state.send(queue_id, 1).is_ok());
assert_eq!(state.len(queue_id), 1);
assert!(!state.send(queue_id, 2).is_ok());
assert_eq!(state.len(queue_id), 1);

Components

The [Components] structure is a container for all registered components. Similarly to values and queues in the state, components are identified by [ComponentId].

# use simrs::{Components, Component, State, Scheduler, ComponentId};
struct SomeComponent {
    // ...
}
#[derive(Debug)]
enum SomeEvent {
    A,
    B,
    C,
}
# impl SomeComponent {
#     fn new() -> Self {
#         SomeComponent {}
#     }
# }
impl Component for SomeComponent {
    type Event = SomeEvent;
    fn process_event(
        &self,
        self_id: ComponentId<Self::Event>,
        event: &Self::Event,
        scheduler: &mut Scheduler,
        state: &mut State,
    ) {
        // Do some work...
    }
}

# fn main() {
let mut components = Components::default();
let component_id = components.add_component(SomeComponent::new());
# }

Scheduler

The scheduler's main functionality is to keep track of the simulation time and the future events. Events are scheduled to run on a specific component at a specified time interval. Because the events are type-erased, it's up to the component to downcast the event. To make it easy, each component gets a blanket implementation of an internal trait that does that automatically. It is all encapsulated in the Components container, as shown in the below example:

# use simrs::{Components, Component, State, Scheduler, ComponentId};
# use std::time::Duration;
# struct SomeComponent {
#     // ...
# }
# #[derive(Debug)]
# enum SomeEvent {
#     A,
#     B,
#     C,
# }
# impl SomeComponent {
#     fn new() -> Self {
#         SomeComponent {}
#     }
# }
# impl Component for SomeComponent {
#     type Event = SomeEvent;
#     fn process_event(
#         &self,
#         self_id: ComponentId<Self::Event>,
#         event: &Self::Event,
#         scheduler: &mut Scheduler,
#         state: &mut State,
#     ) {
#         // Do some work...
#     }
# }
# fn main() {
let mut components = Components::default();
let mut scheduler = Scheduler::default();
let mut state = State::default();
let component_id = components.add_component(SomeComponent::new());
scheduler.schedule(
    Duration::from_secs(1), // schedule 1 second from now
    component_id,
    SomeEvent::A,
);
let event_entry = scheduler.pop().unwrap();
components.process_event_entry(event_entry, &mut scheduler, &mut state);
# }

Simulation

[Simulation] takes aggregates everything under one structure and provides some additional functions. See the example below.

Example

# use simrs::{Simulation, State, Scheduler, Components, ComponentId, Component, QueueId, Key, Fifo, Executor};
# use std::time::Duration;

#[derive(Debug)]
struct Product;

struct Producer {
    outgoing: QueueId<Fifo<Product>>,
    consumer: ComponentId<ConsumerEvent>,
    produced_count: Key<usize>,
}

struct Consumer {
    incoming: QueueId<Fifo<Product>>,
    working_on: Key<Option<Product>>,
}

#[derive(Debug)]
struct ProducerEvent;

#[derive(Debug)]
enum ConsumerEvent {
    Received,
    Finished,
}

impl Producer {
    fn produce(&self) -> Product {
        Product
    }
    fn interval(&self) -> Duration {
        Duration::from_secs(1)
    }
}

impl Consumer {
    fn interval(&self) -> Duration {
        Duration::from_secs(1)
    }
    fn log(&self, product: Product) {
        println!("{:?}", product)
    }
}

impl Component for Producer {
    type Event = ProducerEvent;

    fn process_event(
        &self,
        self_id: ComponentId<ProducerEvent>,
        _event: &ProducerEvent,
        scheduler: &mut Scheduler,
        state: &mut State,
    ) {
        let count = *state.get(self.produced_count).unwrap();
        if count < 10 {
            let _ = state.send(self.outgoing, self.produce());
            scheduler.schedule(self.interval(), self_id, ProducerEvent);
            scheduler.schedule(Duration::default(), self.consumer, ConsumerEvent::Received);
            *state.get_mut(self.produced_count).unwrap() = count + 1;
        }
    }
}

impl Component for Consumer {
    type Event = ConsumerEvent;

    fn process_event(
        &self,
        self_id: ComponentId<ConsumerEvent>,
        event: &ConsumerEvent,
        scheduler: &mut Scheduler,
        state: &mut State,
    ) {
        let busy = state.get(self.working_on).is_some();
        match event {
            ConsumerEvent::Received => {
                if busy {
                    if let Some(product) = state.recv(self.incoming) {
                        state.get_mut(self.working_on).map(|w| *w = Some(product));
                        scheduler.schedule(self.interval(), self_id, ConsumerEvent::Finished);
                    }
                }
            }
            ConsumerEvent::Finished => {
                let product = state.get_mut(self.working_on).unwrap().take().unwrap();
                self.log(product);
                if state.len(self.incoming) > 0 {
                    scheduler.schedule(Duration::default(), self_id, ConsumerEvent::Received);
                }
            }
        }
    }
}

fn main() {
    let mut simulation = Simulation::default();
    let queue = simulation.add_queue(Fifo::default());
    let working_on = simulation.state.insert::<Option<Product>>(None);
    let consumer = simulation.add_component(Consumer {
        incoming: queue,
        working_on,
    });
    let produced_count = simulation.state.insert(0_usize);
    let producer = simulation.add_component(Producer {
        outgoing: queue,
        consumer,
        produced_count,
    });
    simulation.schedule(Duration::new(0, 0), producer, ProducerEvent);
    // simulation.schedule(Duration::new(0, 0), consumer, ProducerEvent);
    // The above would fail with:                         ^^^^^^^^^^^^^ expected enum `ConsumerEvent`, found struct `ProducerEvent`
    simulation.execute(Executor::unbound().side_effect(|sim| {
        println!("{:?}", sim.scheduler.time());
    }));
}

No runtime deps