Events, Timers, and Signals
Most models can be written with process blocking calls. Explicit events, process timers, and signal handling are useful when a model needs direct wakeups, cancellation, deadlines, or scheduled callbacks.
Explicit events
Declare a sim.Event field and bind a class-declared @sim.event
callback:
class Clinic(sim.Model):
close_shift: sim.Event
arrivals: sim.Processes
closed: sim.State
@sim.event(field="close_shift")
def on_close_shift(self: "Clinic"):
self.closed = 1
sim.stop(self.arrivals[0], 0)
@sim.process(field="arrivals")
def arrival_loop(self: "Clinic"):
while True:
sim.hold(1.0)
@sim.process
def supervisor(self: "Clinic"):
self.close_shift.schedule(480.0)
sim.suspend()
model = Clinic("clinic")
env.<event>.schedule() uses a delay from the current time.
.schedule_at() uses an absolute simulation time. Both return a
scheduled-instance handle with its own methods: .cancel(),
.reschedule(), .reprioritize(), .scheduled(), .time(),
.priority(), and .wait_event(). When two events are due at the exact
same simulated time, priority= (default 0) decides which fires first,
ties going to whichever was scheduled first; see Priority: Processes, Resources, Events, and Queues for how
this compares to process, resource/pool, and priority-queue priority.
Waiting on scheduled events
An event can be used as a deadline that another process waits for:
class Clinic(sim.Model):
close_shift: sim.Event
closed: sim.State
@sim.event(field="close_shift")
def on_close_shift(self: "Clinic"):
self.closed = 1
@sim.process
def reminder(self: "Clinic"):
handle = self.close_shift.schedule(480.0)
sig = handle.wait_event()
if sig == sim.SUCCESS:
self.closed = 1
If the event is cancelled before it fires, .wait_event() returns a
non-success signal. Check the signal when cancellation changes the model path.
Process timers
Timers wake a specific process. They are a natural fit for impatience, timeouts, appointment no-shows, and retry deadlines:
TIMER_PATIENCE = 17
class Clinic(sim.Model):
@sim.process(spawnable=True)
def patient(self: "Clinic", p: Patient):
me = sim.current()
sim.timer_set(me, p.patience, TIMER_PATIENCE)
sig = sim.suspend()
if sig == TIMER_PATIENCE:
# The patient waited too long.
return
sim.timers_clear(me)
# The patient was resumed by service before the timer fired.
sim.timer_set() clears existing timers before adding one. sim.timer_add()
adds another independent timer. sim.timer_cancel() cancels one timer handle,
and sim.timers_clear() clears all timers for a process.
Signals and cleanup
Blocking calls return signals. sim.SUCCESS means the operation completed
normally. Other values can indicate timeout, interruption, stop, cancellation,
or preemption:
import cimba.random as random
sig = env.doctor.acquire()
if sig != sim.SUCCESS:
return
try:
sig = sim.hold(random.exponential(env.mean_service))
if sig == sim.SUCCESS:
env.completed += 1
finally:
if env.doctor.held(sim.current()):
env.doctor.release()
Treat every blocking call as a possible handoff point. Another process may interrupt this process, stop it, preempt held capacity, or resume it with a domain-specific signal before it runs again.
Use explicit events and timers when they make the model rule clearer. If a
normal sim.hold(), queue operation, resource acquire, or condition wait
expresses the rule directly, prefer the simpler blocking operation.
For process fundamentals, see Processes and Simulated Time.