///|
/// Deterministic simulation state.
pub(all) struct Sim {
mut now : Int
mut next_id : Int
mut events : Array[ScheduledEvent]
mut trace_entries : Array[TraceEntry]
rng : Rng
metrics : Metrics
}
///|
pub(all) enum RunStep {
Executed(ScheduledEvent)
Idle
}
///|
pub(all) enum RunStopReason {
Idle
StepLimit
TickLimit
}
///|
pub(all) struct RunReport {
steps : Int
final_tick : Int
pending : Int
reason : RunStopReason
}
///|
pub fn Sim::new(seed? : UInt64 = 1UL) -> Sim {
{
now: 0,
next_id: 1,
events: [],
trace_entries: [],
rng: Rng::new(seed),
metrics: Metrics::new(),
}
}
///|
pub fn Sim::time(self : Sim) -> Int {
self.now
}
///|
pub fn Sim::tick(self : Sim) -> Tick {
tick(self.now)
}
///|
pub fn Sim::pending_count(self : Sim) -> Int {
self.events.length()
}
///|
pub fn Sim::schedule_at_tick(
self : Sim,
at : Tick,
name : String,
priority? : Int = 0,
) -> EventId {
event_id(self.schedule_at(at.to_int(), name, priority~))
}
///|
pub fn Sim::schedule_at(
self : Sim,
tick : Int,
name : String,
priority? : Int = 0,
) -> Int {
let scheduled_tick = if tick < self.now { self.now } else { tick }
let id = self.next_id
self.next_id += 1
self.push_event(ScheduledEvent::new(id, scheduled_tick, name, priority~))
self.record(id, "schedule", name)
id
}
///|
pub fn Sim::schedule_after_duration(
self : Sim,
delay : Duration,
name : String,
priority? : Int = 0,
) -> EventId {
event_id(self.schedule_after(delay.to_int(), name, priority~))
}
///|
pub fn Sim::schedule_after(
self : Sim,
delay : Int,
name : String,
priority? : Int = 0,
) -> Int {
let normalized_delay = if delay < 0 { 0 } else { delay }
self.schedule_at(self.now + normalized_delay, name, priority~)
}
///|
pub fn Sim::schedule_repeating(
self : Sim,
start_after : Int,
every : Int,
times : Int,
name : String,
priority? : Int = 0,
) -> Int {
let start = self.now + (if start_after < 0 { 0 } else { start_after })
let id = self.next_id
self.next_id += 1
self.push_event(
ScheduledEvent::repeating(id, start, name, every, times, priority~),
)
self.record(id, "schedule.repeat", name)
id
}
///|
pub fn Sim::cancel(self : Sim, event_id : Int) -> Bool {
let mut i = 0
while i < self.events.length() {
if self.events[i].id == event_id && !self.events[i].cancelled {
let event = self.events.remove(i)
self.rebuild_event_heap()
self.record(event_id, "cancel", event.name)
return true
}
i += 1
}
false
}
///|
pub fn Sim::run_next(self : Sim) -> ScheduledEvent? {
match self.pop_next_event() {
None => None
Some(event) => {
self.now = event.tick
self.record(event.id, "execute", event.name)
self.metrics.inc("events_executed")
if event.is_repeating() {
let next = event.next_repeat()
self.push_event(next)
self.record(next.id, "reschedule.repeat", next.name)
}
Some(event)
}
}
}
///|
pub fn Sim::step(self : Sim) -> RunStep {
match self.run_next() {
Some(event) => Executed(event)
None => Idle
}
}
///|
pub fn Sim::run_until_idle(self : Sim, max_steps? : Int = 100000) -> Int {
let mut steps = 0
while steps < max_steps {
match self.run_next() {
None => return steps
Some(_) => steps += 1
}
}
steps
}
///|
pub fn Sim::run_report_until_idle(
self : Sim,
max_steps? : Int = 100000,
) -> RunReport {
let steps = self.run_until_idle(max_steps~)
{
steps,
final_tick: self.now,
pending: self.pending_count(),
reason: if self.pending_count() == 0 {
RunStopReason::Idle
} else {
StepLimit
},
}
}
///|
pub fn Sim::run_until_tick(
self : Sim,
tick_limit : Int,
max_steps? : Int = 100000,
) -> RunReport {
let mut steps = 0
while steps < max_steps {
match self.peek_next_event() {
None =>
return {
steps,
final_tick: self.now,
pending: self.pending_count(),
reason: RunStopReason::Idle,
}
Some(event) =>
if event.tick > tick_limit {
return {
steps,
final_tick: self.now,
pending: self.pending_count(),
reason: RunStopReason::TickLimit,
}
} else {
ignore(self.run_next())
steps += 1
}
}
}
{
steps,
final_tick: self.now,
pending: self.pending_count(),
reason: StepLimit,
}
}
///|
pub fn Sim::next_int(self : Sim, bound : Int) -> Int {
let value = self.rng.next_int(bound)
self.record(0, "rng.int", value.to_string())
value
}
///|
pub fn Sim::next_range(self : Sim, low : Int, high : Int) -> Int {
let value = self.rng.next_range(low, high)
self.record(
0,
"rng.range",
low.to_string() + ".." + high.to_string() + "=" + value.to_string(),
)
value
}
///|
pub fn[T] Sim::choose(self : Sim, label : String, items : Array[T]) -> T? {
let index = if items.length() == 0 {
-1
} else {
self.rng.next_int(items.length())
}
self.record(0, "rng.choose", label + "=" + index.to_string())
if index < 0 {
None
} else {
Some(items[index])
}
}
///|
pub fn[T] Sim::shuffle(
self : Sim,
label : String,
items : Array[T],
) -> Array[T] {
let result = self.rng.shuffle(items)
self.record(0, "rng.shuffle", label + "#" + items.length().to_string())
result
}
///|
pub fn Sim::choose_weighted(
self : Sim,
label : String,
choices : Array[WeightedChoice],
) -> String? {
let value = self.rng.choose_weighted(choices)
match value {
None => self.record(0, "rng.weighted", label + "=None")
Some(v) => self.record(0, "rng.weighted", label + "=" + v)
}
value
}
///|
pub fn Sim::trace(self : Sim) -> Array[TraceEntry] {
self.trace_entries.copy()
}
///|
pub fn Sim::trace_text(self : Sim) -> String {
trace_to_text(self.trace_entries)
}
///|
pub fn Sim::digest(self : Sim) -> UInt64 {
trace_digest(self.trace_entries)
}
///|
pub fn Sim::metrics(self : Sim) -> Metrics {
self.metrics
}
///|
pub fn Sim::inc_counter(self : Sim, name : String, delta? : Int = 1) -> Unit {
self.metrics.inc(name, delta~)
self.record(0, "metric.counter", name + "+=" + delta.to_string())
}
///|
pub fn Sim::set_gauge(self : Sim, name : String, value : Int) -> Unit {
self.metrics.set_gauge(name, value)
self.record(0, "metric.gauge", name + "=" + value.to_string())
}
///|
pub fn Sim::sample(self : Sim, name : String, value : Int) -> Unit {
self.metrics.sample(name, value)
self.record(0, "metric.sample", name + "=" + value.to_string())
}
///|
pub fn Sim::record(
self : Sim,
event_id : Int,
kind : String,
detail : String,
) -> Unit {
self.trace_entries.push(trace_entry(self.now, event_id, kind, detail))
}
///|
fn Sim::push_event(self : Sim, event : ScheduledEvent) -> Unit {
self.events.push(event)
let mut index = self.events.length() - 1
let mut bubbling = true
while index > 0 && bubbling {
let parent = (index - 1) / 2
if compare_event(self.events[index], self.events[parent]) < 0 {
let current = self.events[index]
self.events[index] = self.events[parent]
self.events[parent] = current
index = parent
} else {
bubbling = false
}
}
}
///|
/// Removes the heap root without observing cancellation. Cancellation remains
/// a stable marker so snapshots preserve both pending work and cancel state.
fn Sim::pop_heap_root(self : Sim) -> ScheduledEvent? {
if self.events.length() == 0 {
return None
}
let root = self.events[0]
let last = self.events.remove(self.events.length() - 1)
if self.events.length() > 0 {
self.events[0] = last
let mut index = 0
let mut settling = true
while settling {
let left = index * 2 + 1
if left >= self.events.length() {
settling = false
} else {
let right = left + 1
let child = if right < self.events.length() &&
compare_event(self.events[right], self.events[left]) < 0 {
right
} else {
left
}
if compare_event(self.events[child], self.events[index]) < 0 {
let current = self.events[index]
self.events[index] = self.events[child]
self.events[child] = current
index = child
} else {
settling = false
}
}
}
}
Some(root)
}
///|
fn Sim::peek_next_event(self : Sim) -> ScheduledEvent? {
if self.events.length() == 0 {
None
} else {
Some(self.events[0])
}
}
///|
fn Sim::pop_next_event(self : Sim) -> ScheduledEvent? {
match self.peek_next_event() {
None => None
Some(_) => self.pop_heap_root()
}
}
///|
fn Sim::rebuild_event_heap(self : Sim) -> Unit {
let mut parent = self.events.length() / 2
while parent > 0 {
parent -= 1
self.sift_down_event(parent)
}
}
///|
fn Sim::sift_down_event(self : Sim, start : Int) -> Unit {
let mut index = start
let mut settling = true
while settling {
let left = index * 2 + 1
if left >= self.events.length() {
settling = false
} else {
let right = left + 1
let mut child = left
if right < self.events.length() &&
compare_event(self.events[right], self.events[left]) < 0 {
child = right
}
if compare_event(self.events[child], self.events[index]) < 0 {
let current = self.events[index]
self.events[index] = self.events[child]
self.events[child] = current
index = child
} else {
settling = false
}
}
}
}