4 releases
0.2.0 | Jun 21, 2022 |
---|---|
0.1.2 | Mar 28, 2021 |
0.1.1 | Dec 4, 2020 |
0.1.0 | Nov 9, 2020 |
#144 in Simulation
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.
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:
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.
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.
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
.
struct SomeComponent {
// ...
}
#[derive(Debug)]
enum SomeEvent {
A,
B,
C,
}
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...
}
}
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:
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
#[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());
}));
}