1use clap::{Args, Command};
4use gwr_engine::types::SimError;
5
6use crate::time_of_day::TimeOfDay;
7
8#[derive(Debug, Clone, Args)]
9pub struct RestaurantArgs {
10 #[arg(long, default_value = "7")]
12 pub seed: u64,
13
14 #[arg(long, default_value = "07:00")]
16 pub opening_time: TimeOfDay,
17
18 #[arg(long, default_value = "22:00")]
20 pub closing_time: TimeOfDay,
21
22 #[arg(long, default_value = "46")]
24 pub base_arrival_gap: u64,
25
26 #[arg(long, default_value = "14")]
28 pub arrival_jitter: i64,
29
30 #[arg(long, default_value = "0.98")]
32 pub join_base_probability: f64,
33
34 #[arg(long, default_value = "0.17")]
36 pub join_queue_sensitivity: f64,
37
38 #[arg(long, default_value = "240")]
41 pub max_queue_wait_ticks: u64,
42
43 #[arg(long, default_value = "6")]
45 pub move_to_till_ticks: u64,
46
47 #[arg(long, default_value = "7")]
49 pub order_overhead_ticks: u64,
50
51 #[arg(long, default_value = "15")]
53 pub payment_ticks: u64,
54
55 #[arg(long, default_value = "12")]
57 pub pack_order_ticks: u64,
58
59 #[arg(long, default_value = "24")]
61 pub max_kitchen_queue_len: usize,
62
63 #[arg(long, default_value = "7")]
65 pub take_food_ticks: u64,
66
67 #[arg(long, default_value = "10")]
69 pub leave_ticks: u64,
70
71 #[arg(long, default_value = "16.0")]
73 pub till_salary_per_hour: f64,
74
75 #[arg(long, default_value = "18.0")]
77 pub kitchen_salary_per_hour: f64,
78}
79
80#[must_use]
81pub fn long_arg_name(command: &Command, id: &str) -> String {
82 let long = command
83 .get_arguments()
84 .find(|arg| arg.get_id().as_str() == id)
85 .and_then(|arg| arg.get_long())
86 .map_or_else(|| id.replace('_', "-"), ToOwned::to_owned);
87 format!("--{long}")
88}
89
90#[derive(Debug, Clone, Copy)]
91pub struct RestaurantConfig {
92 pub seed: u64,
93 pub opening_time: TimeOfDay,
94 pub closing_time: TimeOfDay,
95 pub day_ticks: u64,
96 pub base_arrival_gap: u64,
97 pub arrival_jitter: i64,
98 pub join_base_probability: f64,
99 pub join_queue_sensitivity: f64,
100 pub max_queue_wait_ticks: u64,
101 pub move_to_till_ticks: u64,
102 pub order_overhead_ticks: u64,
103 pub payment_ticks: u64,
104 pub pack_order_ticks: u64,
105 pub max_kitchen_queue_len: usize,
106 pub take_food_ticks: u64,
107 pub leave_ticks: u64,
108 pub till_salary_per_hour: f64,
109 pub kitchen_salary_per_hour: f64,
110}
111
112impl From<RestaurantArgs> for RestaurantConfig {
113 fn from(restaurant_args: RestaurantArgs) -> Self {
114 let day_ticks = restaurant_args
115 .closing_time
116 .seconds_since_midnight()
117 .saturating_sub(restaurant_args.opening_time.seconds_since_midnight());
118 Self {
119 seed: restaurant_args.seed,
120 opening_time: restaurant_args.opening_time,
121 closing_time: restaurant_args.closing_time,
122 day_ticks,
123 base_arrival_gap: restaurant_args.base_arrival_gap,
124 arrival_jitter: restaurant_args.arrival_jitter,
125 join_base_probability: restaurant_args.join_base_probability,
126 join_queue_sensitivity: restaurant_args.join_queue_sensitivity,
127 max_queue_wait_ticks: restaurant_args.max_queue_wait_ticks,
128 move_to_till_ticks: restaurant_args.move_to_till_ticks,
129 order_overhead_ticks: restaurant_args.order_overhead_ticks,
130 payment_ticks: restaurant_args.payment_ticks,
131 pack_order_ticks: restaurant_args.pack_order_ticks,
132 max_kitchen_queue_len: restaurant_args.max_kitchen_queue_len,
133 take_food_ticks: restaurant_args.take_food_ticks,
134 leave_ticks: restaurant_args.leave_ticks,
135 till_salary_per_hour: restaurant_args.till_salary_per_hour,
136 kitchen_salary_per_hour: restaurant_args.kitchen_salary_per_hour,
137 }
138 }
139}
140
141impl RestaurantConfig {
142 pub fn validate(&self) -> Result<(), SimError> {
143 let command = RestaurantArgs::augment_args(Command::new("sim-restaurant"));
144 let join_base_probability = long_arg_name(&command, "join_base_probability");
145 let opening_time = long_arg_name(&command, "opening_time");
146 let closing_time = long_arg_name(&command, "closing_time");
147
148 if !(0.0..=1.0).contains(&self.join_base_probability) {
149 return Err(SimError(format!(
150 "`{join_base_probability}` must be in the range 0..=1"
151 )));
152 }
153 if self.opening_time >= self.closing_time {
154 return Err(SimError(format!(
155 "`{opening_time}` must be earlier than `{closing_time}`"
156 )));
157 }
158 if self.day_ticks == 0 {
159 return Err(SimError("day length must be greater than zero".to_string()));
160 }
161 Ok(())
162 }
163
164 #[must_use]
165 pub fn paid_hours(&self) -> u64 {
166 self.day_ticks.div_ceil(3600)
167 }
168}