[][src]Crate simrs

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:

This example deliberately fails to compile
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.new_queue();
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.new_bounded_queue(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

struct Product;

struct Producer {
    outgoing: QueueId<Product>,
}

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

#[derive(Debug)]
struct ProducerEvent;

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

impl Producer {
    fn produce(&self) -> Product { todo!() }
    fn interval(&self) -> std::time::Duration { todo!() }
}

impl Consumer {
    fn produce(&self) -> Product { todo!() }
    fn interval(&self) -> std::time::Duration { todo!() }
    fn log(&self, product: Product) { todo!() }
}

impl Component for Producer {
    type Event = ProducerEvent;
     
    fn process_event(
        &self,
        self_id: ComponentId<ProducerEvent>,
        _event: &ProducerEvent,
        scheduler: &mut Scheduler,
        state: &mut State,
    ) {
        state.send(self.outgoing, self.produce());
        scheduler.schedule(self.interval(), self_id, ProducerEvent);
    }
}

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_none();
        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
                        );
                }
            }
        }
    }
}

Structs

ClockRef

This struct exposes only immutable access to the simulation clock. The clock itself is owned by the scheduler, while others can obtain ClockRef to read the current simulation time.

ComponentId

A type-safe identifier of a component. This is an analogue of Key used specifically for components.

Components

Container holding type-erased components.

EventEntry

Entry type stored in the scheduler, including the event value, component ID, and the time when it is supposed to occur.

Key

A type-safe key used to fetch values from the value store.

QueueId

A type-safe identifier of a queue. This is an analogue of Key used specifically for queues.

Scheduler

Scheduler is used to keep the current time and information about the upcoming events.

Simulation

Simulation struct that puts different parts of the simulation together.

State

State of a simulation holding all queues and arbitrary values in a store value.

Traits

Component

Interface of a simulation component.