banished_game/behaviour/agents/views.rs
1//! What the agents see of the town: the map sync, entity views and storages.
2
3use super::*;
4
5/// Entities placed during the update (piles, plantings) join the views; the original reads
6/// its entities live.
7pub(super) fn refresh_views(entities: &mut Vec<EntityView>, env: &Env) {
8 if entities.len() != env.placed.len() {
9 *entities = entity_views(env.placed, env.templates, env.construction);
10 }
11}
12
13/// The parts of each placed entity the activities read.
14pub fn entity_views(
15 placed: &[Placed],
16 templates: &[Template],
17 construction: &crate::work::construction::Construction,
18) -> Vec<EntityView> {
19 // Entities and sites are both in index order, so one pass walks the sites alongside the
20 // entities instead of looking each entity up in the map (28,000 lookups per call in a
21 // large town). The output is the same as looking each up.
22 let mut sites = construction.sites.iter().peekable();
23 placed
24 .iter()
25 .enumerate()
26 .map(|(e, p)| {
27 while sites.next_if(|(&k, _)| k < e).is_some() {}
28 let site = sites.next_if(|(&k, _)| k == e).map(|(_, s)| s);
29 let t = &templates[p.template];
30 let rect = t.map.as_ref().map(|_| {
31 let (w, h) = p.footprint;
32 (p.cell.0, p.cell.1, w, h)
33 });
34 // The entity's use points, then its MultiObject parts' (`FUN_140128540`,
35 // `FUN_1401282f0`), each turned by its own transform.
36 let parts = site.map_or(&[][..], |s| &s.parts[..]);
37 // Most entities (trees, rocks, piles) have no use points and no parts: skip the
38 // iterator chain for them. The result is the same empty list; in a town of 28,000
39 // entities the chain alone was a quarter of the simulation time.
40 let use_points = if parts.is_empty() && t.use_points.is_empty() {
41 Vec::new()
42 } else {
43 std::iter::once(e)
44 .chain(parts.iter().copied())
45 .filter_map(|x| placed.get(x))
46 .flat_map(|q| {
47 let qt = &templates[q.template];
48 let model = q.model(qt);
49 qt.use_points.iter().map(move |&l| model.apply(l))
50 })
51 .collect()
52 };
53 // `FUN_14004b080`: registered, and not the unhappy kind.
54 let happiness = site.and_then(|site| {
55 let d = construction.templates[site.template].happiness?;
56 (site.happiness_registered && d.kind != 5).then_some(d.pad)
57 });
58 EntityView {
59 alive: p.alive,
60 rect: if p.alive { rect } else { None },
61 use_points,
62 has_interact: t.has_interact,
63 position: p.position,
64 has_model: t.model.is_some(),
65 happiness,
66 }
67 })
68 .collect()
69}
70
71/// An entity's StorageComponent: a construction site's (houses, workplaces), else a town
72/// storage's.
73pub(super) fn storage_of(env: &Env, entity: usize) -> Option<usize> {
74 env.construction
75 .sites
76 .get(&entity)
77 .map(|s| s.storage)
78 .or_else(|| {
79 env.sites
80 .iter()
81 .find(|s| s.entity == entity)
82 .map(|s| s.storage)
83 })
84}
85
86impl Agents {
87 /// The path sectors' marks in the order the cells flipped (`Cells::set_type`, made on the
88 /// spot by `FUN_1400ae160` in the original: a rebuilt sector hands its node ids out in
89 /// mark order); then `FUN_1400a1950` and the path grid's type commands where the map
90 /// changed.
91 pub(super) fn sync_map(&mut self, cells: &Cells) {
92 for &i in cells
93 .sector_marks
94 .get(self.marks_seen..)
95 .unwrap_or_default()
96 {
97 let i = i as usize;
98 self.sectors
99 .mark((i % cells.size) as i32, (i / cells.size) as i32);
100 }
101 self.marks_seen = cells.sector_marks.len();
102 // Comparing the whole map (262,144 cells on a large map) six times an iteration was
103 // the largest single cost in a large town; the revision says whether anything changed.
104 if self.types_seen == cells.types_revision {
105 return;
106 }
107 self.types_seen = cells.types_revision;
108 if self.region_types == cells.types {
109 return;
110 }
111 // The regions depend only on which cells are walkable (`regions`), so the map-wide
112 // fill runs again only when a change makes a cell walkable or not walkable. In a large
113 // town fields, roads and plantings change cell types often without that.
114 let mut walkability_changed = false;
115 for (i, (old, new)) in self.region_types.iter_mut().zip(&cells.types).enumerate() {
116 if *old != *new {
117 self.pathfinder.set_type(i, *new);
118 walkability_changed |=
119 crate::pathfind::walkable(*old) != crate::pathfind::walkable(*new);
120 *old = *new;
121 }
122 }
123 if walkability_changed {
124 self.regions = regions(cells.size, &cells.types);
125 }
126 }
127}