Architectural Design
Simulation Engine
How Days forks the Nexosim discrete-event simulation runtime.
Days builds on nexosim, which provides:
- Models: stateful actors (
impl Model) with async message handlers. - Mailboxes: bounded queues for messages delivered to a model.
- Ports: typed outputs (
Output<T>) that can be connected to a method on another model. - Event scheduling: models can schedule one-shot or periodic events in the future.
What a model looks like
Most core components follow this pattern:
- a method like
packet_received(...)(orframe_received(...)) is connected to an upstreamOutput. - the handler updates local state and schedules future work using
cx.schedule_event(...). - output is sent via
self.output.send(msg).await.
Example kinds of models in this repo:
PacketSwitch(src/switches/switch.rs)- schedulers such as
Port,DRRServer,WFQServer(src/schedulers/) - flow endpoints such as
DistPacketSource,TCPPacketSink(src/flows/)
Time
nexosim maintains an internal clock (exposed as cx.time() / sim.time()), represented as a MonotonicTime.
Days typically works in seconds as f64:
- handlers read “now” from
Packet.time(orLinkFrame.time()), - handlers schedule future work with
Duration::from_secs_f64(delta), - some code converts from
cx.time()tof64only when needed (often gated by thetestfeature).
This “time-carrying message” design is central to performance; see /docs/architecture/time-concurrency.
Single-threaded vs multi-threaded runs
Days chooses the runtime based on config:
threading = "single"uses a single-threaded runtime (lower scheduling overhead).threading = "multiple"uses a multi-threaded runtime sized to CPU cores by default (override withnum_threads).
The choice is made in Topology::new (src/topos/topo.rs) using SimInit::with_num_threads(...).
Wiring models together
Days constructs the full model graph in two stages:
- Create
Mailbox<T>for every model and attachOutput<T>ports. - Register models into the simulation initializer (
SimInit) and callinit(...)to produce aSimulation.
The entrypoint Topology::run (src/topos/topo.rs) performs this wiring and then runs:
sim.step_until(Duration::from_secs_f64(duration))