1use anyhow::Result;
5
6use abstutil::Timer;
7use map_model::RoadFilter;
8use map_model::RoadID;
9use widgetry::{Choice, EventCtx};
10
11use crate::{redraw_all_icons, App, Neighbourhood};
12
13#[derive(Clone, Copy, Debug, PartialEq)]
14pub enum AutoFilterHeuristic {
15 Greedy,
21 BruteForce,
24 SplitCells,
26 OnlyOneBorder,
29}
30
31impl AutoFilterHeuristic {
32 pub fn choices() -> Vec<Choice<Self>> {
33 vec![
34 Choice::new(
35 "filter the road with the most shortcuts (greedy)",
36 AutoFilterHeuristic::Greedy,
37 ),
38 Choice::new(
39 "stop the most shortcuts (brute-force)",
40 AutoFilterHeuristic::BruteForce,
41 ),
42 Choice::new("split large cells", AutoFilterHeuristic::SplitCells),
43 Choice::new(
44 "only one entrance per cell",
45 AutoFilterHeuristic::OnlyOneBorder,
46 ),
47 ]
48 }
49
50 pub fn apply(
51 self,
52 ctx: &mut EventCtx,
53 app: &mut App,
54 neighbourhood: &Neighbourhood,
55 timer: &mut Timer,
56 ) -> Result<()> {
57 if neighbourhood
58 .cells
59 .iter()
60 .filter(|c| c.is_disconnected())
61 .count()
62 != 0
63 {
64 bail!("This neighbourhood has a disconnected cell; fix that first");
65 }
66
67 match self {
70 AutoFilterHeuristic::Greedy => greedy(app, neighbourhood),
71 AutoFilterHeuristic::BruteForce => brute_force(app, neighbourhood, timer),
72 AutoFilterHeuristic::SplitCells => split_cells(app, neighbourhood, timer),
73 AutoFilterHeuristic::OnlyOneBorder => only_one_border(app, neighbourhood),
74 }
75
76 let empty = false;
78 redraw_all_icons(ctx, app);
79 if empty {
80 bail!("No new filters created");
81 } else {
82 Ok(())
83 }
84 }
85}
86
87fn greedy(app: &mut App, neighbourhood: &Neighbourhood) {
88 if let Some((r, _)) = neighbourhood
92 .shortcuts
93 .count_per_road
94 .borrow()
95 .iter()
96 .max_by_key(|pair| pair.1)
97 {
98 if try_to_filter_road(app, neighbourhood, *r).is_none() {
99 warn!("Filtering {} disconnects a cell, never mind", r);
100 }
102 }
103}
104
105fn brute_force(app: &mut App, neighbourhood: &Neighbourhood, timer: &mut Timer) {
106 let mut best: Option<(RoadID, usize)> = None;
108
109 let orig_filters = app.per_map.map.all_roads_with_modal_filter().count();
110 timer.start_iter(
111 "evaluate candidate filters",
112 neighbourhood.interior_roads.len(),
113 );
114 for r in &neighbourhood.interior_roads {
115 timer.next();
116 if app.per_map.map.get_r(*r).modal_filter.is_some() {
117 continue;
118 }
119 if let Some(new) = try_to_filter_road(app, neighbourhood, *r) {
120 let num_shortcuts = new.shortcuts.paths.len();
121 if best.map(|(_, score)| num_shortcuts < score).unwrap_or(true) {
123 best = Some((*r, num_shortcuts));
124 }
125 remove_filter(app, *r);
127 }
128
129 assert_eq!(
130 orig_filters,
131 app.per_map.map.all_roads_with_modal_filter().count()
132 );
133 }
134
135 if let Some((r, _)) = best {
136 try_to_filter_road(app, neighbourhood, r).unwrap();
137 }
138}
139
140fn split_cells(app: &mut App, neighbourhood: &Neighbourhood, timer: &mut Timer) {
141 let mut best: Option<(RoadID, usize)> = None;
143
144 let orig_filters = app.per_map.map.all_roads_with_modal_filter().count();
145 timer.start_iter(
146 "evaluate candidate filters",
147 neighbourhood.interior_roads.len(),
148 );
149 for r in &neighbourhood.interior_roads {
150 timer.next();
151 if app.per_map.map.get_r(*r).modal_filter.is_some() {
152 continue;
153 }
154 if let Some(new) = try_to_filter_road(app, neighbourhood, *r) {
155 if new.cells.len() > neighbourhood.cells.len() {
157 let split_cells: Vec<_> = new
159 .cells
160 .iter()
161 .filter(|cell| cell.roads.contains_key(r))
162 .collect();
163 if split_cells.len() == 2 {
164 let new_score = split_cells[0].roads.len().min(split_cells[1].roads.len());
167 if best
168 .map(|(_, old_score)| new_score > old_score)
169 .unwrap_or(true)
170 {
171 best = Some((*r, new_score));
172 }
173 } else {
174 warn!("The split cell heuristic wound up with an unexpected number of cells after trying to split {r}; skipping");
177 }
178 }
179 remove_filter(app, *r);
181 }
182
183 assert_eq!(
184 orig_filters,
185 app.per_map.map.all_roads_with_modal_filter().count()
186 );
187 }
188
189 if let Some((r, _)) = best {
190 try_to_filter_road(app, neighbourhood, r).unwrap();
191 }
192}
193
194fn only_one_border(app: &mut App, neighbourhood: &Neighbourhood) {
195 for cell in &neighbourhood.cells {
196 if cell.borders.len() > 1 {
197 for i in cell.borders.iter().skip(1) {
199 for r in cell.roads.keys() {
201 let road = app.per_map.map.get_r(*r);
202 if road.src_i == *i {
203 add_filter(app, *r, 0.1);
204 break;
205 } else if road.dst_i == *i {
206 add_filter(app, *r, 0.9);
207 break;
208 }
209 }
210 }
211 }
212 }
213}
214
215fn try_to_filter_road(
218 app: &mut App,
219 neighbourhood: &Neighbourhood,
220 r: RoadID,
221) -> Option<Neighbourhood> {
222 add_filter(app, r, 0.5);
223 let new_neighbourhood = Neighbourhood::new(app, neighbourhood.id);
224 if new_neighbourhood.cells.iter().any(|c| c.is_disconnected()) {
225 remove_filter(app, r);
226 None
227 } else {
228 Some(new_neighbourhood)
229 }
230}
231
232fn add_filter(app: &mut App, r: RoadID, pct: f64) {
233 let map = &app.per_map.map;
234 let mut edits = map.get_edits().clone();
235 let road = map.get_r(r);
236 edits.commands.push(map.edit_road_cmd(r, |new| {
237 new.modal_filter = Some(RoadFilter::new(
238 pct * road.length(),
239 app.session.filter_type,
240 ));
241 }));
242 app.apply_edits(edits);
243}
244
245fn remove_filter(app: &mut App, r: RoadID) {
246 let map = &app.per_map.map;
247 let mut edits = map.get_edits().clone();
248 edits.commands.push(map.edit_road_cmd(r, |new| {
249 new.modal_filter = None;
250 }));
251 app.apply_edits(edits);
252}