$ show JuliaDynamics/ConcurrentSim.jl
ConcurrentSim.jl is a discrete-event process-oriented simulation framework for Julia, directly inspired by Python's SimPy. It provides a simulation engine where user-defined processes (implemented as coroutines via the ResumableFunctions.jl @resumable macro) yield events that are scheduled on a priority-ordered event heap. The framework offers a complete set of resource primitives for modeling limited-capacity congestion points: Resource (a lock-like container with capacity ≥1), Container (arbitrary capacity with continuous levels), Store (unordered collection with item filtering), QueueStore (FIFO), StackStore (LIFO), and DelayQueue (FIFO with configurable latency). Event operators (&, |) provide combinatorial AND/OR synchronization, and the framework supports both Float64 simulation time and wall-clock DateTime-based scheduling. The architecture is cleanly separated across modules: the Simulation environment manages the event heap, clock, and active process; a shared BaseEvent struct implements the callback mechanism; processes integrate with the event loop via a custom finite-state machine iterator from ResumableFunctions; resources build on a common trigger/dispatch abstraction in resources/base.jl. The codebase displays mature engineering practices: CI across Julia 1.10–1.12 plus nightly (Windows, macOS, Linux), automated static analysis via Aqua and JET, a 14-file test suite with doctests, a benchmark suite comparing old and new process implementations, and comprehensive documentation. It is the successor to SimJulia and one of the longest-lived Julia packages. The repository also contains three example notebooks, but these are legacy artifacts targeting Julia 0.6 (using deprecated APIs like put/release/srand) and should be considered historical rather than current usage guides.
The LLM identified 4 files worth highlighting for reuse:
Implements AND/OR event combination operators (via &, |, AllOf, AnyOf) that allow processes to synchronize on multiple events simultaneously. The Operator builds a state-value map of all subscribed events and schedules itself only when the predicate evaluates true, correctly handling already-processed events, exceptions, and late-arriving values. This is a self-contained, composable pattern usable in any event-driven system requiring multi-event synchronization, demonstrating non-trivial state tracking without leaking internal event details.
Core event scheduling engine. Events are stored in a DataStructures.PriorityQueue keyed by EventKey, with a custom isless that orders by (time, priority, id) ensuring deterministic FIFO behavior for simultaneous same-priority events. The step() function pops the earliest event, advances the simulation clock, marks the event processed, and dispatches callbacks. This is the canonical heartbeat of a discrete-event simulator, showing careful design of event state transitions and heap-based ordering that any event-driven system would need.
DelayQueue wraps a QueueStore and introduces a user-specified latency between put! and take! by launching an internal process that yields a timeout before forwarding each item to the underlying store. This cleanly demonstrates process-composition as a technique for adding temporal semantics (delays) to storage resources without duplicating store logic, and is arguably the cleanest pattern in the codebase for extending resource behavior.
Provides transparent bidirectional conversion between the simulation's Float64 time domain and Julia's DateTime/Period types. Simulation and run accept DateTime initial/target times, timeout accepts Period delays, and nowDatetime() exposes the current time as a DateTime. This small, self-contained adaptation layer allows simulations to operate in familiar calendar semantics while the core engine stays in epoch-milliseconds, a pattern reusable in any timed system needing multiple time representations.