Skip to main content

waremax_core/
event.rs

1//! Simulation events for the discrete-event simulation
2
3use crate::{
4    BinId, ChargingStationId, EdgeId, EventId, MaintenanceStationId, NodeId, OrderId, RobotId,
5    ShipmentId, SimTime, SkuId, StationId, TaskId,
6};
7use rkyv::{Archive, Deserialize, Serialize};
8use std::cmp::Ordering;
9
10/// All possible simulation events
11#[derive(Archive, Deserialize, Serialize, Clone, Debug)]
12pub enum SimEvent {
13    /// New order arrives in the system
14    OrderArrival { order_id: OrderId },
15
16    /// Task is assigned to a robot
17    TaskAssignment { task_id: TaskId, robot_id: RobotId },
18
19    /// Robot departs from a node to traverse an edge
20    RobotDepartNode {
21        robot_id: RobotId,
22        from_node: NodeId,
23        to_node: NodeId,
24        edge_id: EdgeId,
25    },
26
27    /// Robot arrives at a node after traversing an edge
28    RobotArriveNode {
29        robot_id: RobotId,
30        node_id: NodeId,
31        from_node: NodeId,
32    },
33
34    /// Robot begins service at a station
35    StationServiceStart {
36        robot_id: RobotId,
37        station_id: StationId,
38        task_id: TaskId,
39    },
40
41    /// Robot completes service at a station
42    StationServiceEnd {
43        robot_id: RobotId,
44        station_id: StationId,
45        task_id: TaskId,
46    },
47
48    /// Inventory is updated (pick decrements, putaway increments)
49    InventoryUpdate {
50        sku_id: SkuId,
51        bin_id: BinId,
52        delta: i32,
53        task_id: TaskId,
54    },
55
56    /// Robot starts waiting for an edge to become available
57    RobotWaitStart {
58        robot_id: RobotId,
59        at_node: NodeId,
60        waiting_for_edge: EdgeId,
61    },
62
63    /// Robot wait ends, can attempt to proceed
64    RobotWaitEnd { robot_id: RobotId, at_node: NodeId },
65
66    /// Robot pickup at bin location
67    RobotPickup {
68        robot_id: RobotId,
69        task_id: TaskId,
70        node_id: NodeId,
71    },
72
73    /// Try to dispatch pending tasks to available robots
74    DispatchTasks,
75
76    // === v1: Inbound/Outbound Flow Events ===
77    /// Shipment arrives at inbound station
78    InboundArrival {
79        shipment_id: ShipmentId,
80        station_id: StationId,
81    },
82
83    /// Putaway task created from inbound shipment
84    PutawayTaskCreated {
85        task_id: TaskId,
86        shipment_id: ShipmentId,
87    },
88
89    /// Order is ready for outbound (all picks complete)
90    OutboundReady { order_id: OrderId },
91
92    /// Shipment departs from outbound station
93    ShipmentDeparture {
94        shipment_id: ShipmentId,
95        station_id: StationId,
96    },
97
98    /// Replenishment triggered due to low inventory
99    ReplenishmentTrigger {
100        sku_id: SkuId,
101        bin_id: BinId,
102        current_qty: u32,
103        threshold: u32,
104    },
105
106    // === v1: Battery & Charging Events ===
107    /// Robot starts charging at a station
108    RobotChargingStart {
109        robot_id: RobotId,
110        station_id: ChargingStationId,
111    },
112
113    /// Robot completes charging
114    RobotChargingEnd {
115        robot_id: RobotId,
116        station_id: ChargingStationId,
117        energy_charged_wh: f64,
118    },
119
120    /// Robot battery drops below threshold
121    RobotLowBattery { robot_id: RobotId, soc: f64 },
122
123    // === v1: Metrics Events ===
124    /// Periodic metrics sampling tick
125    MetricsSampleTick,
126
127    // === v2: Traffic & Safety Events ===
128    /// Deadlock detected between robots
129    DeadlockDetected {
130        /// Robot IDs involved in the deadlock cycle
131        robots: Vec<RobotId>,
132    },
133
134    /// Deadlock resolution action taken
135    DeadlockResolved {
136        /// Robot IDs that were in the deadlock
137        robots: Vec<RobotId>,
138        /// Robot that was selected to resolve the deadlock
139        resolver_robot: RobotId,
140    },
141
142    // === v3: Robot Failures & Maintenance Events ===
143    /// Robot has failed and needs repair
144    RobotFailure {
145        robot_id: RobotId,
146        /// Task that was interrupted by the failure, if any
147        interrupted_task: Option<TaskId>,
148    },
149
150    /// Robot scheduled maintenance is due
151    RobotMaintenanceDue {
152        robot_id: RobotId,
153        /// Operating hours since last maintenance
154        operating_hours: f64,
155    },
156
157    /// Robot starts maintenance or repair at a station
158    MaintenanceStart {
159        robot_id: RobotId,
160        station_id: MaintenanceStationId,
161        /// True if this is a repair (failure recovery), false if scheduled maintenance
162        is_repair: bool,
163    },
164
165    /// Robot completes maintenance or repair
166    MaintenanceEnd {
167        robot_id: RobotId,
168        station_id: MaintenanceStationId,
169        /// True if this was a repair, false if scheduled maintenance
170        is_repair: bool,
171        /// Duration of the maintenance/repair in seconds
172        duration_s: f64,
173    },
174}
175
176impl SimEvent {
177    /// Get a string name for the event type
178    pub fn event_type_name(&self) -> &'static str {
179        match self {
180            SimEvent::OrderArrival { .. } => "order_arrival",
181            SimEvent::TaskAssignment { .. } => "task_assignment",
182            SimEvent::RobotDepartNode { .. } => "robot_depart_node",
183            SimEvent::RobotArriveNode { .. } => "robot_arrive_node",
184            SimEvent::StationServiceStart { .. } => "station_service_start",
185            SimEvent::StationServiceEnd { .. } => "station_service_end",
186            SimEvent::InventoryUpdate { .. } => "inventory_update",
187            SimEvent::RobotWaitStart { .. } => "robot_wait_start",
188            SimEvent::RobotWaitEnd { .. } => "robot_wait_end",
189            SimEvent::RobotPickup { .. } => "robot_pickup",
190            SimEvent::DispatchTasks => "dispatch_tasks",
191            // v1: Inbound/Outbound flow events
192            SimEvent::InboundArrival { .. } => "inbound_arrival",
193            SimEvent::PutawayTaskCreated { .. } => "putaway_task_created",
194            SimEvent::OutboundReady { .. } => "outbound_ready",
195            SimEvent::ShipmentDeparture { .. } => "shipment_departure",
196            SimEvent::ReplenishmentTrigger { .. } => "replenishment_trigger",
197            // v1: Battery & Charging events
198            SimEvent::RobotChargingStart { .. } => "robot_charging_start",
199            SimEvent::RobotChargingEnd { .. } => "robot_charging_end",
200            SimEvent::RobotLowBattery { .. } => "robot_low_battery",
201            // v1: Metrics events
202            SimEvent::MetricsSampleTick => "metrics_sample_tick",
203            // v2: Traffic & Safety events
204            SimEvent::DeadlockDetected { .. } => "deadlock_detected",
205            SimEvent::DeadlockResolved { .. } => "deadlock_resolved",
206            // v3: Robot Failures & Maintenance events
207            SimEvent::RobotFailure { .. } => "robot_failure",
208            SimEvent::RobotMaintenanceDue { .. } => "robot_maintenance_due",
209            SimEvent::MaintenanceStart { .. } => "maintenance_start",
210            SimEvent::MaintenanceEnd { .. } => "maintenance_end",
211        }
212    }
213
214    /// Get the robot ID associated with this event, if any
215    pub fn robot_id(&self) -> Option<RobotId> {
216        match self {
217            SimEvent::TaskAssignment { robot_id, .. } => Some(*robot_id),
218            SimEvent::RobotDepartNode { robot_id, .. } => Some(*robot_id),
219            SimEvent::RobotArriveNode { robot_id, .. } => Some(*robot_id),
220            SimEvent::StationServiceStart { robot_id, .. } => Some(*robot_id),
221            SimEvent::StationServiceEnd { robot_id, .. } => Some(*robot_id),
222            SimEvent::RobotWaitStart { robot_id, .. } => Some(*robot_id),
223            SimEvent::RobotWaitEnd { robot_id, .. } => Some(*robot_id),
224            SimEvent::RobotPickup { robot_id, .. } => Some(*robot_id),
225            SimEvent::RobotChargingStart { robot_id, .. } => Some(*robot_id),
226            SimEvent::RobotChargingEnd { robot_id, .. } => Some(*robot_id),
227            SimEvent::RobotLowBattery { robot_id, .. } => Some(*robot_id),
228            SimEvent::DeadlockResolved { resolver_robot, .. } => Some(*resolver_robot),
229            SimEvent::RobotFailure { robot_id, .. } => Some(*robot_id),
230            SimEvent::RobotMaintenanceDue { robot_id, .. } => Some(*robot_id),
231            SimEvent::MaintenanceStart { robot_id, .. } => Some(*robot_id),
232            SimEvent::MaintenanceEnd { robot_id, .. } => Some(*robot_id),
233            _ => None,
234        }
235    }
236
237    /// Get the task ID associated with this event, if any
238    pub fn task_id(&self) -> Option<TaskId> {
239        match self {
240            SimEvent::TaskAssignment { task_id, .. } => Some(*task_id),
241            SimEvent::StationServiceStart { task_id, .. } => Some(*task_id),
242            SimEvent::StationServiceEnd { task_id, .. } => Some(*task_id),
243            SimEvent::InventoryUpdate { task_id, .. } => Some(*task_id),
244            SimEvent::RobotPickup { task_id, .. } => Some(*task_id),
245            SimEvent::PutawayTaskCreated { task_id, .. } => Some(*task_id),
246            _ => None,
247        }
248    }
249
250    /// Get the shipment ID associated with this event, if any
251    pub fn shipment_id(&self) -> Option<ShipmentId> {
252        match self {
253            SimEvent::InboundArrival { shipment_id, .. } => Some(*shipment_id),
254            SimEvent::PutawayTaskCreated { shipment_id, .. } => Some(*shipment_id),
255            SimEvent::ShipmentDeparture { shipment_id, .. } => Some(*shipment_id),
256            _ => None,
257        }
258    }
259
260    /// Get the charging station ID associated with this event, if any
261    pub fn charging_station_id(&self) -> Option<ChargingStationId> {
262        match self {
263            SimEvent::RobotChargingStart { station_id, .. } => Some(*station_id),
264            SimEvent::RobotChargingEnd { station_id, .. } => Some(*station_id),
265            _ => None,
266        }
267    }
268
269    /// Get the maintenance station ID associated with this event, if any
270    pub fn maintenance_station_id(&self) -> Option<MaintenanceStationId> {
271        match self {
272            SimEvent::MaintenanceStart { station_id, .. } => Some(*station_id),
273            SimEvent::MaintenanceEnd { station_id, .. } => Some(*station_id),
274            _ => None,
275        }
276    }
277}
278
279/// A scheduled event with timestamp and unique ID
280#[derive(Archive, Deserialize, Serialize, Clone, Debug)]
281pub struct ScheduledEvent {
282    pub id: EventId,
283    pub time: SimTime,
284    pub event: SimEvent,
285}
286
287impl ScheduledEvent {
288    /// Create a new scheduled event
289    pub fn new(id: EventId, time: SimTime, event: SimEvent) -> Self {
290        Self { id, time, event }
291    }
292}
293
294impl PartialEq for ScheduledEvent {
295    fn eq(&self, other: &Self) -> bool {
296        self.id == other.id
297    }
298}
299
300impl Eq for ScheduledEvent {}
301
302impl PartialOrd for ScheduledEvent {
303    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
304        Some(self.cmp(other))
305    }
306}
307
308impl Ord for ScheduledEvent {
309    fn cmp(&self, other: &Self) -> Ordering {
310        // Reverse ordering for min-heap (earliest time first)
311        // If times are equal, use event ID for deterministic ordering
312        match other.time.0.partial_cmp(&self.time.0) {
313            Some(Ordering::Equal) | None => other.id.0.cmp(&self.id.0),
314            Some(ord) => ord,
315        }
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_event_ordering() {
325        let e1 = ScheduledEvent::new(
326            EventId(1),
327            SimTime::from_seconds(10.0),
328            SimEvent::DispatchTasks,
329        );
330        let e2 = ScheduledEvent::new(
331            EventId(2),
332            SimTime::from_seconds(5.0),
333            SimEvent::DispatchTasks,
334        );
335
336        // e2 should come first (earlier time)
337        assert!(e2 > e1);
338    }
339
340    #[test]
341    fn test_event_type_name() {
342        let event = SimEvent::OrderArrival {
343            order_id: OrderId(1),
344        };
345        assert_eq!(event.event_type_name(), "order_arrival");
346    }
347}