1use std::cmp::Reverse;
20use std::collections::{BTreeMap, BTreeSet};
21
22use crate::people::citizens::Citizen;
23use crate::scheduler::ticks;
24use crate::town::Sim;
25
26const PERIOD_SECONDS: f32 = 300.0;
28const FAR: i32 = 0x100_0000;
30const STEP: i32 = 7;
32
33#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct Commute {
38 pub timer: i64,
40 pub pairs: Vec<(i32, i32)>,
42 pub index: i32,
44 pub ranks: BTreeMap<(i32, i32), i32>,
46}
47
48impl Default for Commute {
49 fn default() -> Self {
50 Commute {
51 timer: 0,
52 pairs: Vec::new(),
53 index: -1,
54 ranks: BTreeMap::new(),
55 }
56 }
57}
58
59struct Place {
62 entity: usize,
63 profession: Option<usize>,
64 entrance: i32,
65 left: i32,
66 total: i32,
67 candidates: BTreeSet<(i32, usize)>,
68}
69
70impl Place {
71 fn priority(&self, score: i32) -> i32 {
73 (score as f32 * (1.0 - (self.total - self.left) as f32 / self.total as f32)) as i32
74 }
75}
76
77impl Sim<'_, '_> {
78 fn region(&self, cell: i32) -> i32 {
80 match self.regions.get(cell as usize) {
81 Some(&r) if r != 0 => r as i32,
82 _ => -1,
83 }
84 }
85
86 fn region_key(&self, a: i32, b: i32) -> (i32, i32) {
87 let (ra, rb) = (self.region(a), self.region(b));
88 (ra.max(rb), ra.min(rb))
89 }
90
91 fn straight(&self, a: i32, b: i32) -> i32 {
93 let n = self.size as i32;
94 let (dy, dx) = (a / n - b / n, a % n - b % n);
95 ((dy * dy + dx * dx) as f32).sqrt() as i32
96 }
97
98 pub fn update_commute(&mut self, job: &mut Commute, citizens: &[Citizen], dt: i64) {
100 job.timer += dt;
101 let period = ticks(PERIOD_SECONDS);
102 if job.timer < period {
103 return;
104 }
105 if !self.commute_step(job, citizens) {
106 self.commute_start(job);
107 } else if job.index < 0 && period < job.timer {
108 loop {
109 job.timer -= period;
110 if job.timer <= period {
111 break;
112 }
113 }
114 }
115 }
116
117 fn commute_start(&mut self, job: &mut Commute) {
119 if 0 <= job.index {
120 return;
121 }
122 let residences = self.workforce.residences.clone();
123 let homes: Vec<i32> = residences.iter().map(|&r| self.entrance_cell(r)).collect();
124 job.ranks.clear();
125 job.pairs.clear();
126 for p in 0..self.professions.list.len() {
127 let places = self.workforce.registry.get(&p).cloned().unwrap_or_default();
128 for w in places {
129 let entrance = self.entrance_cell(w);
130 if self.region(entrance) < 0 {
131 continue;
132 }
133 for &home in &homes {
134 if self.region(home) < 0 {
135 continue;
136 }
137 let key = self.region_key(entrance, home);
138 if job.ranks.contains_key(&key) {
139 continue;
140 }
141 job.pairs.push((entrance.max(home), entrance.min(home)));
142 job.ranks.insert(key, 0);
143 }
144 }
145 }
146 job.index = job.pairs.len() as i32 - 1;
147 }
148
149 fn commute_step(&mut self, job: &mut Commute, citizens: &[Citizen]) -> bool {
151 if job.index < 0 {
152 return false;
153 }
154 for _ in 0..job.index.min(STEP - 1) + 1 {
155 let (a, b) = job.pairs[job.index as usize];
156 let mut rank = self.path_rank(a, b, false);
157 if (rank as u32) < 2 {
158 rank = self.straight(a, b);
159 }
160 let key = self.region_key(a, b);
161 job.ranks.insert(key, rank);
162 job.index -= 1;
163 }
164 if job.index < 0 {
165 self.commute_homes(citizens);
166 self.commute_jobs(job);
167 job.pairs.clear();
168 }
169 true
170 }
171
172 fn family_cost(&mut self, family: usize, home: usize) -> i32 {
176 let from = self.point_cell(home);
177 let laborer = self.professions.laborer;
178 let members = self
179 .homes
180 .get(&family)
181 .map(|h| h.families[0].clone())
182 .unwrap_or_default();
183 let mut cost = 0i32;
184 for c in members {
185 let s = &self.states[c];
186 if !self.adult[c] || s.profession == laborer {
187 continue;
188 }
189 let Some(wp) = s.workplace else {
190 continue;
191 };
192 let to = self.entrance_cell(wp);
193 if self.reachable(from, to) {
194 cost = cost.wrapping_add(self.rank_or_distance(from, to));
195 }
196 }
197 cost
198 }
199
200 fn commute_homes(&mut self, citizens: &[Citizen]) {
202 let mut empty = BTreeSet::new();
203 let mut occupied = BTreeSet::new();
205 for r in self.workforce.residences.clone() {
206 let Some(h) = self.homes.get(&r) else {
207 continue;
208 };
209 if 2 <= h.families.len() {
210 continue;
211 }
212 if h.families[0].is_empty() {
213 empty.insert(r);
214 } else {
215 let cost = self.family_cost(r, r);
216 occupied.insert((Reverse(cost), r));
217 }
218 }
219 if empty.is_empty() {
220 return;
221 }
222 for (Reverse(cost), from) in occupied {
223 let mut best = (cost, None);
224 for &to in &empty {
225 let c = self.family_cost(from, to);
226 if c < best.0 {
227 best = (c, Some(to));
228 }
229 }
230 let Some(to) = best.1 else {
231 continue;
232 };
233 let mut i = 0;
235 while let Some(&c) = self.homes.get(&from).and_then(|h| h.families[0].get(i)) {
236 self.move_home(citizens, c, to, 0);
237 i += 1;
238 }
239 self.move_goods(from, to);
240 empty.remove(&to);
241 if empty.is_empty() {
242 break;
243 }
244 }
245 }
246
247 fn move_goods(&mut self, from: usize, to: usize) {
250 let (Some(a), Some(b)) = (
251 self.homes.get(&from).map(|h| h.storage),
252 self.homes.get(&to).map(|h| h.storage),
253 ) else {
254 return;
255 };
256 let items: Vec<(usize, i32)> = self.storages[a]
257 .items
258 .iter()
259 .map(|i| (i.material, i.count - i.reserved))
260 .collect();
261 let mut moved = Vec::new();
262 for (m, n) in items {
263 if self.storages[b].fits(self.materials, m, n) {
264 self.storages[b].add(m, n);
265 moved.push((m, n));
266 }
267 }
268 for (m, n) in moved {
269 self.storages[a].remove(m, n);
270 }
271 }
272
273 fn move_home(&mut self, citizens: &[Citizen], c: usize, home: usize, slot: i32) {
275 let s = &self.states[c];
276 if s.home == Some(home) && s.home_slot == slot {
277 return;
278 }
279 if let Some(old) = s.home {
280 let old_slot = s.home_slot.max(0) as usize;
281 if let Some(h) = self.homes.get_mut(&old) {
282 if let Some(f) = h.families.get_mut(old_slot) {
283 f.retain(|&x| x != c);
284 }
285 if h.occupants() == 0 {
286 crate::people::status::set_icon(&mut h.status, 6, false, false);
287 crate::people::status::set_icon(&mut h.status, 7, false, false);
288 }
289 }
290 }
291 self.states[c].home = Some(home);
292 self.states[c].home_slot = slot;
293 if let Some(f) = self
294 .homes
295 .get_mut(&home)
296 .and_then(|h| h.families.get_mut(slot.max(0) as usize))
297 {
298 f.push(c);
299 f.sort_by(|&a, &b| crate::people::life::family_order(citizens, a, b));
300 }
301 for d in self.states[c].dependents.clone() {
302 self.move_home(citizens, d, home, slot);
303 }
304 }
305
306 fn job_score(&mut self, ranks: &mut BTreeMap<(i32, i32), i32>, home: i32, to: i32) -> i32 {
310 if home < 0 || !self.reachable(home, to) {
311 return FAR;
312 }
313 if self.region(home) < 0 || self.region(to) < 0 {
314 let d = self.straight(home, to);
315 return if d < 0 { FAR } else { d };
316 }
317 let key = self.region_key(home, to);
318 let score = match ranks.get(&key) {
319 Some(&r) => r,
320 None => {
321 let mut r = self.rank_or_distance(home, to);
322 if (r as u32) < 2 {
323 r = self.straight(home, to);
324 }
325 ranks.insert(key, r);
326 0
327 }
328 };
329 if score < 0 {
330 FAR
331 } else {
332 score
333 }
334 }
335
336 fn home_cell(&self, c: usize) -> i32 {
338 self.states[c].home.map_or(-1, |h| self.point_cell(h))
339 }
340
341 fn place_profession(&self, wp: usize) -> Option<usize> {
343 self.workforce
344 .workplaces
345 .get(&wp)
346 .and_then(|w| w.profession)
347 }
348
349 fn assign(
351 &mut self,
352 workers: &mut [crate::work::professions::Worker],
353 c: usize,
354 profession: usize,
355 wp: Option<usize>,
356 ) {
357 if workers[c].profession != profession || workers[c].workplace != wp {
358 self.workforce.set_workplace(workers, c, wp);
359 workers[c].profession = profession;
360 }
361 }
362
363 fn commute_jobs(&mut self, job: &mut Commute) {
365 let laborer = self.professions.laborer;
366 let mut workers = self.workers();
367 let n = workers.len();
368 for c in 0..n {
370 let Some(wp) = workers[c].workplace.filter(|_| workers[c].adult) else {
371 continue;
372 };
373 if self.place_profession(wp) != Some(workers[c].profession) {
374 self.assign(&mut workers, c, laborer, None);
375 }
376 }
377 let mut places = Vec::new();
379 for p in 0..self.professions.list.len() {
380 for w in self.workforce.registry.get(&p).cloned().unwrap_or_default() {
381 let Some(place) = self.workforce.workplaces.get(&w) else {
382 continue;
383 };
384 let count = place.workers.len() as i32;
385 let profession = place.profession;
386 if count == 0 {
387 continue;
388 }
389 let entrance = self.entrance_cell(w);
390 places.push(Place {
391 entity: w,
392 profession,
393 entrance,
394 left: count,
395 total: count,
396 candidates: BTreeSet::new(),
397 });
398 }
399 }
400 let mut free = BTreeSet::new();
403 let mut quota: BTreeMap<usize, i32> = BTreeMap::new();
404 for c in 0..n {
405 if !workers[c].adult {
406 continue;
407 }
408 free.insert(c);
409 if let Some(wp) = workers[c].workplace {
410 if self.place_profession(wp) != Some(workers[c].profession) {
411 self.assign(&mut workers, c, laborer, None);
412 }
413 }
414 *quota.entry(workers[c].profession).or_insert(0) += 1;
415 self.assign(&mut workers, c, laborer, None);
416 let home = self.home_cell(c);
417 for place in places.iter_mut() {
418 let score = self.job_score(&mut job.ranks, home, place.entrance);
419 place.candidates.insert((score, c));
420 }
421 }
422 let mut queue = BTreeSet::new();
424 for (i, place) in places.iter().enumerate() {
425 if let Some(&(score, _)) = place.candidates.first() {
426 queue.insert((Reverse(place.priority(score)), i));
427 }
428 }
429 while let Some(&(key, i)) = queue.first() {
430 queue.remove(&(key, i));
431 let Some(&(_, c)) = places[i].candidates.first() else {
432 continue;
433 };
434 let (entity, profession) = (places[i].entity, places[i].profession);
435 if let Some(p) = profession {
436 self.assign(&mut workers, c, p, Some(entity));
437 }
438 let key = profession.unwrap_or(usize::MAX);
439 if let Some(q) = quota.get_mut(&key) {
440 *q -= 1;
441 if *q == 0 {
442 quota.remove(&key);
443 }
444 }
445 free.remove(&c);
446 let home = self.home_cell(c);
447 places[i].left -= 1;
448 for j in 0..places.len() {
449 if places[j].left == 0 {
450 continue;
451 }
452 let score = self.job_score(&mut job.ranks, home, places[j].entrance);
453 let entry = if places[j].candidates.contains(&(score, c)) {
454 Some((score, c))
455 } else {
456 places[j].candidates.iter().copied().find(|e| e.1 == c)
457 };
458 let Some(entry) = entry else {
459 continue;
460 };
461 if places[j].candidates.first() == Some(&entry) {
462 let old = places[j].priority(entry.0);
463 queue.remove(&(Reverse(old), j));
464 places[j].candidates.remove(&entry);
465 if let Some(&(best, _)) = places[j].candidates.first() {
466 queue.insert((Reverse(places[j].priority(best)), j));
467 }
468 } else {
469 places[j].candidates.remove(&entry);
470 }
471 }
472 }
473 while let Some((&p, _)) = quota.first_key_value() {
475 let Some(c) = free.pop_first() else {
476 break;
477 };
478 self.assign(&mut workers, c, p, None);
479 let q = quota.get_mut(&p).unwrap();
480 *q -= 1;
481 if *q == 0 {
482 quota.remove(&p);
483 }
484 }
485 self.set_workers(&workers);
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn priority_weights_the_share_left() {
495 let mut p = Place {
496 entity: 0,
497 profession: None,
498 entrance: 0,
499 left: 4,
500 total: 4,
501 candidates: BTreeSet::new(),
502 };
503 assert_eq!(p.priority(100), 100);
504 p.left = 1;
505 assert_eq!(p.priority(100), 25);
506 assert_eq!(p.priority(FAR), FAR / 4);
507 }
508}