1use crate::sim::{Metrics, RunSummary};
4use crate::staff::Staffing;
5use crate::time_of_day::TimeOfDay;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum CustomerPhase {
9 Planned,
10 Balked,
11 WaitingTill,
12 AtTill,
13 WaitingKitchen,
14 PreparingFood,
15 CollectingFood,
16 Served,
17 GaveUpQueue,
18 Abandoned,
19}
20
21#[derive(Clone, Debug, Default)]
22pub struct CustomerStateCounts {
23 pub planned: usize,
24 pub balked: usize,
25 pub waiting_till: usize,
26 pub at_till: usize,
27 pub waiting_kitchen: usize,
28 pub preparing_food: usize,
29 pub collecting_food: usize,
30 pub served: usize,
31 pub gave_up_queue: usize,
32 pub abandoned: usize,
33}
34
35#[derive(Clone, Debug)]
36pub struct SimulationSnapshot {
37 pub metrics: Metrics,
38 pub till_queue: Vec<usize>,
39 pub kitchen_queue: Vec<usize>,
40 pub active_till_workers: usize,
41 pub active_kitchen_workers: usize,
42 pub arrivals_complete: bool,
43 pub closed: bool,
44 pub customer_counts: CustomerStateCounts,
45}
46
47#[derive(Clone, Debug)]
48pub struct TimelinePoint {
49 pub tick: u64,
50 pub snapshot: SimulationSnapshot,
51}
52
53#[derive(Clone, Debug)]
54pub struct TimelineEvent {
55 pub tick: u64,
56 pub message: String,
57}
58
59#[derive(Clone, Debug)]
60pub struct RecordedSimulation {
61 pub staffing: Staffing,
62 pub opening_time: TimeOfDay,
63 pub day_ticks: u64,
64 pub summary: RunSummary,
65 pub demand_size: usize,
66 pub timeline: Vec<TimelinePoint>,
67 pub events: Vec<TimelineEvent>,
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub enum PlotStat {
72 TillQueueLen,
73 KitchenQueueLen,
74 ActiveTillWorkers,
75 ActiveKitchenWorkers,
76 Arrivals,
77 Balked,
78 GaveUpQueue,
79 OrdersStarted,
80 OrdersServed,
81 OrdersAbandoned,
82 Revenue,
83 IngredientCost,
84 Profit,
85}
86
87impl PlotStat {
88 pub const ALL: [Self; 13] = [
89 Self::TillQueueLen,
90 Self::KitchenQueueLen,
91 Self::ActiveTillWorkers,
92 Self::ActiveKitchenWorkers,
93 Self::Arrivals,
94 Self::Balked,
95 Self::GaveUpQueue,
96 Self::OrdersStarted,
97 Self::OrdersServed,
98 Self::OrdersAbandoned,
99 Self::Revenue,
100 Self::IngredientCost,
101 Self::Profit,
102 ];
103
104 #[must_use]
105 pub fn label(self) -> &'static str {
106 match self {
107 Self::TillQueueLen => "Till Queue",
108 Self::KitchenQueueLen => "Kitchen Queue",
109 Self::ActiveTillWorkers => "Busy Till Workers",
110 Self::ActiveKitchenWorkers => "Busy Kitchen Workers",
111 Self::Arrivals => "Arrivals",
112 Self::Balked => "Balked",
113 Self::GaveUpQueue => "Gave Up Queue",
114 Self::OrdersStarted => "Orders Started",
115 Self::OrdersServed => "Orders Served",
116 Self::OrdersAbandoned => "Orders Abandoned",
117 Self::Revenue => "Revenue",
118 Self::IngredientCost => "Ingredient Cost",
119 Self::Profit => "Profit",
120 }
121 }
122
123 #[must_use]
124 pub fn supports_windowing(self) -> bool {
125 matches!(
126 self,
127 Self::Arrivals
128 | Self::Balked
129 | Self::GaveUpQueue
130 | Self::OrdersStarted
131 | Self::OrdersServed
132 | Self::OrdersAbandoned
133 | Self::Revenue
134 | Self::IngredientCost
135 | Self::Profit
136 )
137 }
138
139 #[must_use]
140 pub fn title(self, windowed: bool, window_ticks: u64) -> String {
141 if windowed && self.supports_windowing() {
142 format!("{} (Windowed over {} ticks)", self.label(), window_ticks)
143 } else {
144 self.label().to_string()
145 }
146 }
147
148 #[must_use]
149 pub fn base_value(
150 self,
151 snapshot: &SimulationSnapshot,
152 tick: u64,
153 salary_cost: f64,
154 day_ticks: u64,
155 ) -> f64 {
156 match self {
157 Self::TillQueueLen => snapshot.till_queue.len() as f64,
158 Self::KitchenQueueLen => snapshot.kitchen_queue.len() as f64,
159 Self::ActiveTillWorkers => snapshot.active_till_workers as f64,
160 Self::ActiveKitchenWorkers => snapshot.active_kitchen_workers as f64,
161 Self::Arrivals => snapshot.metrics.arrivals as f64,
162 Self::Balked => snapshot.metrics.balked as f64,
163 Self::GaveUpQueue => snapshot.metrics.gave_up_queue as f64,
164 Self::OrdersStarted => snapshot.metrics.orders_started as f64,
165 Self::OrdersServed => snapshot.metrics.orders_served as f64,
166 Self::OrdersAbandoned => snapshot.metrics.orders_abandoned as f64,
167 Self::Revenue => snapshot.metrics.revenue,
168 Self::IngredientCost => snapshot.metrics.ingredient_cost,
169 Self::Profit => {
170 let accrued_salary_cost = if day_ticks == 0 {
171 0.0
172 } else {
173 salary_cost * (tick.min(day_ticks) as f64 / day_ticks as f64)
174 };
175 snapshot.metrics.revenue - snapshot.metrics.ingredient_cost - accrued_salary_cost
176 }
177 }
178 }
179
180 #[must_use]
181 pub fn next(self) -> Self {
182 let index = Self::ALL.iter().position(|stat| *stat == self).unwrap_or(0);
183 Self::ALL[(index + 1) % Self::ALL.len()]
184 }
185
186 #[must_use]
187 pub fn previous(self) -> Self {
188 let index = Self::ALL.iter().position(|stat| *stat == self).unwrap_or(0);
189 Self::ALL[(index + Self::ALL.len() - 1) % Self::ALL.len()]
190 }
191}