4.2 KiB
The simulation layer
English | 简体中文
This file records the cross-crate decisions of the simulation layer. The design inside each crate is in its module documentation.
Composition and dependencies
apsis-event the event type and the event queue
apsis-sim-engine the discrete event engine
apsis-scenario scenario definition and text loading
apsis-output simulation output recording
apsis-sim the facade, which joins the four above
The dependency direction:
apsis-event -> apsis-sim-engine -> apsis-sim -> apsis-rs
apsis-scenario -----------------> apsis-sim
apsis-output -------------------> apsis-sim
apsis-sim also depends on the ops layer (apsis-scheduler,
apsis-schedule, apsis-task, apsis-resource), because it plans a
scenario and reads the plan. The remaining crates depend on foundation alone.
The order of events
Several events at the same instant must be delivered in a reproducible order, or the same input would give a different output and there would be nothing to cross-check against a reference implementation. The convention:
- Every event takes an increasing sequence number when it is scheduled.
- Events are ordered by time, and events at the same time by that number, which is the order they were scheduled in.
- An event that a handler schedules while handling another goes behind the events already queued for that instant.
apsis-event provides this order and the engine and the facade carry it
through. The queue assigns the numbers, so a caller cannot create two events
with the same one.
The origin of time
An event time is a number of seconds from the origin of the simulation and has nothing to do with a calendar; a scenario that needs one converts it above this layer.
The epoch of a scenario is where the clock starts. The times in a timeline
are relative to that origin and apsis-sim adds the epoch before
delivering. Without the offset the engine would move the clock backwards when
it delivered the first event.
A time limit is compared on the same absolute axis: an event later than the limit stays in the queue instead of running, so a caller can see what remained when the run ended. The clock stops at the last event it executed rather than at the limit.
The event limit is cumulative
The event limit in apsis-sim-engine bounds the total for the life of the
engine, not the count of a single run. Once the limit is reached a further
run executes nothing. The limit is therefore a bound that cannot be
circumvented, and a caller who wants the remaining events to run has to
raise it explicitly.
What a timeline means
apsis-sim expands a plan into start and end events on a timeline: every
placed task contributes one start and one end, as separate events, so the
engine can order them in time with everything else and the order of
overlapping tasks follows from the clock.
The timeline is the plan, not a physical model. What physical computation a handler runs when a start or end event fires is up to the handler; the facade only orders and records.
The scenario text format
apsis-scenario carries its own line oriented text format with no
dependency, for storing a scenario and loading it back. Parsing runs in three
passes, over the resources, the tasks and the precedence constraints, so a
require may name a resource defined later in the file and a precedence
may name tasks in any order. Every failure carries the line it came from.
The output of Scenario::to_text must parse back with Scenario::parse to
an equal scenario, so the text form is stable under a round trip, which a
test asserts.
A task with no window, whose two ends are infinite, cannot be written as text, so it is written as a finite interval that contains the task and is still usable once read back.
Determinism
A simulation result must be reproducible, because that is what makes a cross-check possible. Three things give it:
- the same-instant ordering rule of the event queue;
- the stable ordering the planner applies to tasks of equal weight, described in the ops layer;
- the recorder, which keeps the order of insertion, where
Trace::in_time_orderis a stable sort.
The integrate_sim example asserts that running one prepared simulation
twice produces the same trace.