///|
/// Deterministic discrete-event simulation helpers.
///
/// The simulation layer is intentionally integer-based: it can replay queue,
/// service, and maintenance scenarios in CI without wall-clock randomness.
/// Events are ordered by time and then sequence number, making traces stable
/// even when several events share a timestamp.
pub enum SimulationEventKind {
Arrival
ServiceStart
ServiceFinish
Maintenance
CustomEvent
}
///|
/// Return the custom-event constructor for callers building integrations.
pub fn custom_event_kind() -> SimulationEventKind {
CustomEvent
}
///|
/// A scheduled event.
pub struct SimulationEvent {
time : Int
sequence : Int
entity : Int
resource : Int
kind : SimulationEventKind
payload : Int
}
///|
/// Construct an event.
pub fn simulation_event(
time : Int,
sequence : Int,
entity : Int,
resource : Int,
kind : SimulationEventKind,
payload : Int,
) -> SimulationEvent {
{ time, sequence, entity, resource, kind, payload }
}
///|
/// Return a stable kind label.
pub fn SimulationEvent::kind_name(self : SimulationEvent) -> String {
match self.kind {
Arrival => "arrival"
ServiceStart => "service-start"
ServiceFinish => "service-finish"
Maintenance => "maintenance"
CustomEvent => "custom"
}
}
///|
/// Return whether the first event precedes the second.
fn event_before(left : SimulationEvent, right : SimulationEvent) -> Bool {
left.time < right.time ||
(left.time == right.time && left.sequence < right.sequence)
}
///|
/// A sorted event queue.
pub struct EventQueue {
events : Array[SimulationEvent]
}
///|
/// Create an empty queue.
pub fn event_queue() -> EventQueue {
{ events: [] }
}
///|
/// Return queue length.
pub fn EventQueue::length(self : EventQueue) -> Int {
self.events.length()
}
///|
/// Add an event in stable order.
pub fn EventQueue::push(self : EventQueue, event : SimulationEvent) -> Bool {
let mut position = 0
while position < self.events.length() &&
event_before(self.events[position], event) {
position += 1
}
self.events.push(event)
let mut index = self.events.length() - 1
while index > position {
self.events[index] = self.events[index - 1]
index -= 1
}
self.events[position] = event
true
}
///|
/// Pop the earliest event.
pub fn EventQueue::pop(self : EventQueue) -> SimulationEvent? {
if self.events.length() == 0 {
return None
}
let result = self.events[0]
for index in 1.. SimulationEvent? {
if self.events.length() == 0 {
None
} else {
Some(self.events[0])
}
}
///|
/// Remove all queued events.
pub fn EventQueue::clear(self : EventQueue) -> Unit {
while self.events.length() > 0 {
ignore(self.events.pop())
}
}
///|
/// A simulated entity record.
pub struct SimulationEntity {
id : Int
arrival : Int
service : Int
priority : Int
}
///|
/// Create an entity.
pub fn simulation_entity(
id : Int,
arrival : Int,
service : Int,
priority : Int,
) -> SimulationEntity {
{
id,
arrival: if arrival < 0 {
0
} else {
arrival
},
service: if service < 0 {
0
} else {
service
},
priority,
}
}
///|
/// A single-server queue simulation.
pub struct QueueSimulation {
horizon : Int
entities : Array[SimulationEntity]
queue : EventQueue
trace : Array[SimulationEvent]
mut next_sequence : Int
mut server_free : Int
mut total_wait : Int
mut completed : Int
}
///|
/// Create a queue simulation and seed arrivals.
pub fn queue_simulation(
horizon : Int,
entities : Array[SimulationEntity],
) -> QueueSimulation? {
if horizon < 0 {
return None
}
for index, entity in entities {
if entity.id != index || entity.arrival > horizon {
return None
}
}
let simulation = {
horizon,
entities: entities.copy(),
queue: event_queue(),
trace: [],
next_sequence: 0,
server_free: 0,
total_wait: 0,
completed: 0,
}
for entity in entities {
ignore(
simulation.schedule(entity.arrival, entity.id, 0, Arrival, entity.service),
)
}
Some(simulation)
}
///|
/// Schedule an event.
pub fn QueueSimulation::schedule(
self : QueueSimulation,
time : Int,
entity : Int,
resource : Int,
kind : SimulationEventKind,
payload : Int,
) -> Bool {
if time < 0 || time > self.horizon {
return false
}
let event = simulation_event(
time,
self.next_sequence,
entity,
resource,
kind,
payload,
)
self.next_sequence += 1
ignore(self.queue.push(event))
true
}
///|
/// Process the next queued event.
pub fn QueueSimulation::step(self : QueueSimulation) -> SimulationEvent? {
match self.queue.pop() {
None => None
Some(event) => {
self.trace.push(event)
match event.kind {
Arrival => {
let start = if self.server_free > event.time {
self.server_free
} else {
event.time
}
self.total_wait += start - event.time
ignore(
self.schedule(
start,
event.entity,
event.resource,
ServiceStart,
event.payload,
),
)
}
ServiceStart => {
let finish = event.time + event.payload
self.server_free = finish
if finish <= self.horizon {
ignore(
self.schedule(
finish,
event.entity,
event.resource,
ServiceFinish,
event.payload,
),
)
}
}
ServiceFinish => self.completed += 1
Maintenance =>
self.server_free = if self.server_free < event.time + event.payload {
event.time + event.payload
} else {
self.server_free
}
CustomEvent => ()
}
Some(event)
}
}
}
///|
/// Run until no event remains or the horizon is reached.
pub fn QueueSimulation::run(self : QueueSimulation) -> Int {
let mut steps = 0
while self.queue.length() > 0 {
match self.step() {
Some(_) => steps += 1
None => break
}
}
steps
}
///|
/// Return current simulation time.
pub fn QueueSimulation::current_time(self : QueueSimulation) -> Int {
if self.trace.length() == 0 {
0
} else {
self.trace[self.trace.length() - 1].time
}
}
///|
/// Return completed entity count.
pub fn QueueSimulation::completed(self : QueueSimulation) -> Int {
self.completed
}
///|
/// Return queued entity count.
pub fn QueueSimulation::pending(self : QueueSimulation) -> Int {
self.entities.length() - self.completed
}
///|
/// Return total waiting time.
pub fn QueueSimulation::total_wait(self : QueueSimulation) -> Int {
self.total_wait
}
///|
/// Return average waiting time.
pub fn QueueSimulation::average_wait(self : QueueSimulation) -> Int {
if self.completed == 0 {
0
} else {
self.total_wait / self.completed
}
}
///|
/// Return the event trace.
pub fn QueueSimulation::trace(self : QueueSimulation) -> Array[SimulationEvent] {
self.trace.copy()
}
///|
/// Return all events for one entity.
pub fn QueueSimulation::entity_trace(
self : QueueSimulation,
entity : Int,
) -> Array[SimulationEvent] {
let result : Array[SimulationEvent] = []
for event in self.trace {
if event.entity == entity {
result.push(event)
}
}
result
}
///|
/// Schedule a maintenance block on the server.
pub fn QueueSimulation::schedule_maintenance(
self : QueueSimulation,
start : Int,
duration : Int,
) -> Bool {
self.schedule(
start,
-1,
0,
Maintenance,
if duration < 0 {
0
} else {
duration
},
)
}
///|
/// Return a deterministic utilization percentage.
pub fn QueueSimulation::utilization(self : QueueSimulation) -> Int {
if self.horizon == 0 {
return 0
}
self.server_free * 100 / self.horizon
}
///|
/// Return all arrivals in id order.
pub fn QueueSimulation::arrivals(self : QueueSimulation) -> Array[Int] {
self.entities.map(entity => entity.arrival)
}
///|
/// Return all service durations in id order.
pub fn QueueSimulation::service_times(self : QueueSimulation) -> Array[Int] {
self.entities.map(entity => entity.service)
}
///|
/// Return a stable simulation signature.
pub fn QueueSimulation::signature(self : QueueSimulation) -> Int {
let mut result = self.horizon * 31 + self.entities.length()
for event in self.trace {
result = result * 37 + event.time * 3 + event.entity * 5 + event.payload
}
result
}
///|
/// Return a trace summary.
pub fn QueueSimulation::describe(self : QueueSimulation) -> String {
"entities=\{self.entities.length()}, completed=\{self.completed}, wait=\{self.total_wait}, utilization=\{self.utilization()}%"
}
///|
/// Build a deterministic burst-arrival queue.
pub fn burst_queue(
count : Int,
spacing : Int,
service : Int,
horizon : Int,
) -> QueueSimulation? {
let entities : Array[SimulationEntity] = []
for id in 0.. Int {
let mut arrivals = 0
let mut finishes = 0
let mut maximum = 0
for event in simulation.trace {
match event.kind {
Arrival => arrivals += 1
ServiceFinish => finishes += 1
_ => ()
}
if arrivals - finishes > maximum {
maximum = arrivals - finishes
}
}
maximum
}
///|
/// Return whether every event timestamp is nondecreasing.
pub fn trace_is_ordered(simulation : QueueSimulation) -> Bool {
for index in 1..