1use std::collections::{BTreeMap, BTreeSet};
26
27use crate::random::Random;
28
29pub const CATEGORIES: usize = 14;
31pub const CONSTRUCTION: u32 = 13;
33pub const EMERGENCY: u32 = 14;
35
36#[derive(Debug, Clone)]
38pub enum WorkKind {
39 Gather(crate::work::construction::Gather),
44 Build(crate::work::construction::BuildWork),
49 Clear(crate::work::construction::ClearWork),
54 Pickup(crate::work::construction::PickupWork),
59 Produce(crate::work::construction::ProduceWork),
64 Location(crate::people::services::LocationWork),
67 Herbalist(crate::production::herbalist::HerbalistWork),
72 Forester(crate::production::herbalist::HerbalistWork),
77 Hunt(crate::production::herbalist::HerbalistWork),
82 PlantArea(crate::production::farm::AreaWork),
87 TendArea(crate::production::herbalist::HerbalistWork),
90 Harvest(crate::production::farm::AreaWork),
95 PlantOrchard(crate::production::farm::AreaWork),
100 PickFruit(crate::production::farm::AreaWork),
105 Pasture(crate::production::herbalist::HerbalistWork),
110 ClearStorage(crate::production::herbalist::HerbalistWork),
115 Fire(crate::production::herbalist::HerbalistWork),
120 Trade(crate::commerce::trade::TradeWork),
123 Road(crate::work::road::RoadWork),
126 Market(crate::commerce::market::MarketWork),
129 Fishing(crate::production::fishing::FishingWork),
134 Test(i32),
136}
137
138#[derive(Debug, Clone)]
143pub struct Work {
144 pub entity: usize,
147 pub id: i32,
150 pub priority: i32,
152 pub cell: i32,
155 pub failed_pass: i32,
158 pub patience: i64,
163 pub patience_base: i64,
165 pub far_count: i32,
167 pub profession: usize,
170 pub kind: WorkKind,
172}
173
174impl Work {
175 pub fn new(entity: usize, cell: i32, profession: usize, kind: WorkKind) -> Work {
179 Work {
180 entity,
181 id: -1,
182 priority: 0,
183 cell,
184 failed_pass: 0,
185 patience: 0,
186 patience_base: 0,
187 far_count: 0,
188 profession,
189 kind,
190 }
191 }
192}
193
194pub trait WorkHost {
196 fn profession(&self, citizen: usize) -> usize;
198 fn workplace(&self, citizen: usize) -> Option<usize>;
200 fn cell(&self, citizen: usize) -> i32;
202 fn has_emergency(&self, citizen: usize) -> bool;
204 fn works_anywhere(&self, profession: usize) -> bool;
206 fn rank(&mut self, a: i32, b: i32) -> i32;
208 fn stock(&self, slot: usize) -> (i32, i32);
210 fn laborer(&self) -> usize;
212 fn builder(&self) -> usize;
214 fn rng(&mut self) -> &mut Random;
216
217 fn refresh(&mut self, work: &mut Work);
219 fn try_assign(&mut self, work: &mut Work, citizen: usize, time: i64) -> u8;
221 fn try_assign_again(&mut self, _work: &mut Work, _citizen: usize, _time: i64) -> bool {
223 false
224 }
225 fn complete(&mut self, work: &mut Work, citizen: usize, param: i32) -> bool;
227 fn fail(&mut self, work: &mut Work, citizen: usize, param: i32);
229 fn take_events(&mut self) -> Vec<JobEvent> {
232 Vec::new()
233 }
234 fn cancel(&mut self, work: &mut Work);
237 fn take_offers(&mut self) -> Vec<i32> {
241 Vec::new()
242 }
243 fn offers_again(&mut self) -> bool {
245 false
246 }
247 fn resume(&mut self, _work: &mut Work, _citizen: usize) -> u8 {
250 1
251 }
252}
253
254pub const NESTED: u8 = 3;
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub struct JobEvent {
260 pub work: i32,
262 pub citizen: usize,
264 pub param: i32,
266 pub complete: bool,
268}
269
270#[derive(Debug, Clone)]
272pub struct Jobs {
273 next_id: i32,
275 next_priority: i32,
276 pub pass: i32,
281 priorities: BTreeMap<i32, i32>,
283 pub works: BTreeMap<i32, Work>,
286 categories: [BTreeSet<i32>; CATEGORIES],
288 professions: BTreeMap<usize, BTreeSet<i32>>,
290 pub time: i64,
293}
294
295impl Default for Jobs {
296 fn default() -> Self {
297 Jobs {
298 next_id: 0,
299 next_priority: 0,
300 pass: 1,
301 priorities: BTreeMap::new(),
302 works: BTreeMap::new(),
303 categories: Default::default(),
304 professions: BTreeMap::new(),
305 time: 0,
306 }
307 }
308}
309
310const EMERGENCY_LISTS: [(usize, &[u32]); 9] = [
312 (5, &[1]),
313 (3, &[2, 4, 10]),
314 (4, &[3, 6, 4, 10, 12]),
315 (0, &[4, 10]),
316 (2, &[6, 12]),
317 (1, &[5, 11]),
318 (10, &[9]),
319 (7, &[7, 9]),
320 (6, &[8]),
321];
322
323impl Jobs {
324 pub fn step(&mut self, dt: i64) {
328 self.pass = self.pass.wrapping_add(1);
329 self.time = self.time.wrapping_add(dt);
330 }
331
332 pub fn is_empty(&self) -> bool {
334 self.works.is_empty()
335 }
336
337 pub fn add(&mut self, mut work: Work, mask: u32, keep: Option<i32>) -> i32 {
342 self.next_id = self.next_id.wrapping_add(1);
343 let id = self.next_id;
344 if id == 0x7ffffffe {
345 self.next_id = 0;
346 }
347 let priority = match keep {
348 None => {
349 let p = self.next_priority;
350 self.next_priority = p.wrapping_add(1);
351 p
352 }
353 Some(mut p) => {
354 while self.priorities.contains_key(&p) {
355 p -= 1;
356 }
357 p
358 }
359 };
360 work.id = id;
361 work.priority = priority;
362 self.priorities.entry(priority).or_insert(id);
363 for c in 1..=CATEGORIES {
364 if mask & (1 << c) != 0 {
365 self.categories[c - 1].insert(id);
366 }
367 }
368 self.professions
369 .entry(work.profession)
370 .or_default()
371 .insert(id);
372 self.works.insert(id, work);
373 id
374 }
375
376 pub fn restore(
383 &mut self,
384 next_id: i32,
385 next_priority: i32,
386 pass: i32,
387 works: impl IntoIterator<Item = (Work, Vec<u32>)>,
388 professions: &[usize],
389 ) {
390 *self = Jobs {
391 next_id,
392 next_priority,
393 pass,
394 ..Default::default()
395 };
396 for &p in professions {
398 self.professions.entry(p).or_default();
399 }
400 for (work, categories) in works {
401 let id = work.id;
402 self.priorities.entry(work.priority).or_insert(id);
403 for c in categories {
404 if let Some(set) = (c as usize)
405 .checked_sub(1)
406 .and_then(|c| self.categories.get_mut(c))
407 {
408 set.insert(id);
409 }
410 }
411 self.professions
412 .entry(work.profession)
413 .or_default()
414 .insert(id);
415 self.works.insert(id, work);
416 }
417 }
418
419 pub fn counters(&self) -> (i32, i32) {
422 (self.next_id, self.next_priority)
423 }
424
425 pub fn category(&self, c: u32) -> Vec<i32> {
427 (c as usize)
428 .checked_sub(1)
429 .and_then(|c| self.categories.get(c))
430 .map(|set| set.iter().copied().collect())
431 .unwrap_or_default()
432 }
433
434 pub fn by_profession(&self) -> Vec<(usize, Vec<i32>)> {
437 self.professions
438 .iter()
439 .map(|(&p, ids)| (p, ids.iter().copied().collect()))
440 .collect()
441 }
442
443 pub fn remove(&mut self, id: i32, host: &mut impl WorkHost) {
447 let Some(mut work) = self.works.remove(&id) else {
448 return;
449 };
450 for c in &mut self.categories {
451 c.remove(&id);
452 }
453 if self.priorities.get(&work.priority) == Some(&id) {
454 self.priorities.remove(&work.priority);
455 }
456 if let Some(set) = self.professions.get_mut(&work.profession) {
457 set.remove(&id);
458 }
459 host.refresh(&mut work);
460 host.cancel(&mut work);
461 self.drain(host);
462 }
463
464 pub fn drain(&mut self, host: &mut impl WorkHost) {
466 loop {
467 let events = host.take_events();
468 if events.is_empty() {
469 return;
470 }
471 for e in events {
472 if e.complete {
473 self.complete(e.work, e.citizen, e.param, host);
474 } else {
475 self.fail(e.work, e.citizen, e.param, host);
476 }
477 }
478 }
479 }
480
481 pub fn prioritize(&mut self, id: i32) {
485 let Some(work) = self.works.get(&id) else {
486 return;
487 };
488 let top = self
489 .priorities
490 .keys()
491 .next()
492 .map_or(0, |&p| p.wrapping_sub(1));
493 let old = work.priority;
494 if self.priorities.get(&old) == Some(&id) {
495 self.priorities.remove(&old);
496 }
497 self.works.get_mut(&id).unwrap().priority = top;
498 self.priorities.entry(top).or_insert(id);
499 }
500
501 pub fn change_profession(&mut self, id: i32, from: usize, to: usize) {
505 if !self.works.contains_key(&id) {
506 return;
507 }
508 let Some(set) = self.professions.get_mut(&from) else {
509 self.professions.entry(from).or_default();
510 return;
511 };
512 if !set.remove(&id) {
513 return;
514 }
515 self.professions.entry(to).or_default().insert(id);
516 self.works.get_mut(&id).unwrap().profession = to;
517 }
518
519 pub fn complete(&mut self, id: i32, citizen: usize, param: i32, host: &mut impl WorkHost) {
524 let Some(mut work) = self.works.remove(&id) else {
525 return;
526 };
527 let done = host.complete(&mut work, citizen, param);
528 self.works.insert(id, work);
529 self.drain(host);
530 if done {
531 self.remove(id, host);
532 }
533 }
534
535 pub fn fail(&mut self, id: i32, citizen: usize, param: i32, host: &mut impl WorkHost) {
539 if let Some(mut work) = self.works.remove(&id) {
540 host.fail(&mut work, citizen, param);
541 self.works.insert(id, work);
542 self.drain(host);
543 }
544 }
545
546 fn try_one(&mut self, id: i32, mut work: Work, citizen: usize, host: &mut impl WorkHost) -> u8 {
550 let mut r = host.try_assign(&mut work, citizen, self.time);
551 self.works.insert(id, work);
552 while r == NESTED {
553 let again = host.offers_again();
554 for other in host.take_offers() {
555 if self.offer_one(other, citizen, again, host) {
556 return 2;
557 }
558 }
559 let Some(mut work) = self.works.remove(&id) else {
560 return 1;
561 };
562 r = host.resume(&mut work, citizen);
563 self.works.insert(id, work);
564 }
565 r
566 }
567
568 pub fn offer_one(
572 &mut self,
573 id: i32,
574 citizen: usize,
575 again: bool,
576 host: &mut impl WorkHost,
577 ) -> bool {
578 let Some(mut work) = self.works.remove(&id) else {
579 return false;
580 };
581 let mut taken = false;
582 if work.failed_pass != self.pass {
583 host.refresh(&mut work);
584 match self.try_one(id, work, citizen, host) {
585 2 => taken = true,
586 1 => {
587 if again {
588 if let Some(mut work) = self.works.remove(&id) {
589 host.refresh(&mut work);
590 taken = host.try_assign_again(&mut work, citizen, self.time);
591 self.works.insert(id, work);
592 }
593 }
594 if !taken {
595 if let Some(w) = self.works.get_mut(&id) {
596 w.failed_pass = self.pass;
597 }
598 }
599 }
600 _ => {}
601 }
602 } else {
603 self.works.insert(id, work);
604 }
605 self.drain(host);
606 taken
607 }
608
609 pub fn assign_work(&mut self, citizen: usize, host: &mut impl WorkHost) {
613 if self.works.is_empty() {
614 return;
615 }
616 let laborer = host.laborer();
617 let profession = host.profession(citizen);
618 if profession != laborer {
619 if let Some(set) = self.professions.get(&profession).cloned() {
620 if self.try_list(citizen, &set, profession, false, false, host) {
621 return;
622 }
623 }
624 }
625 if !self.assign_emergency(citizen, host) {
626 if let Some(set) = self.professions.get(&laborer).cloned() {
627 self.try_list(citizen, &set, laborer, false, false, host);
628 }
629 }
630 }
631
632 pub fn assign_emergency(&mut self, citizen: usize, host: &mut impl WorkHost) -> bool {
637 let mut lists: Vec<&[u32]> = Vec::new();
638 let emergency = host.has_emergency(citizen);
639 if emergency {
640 lists.push(&[EMERGENCY]);
641 } else {
642 if host.profession(citizen) == host.builder() {
643 lists.push(&[CONSTRUCTION]);
644 }
645 for (slot, categories) in EMERGENCY_LISTS {
646 let (count, limit) = host.stock(slot);
647 if count <= limit / 10 {
648 lists.push(categories);
649 }
650 }
651 }
652 let k = host.rng().rand_below(lists.len() as u32) as usize;
653 let Some(list) = lists.get(k).copied() else {
654 return false;
655 };
656 let profession = host.profession(citizen);
657 for &c in list {
658 let set = self.categories[c as usize - 1].clone();
659 if self.try_list(citizen, &set, profession, true, emergency, host) {
660 return true;
661 }
662 }
663 false
664 }
665
666 fn try_list(
669 &mut self,
670 citizen: usize,
671 set: &BTreeSet<i32>,
672 profession: usize,
673 by_distance: bool,
674 emergency: bool,
675 host: &mut impl WorkHost,
676 ) -> bool {
677 let workplace = if emergency {
678 None
679 } else {
680 host.workplace(citizen)
681 };
682 if let Some(wp) = workplace {
683 if profession == host.profession(citizen) {
684 let order = self.sort(citizen, set, Some(wp), by_distance, host);
685 if self.offer(citizen, &order, host) {
686 return true;
687 }
688 }
689 }
690 if host.works_anywhere(profession) || emergency {
691 let order = self.sort(citizen, set, None, by_distance, host);
692 if self.offer(citizen, &order, host) {
693 return true;
694 }
695 }
696 false
697 }
698
699 fn sort(
702 &self,
703 citizen: usize,
704 set: &BTreeSet<i32>,
705 owner: Option<usize>,
706 by_distance: bool,
707 host: &mut impl WorkHost,
708 ) -> Vec<i32> {
709 let from = host.cell(citizen);
710 let mut keyed: Vec<(i32, i32)> = set
711 .iter()
712 .filter_map(|id| self.works.get(id))
713 .filter(|w| owner.is_none_or(|o| w.entity == o))
714 .map(|w| {
715 let key = if by_distance {
716 host.rank(from, w.cell)
717 } else {
718 w.priority
719 };
720 (key, w.id)
721 })
722 .collect();
723 keyed.sort();
724 keyed.dedup();
725 keyed.into_iter().map(|(_, id)| id).collect()
726 }
727
728 fn offer(&mut self, citizen: usize, order: &[i32], host: &mut impl WorkHost) -> bool {
730 for &id in order {
731 let Some(mut work) = self.works.remove(&id) else {
732 continue;
733 };
734 let mut taken = false;
735 if work.failed_pass != self.pass {
736 host.refresh(&mut work);
737 match self.try_one(id, work, citizen, host) {
738 2 => taken = true,
739 1 => {
740 if let Some(w) = self.works.get_mut(&id) {
741 w.failed_pass = self.pass;
742 }
743 }
744 _ => {}
745 }
746 } else {
747 self.works.insert(id, work);
748 }
749 self.drain(host);
750 if taken {
751 return true;
752 }
753 }
754 false
755 }
756}
757
758#[cfg(test)]
759mod tests {
760 use super::*;
761
762 struct Host {
764 rng: Random,
765 answers: BTreeMap<i32, u8>,
766 offered: Vec<i32>,
767 profession: usize,
768 workplace: Option<usize>,
769 stock: [(i32, i32); 21],
770 cells: BTreeMap<i32, i32>,
771 }
772
773 impl WorkHost for Host {
774 fn profession(&self, _: usize) -> usize {
775 self.profession
776 }
777 fn workplace(&self, _: usize) -> Option<usize> {
778 self.workplace
779 }
780 fn cell(&self, _: usize) -> i32 {
781 0
782 }
783 fn has_emergency(&self, _: usize) -> bool {
784 false
785 }
786 fn works_anywhere(&self, p: usize) -> bool {
787 p < 2
788 }
789 fn rank(&mut self, _: i32, b: i32) -> i32 {
790 self.cells.get(&b).copied().unwrap_or(b)
791 }
792 fn stock(&self, slot: usize) -> (i32, i32) {
793 self.stock[slot]
794 }
795 fn laborer(&self) -> usize {
796 0
797 }
798 fn builder(&self) -> usize {
799 1
800 }
801 fn rng(&mut self) -> &mut Random {
802 &mut self.rng
803 }
804 fn refresh(&mut self, _: &mut Work) {}
805 fn try_assign(&mut self, work: &mut Work, _: usize, _: i64) -> u8 {
806 self.offered.push(work.id);
807 self.answers.get(&work.id).copied().unwrap_or(1)
808 }
809 fn complete(&mut self, _: &mut Work, _: usize, _: i32) -> bool {
810 true
811 }
812 fn fail(&mut self, _: &mut Work, _: usize, _: i32) {}
813 fn cancel(&mut self, _: &mut Work) {}
814 }
815
816 fn host() -> Host {
817 Host {
818 rng: Random::new(7),
819 answers: BTreeMap::new(),
820 offered: Vec::new(),
821 profession: 0,
822 workplace: None,
823 stock: [(100, 100); 21],
824 cells: BTreeMap::new(),
825 }
826 }
827
828 #[test]
829 fn ids_priorities_and_offer_order() {
830 let mut jobs = Jobs::default();
831 let mut h = host();
832 let a = jobs.add(Work::new(5, 10, 0, WorkKind::Test(0)), 1 << 13, None);
833 let b = jobs.add(Work::new(6, 20, 0, WorkKind::Test(0)), 1 << 13, Some(0));
834 assert_eq!((a, b), (1, 2));
835 assert_eq!(jobs.works[&b].priority, -1);
837 jobs.assign_work(0, &mut h);
838 assert_eq!(h.offered, vec![b, a]);
839 h.offered.clear();
841 jobs.assign_work(0, &mut h);
842 assert!(h.offered.is_empty());
843 jobs.step(1);
844 h.answers.insert(a, 2);
845 jobs.assign_work(0, &mut h);
846 assert_eq!(h.offered, vec![b, a]);
847 jobs.remove(a, &mut h);
848 assert_eq!(jobs.works.len(), 1);
849 }
850
851 #[test]
852 fn builders_try_construction_by_distance() {
853 let mut jobs = Jobs::default();
854 let mut h = host();
855 h.profession = 1;
856 let near = jobs.add(Work::new(5, 30, 1, WorkKind::Test(0)), 1 << 13, None);
857 let far = jobs.add(Work::new(6, 3, 1, WorkKind::Test(0)), 1 << 13, None);
858 h.cells.insert(30, 50);
859 h.cells.insert(3, 40);
860 let mut check = Random::new(7);
862 jobs.assign_emergency(0, &mut h);
863 check.rand_below(1);
864 assert_eq!(h.offered, vec![far, near]);
865 assert_eq!(h.rng.rand(), check.rand());
866 }
867
868 #[test]
869 fn no_list_draws_nothing() {
870 let mut jobs = Jobs::default();
871 let mut h = host();
872 jobs.add(Work::new(5, 30, 0, WorkKind::Test(0)), 1 << 10, None);
873 assert!(!jobs.assign_emergency(0, &mut h));
874 assert_eq!(h.rng.rand(), Random::new(7).rand());
875 }
876}