use anyhow::Result;
use abstutil::Timer;
use map_model::RoadFilter;
use map_model::RoadID;
use widgetry::{Choice, EventCtx};
use crate::{redraw_all_icons, App, Neighbourhood};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AutoFilterHeuristic {
Greedy,
BruteForce,
SplitCells,
OnlyOneBorder,
}
impl AutoFilterHeuristic {
pub fn choices() -> Vec<Choice<Self>> {
vec![
Choice::new(
"filter the road with the most shortcuts (greedy)",
AutoFilterHeuristic::Greedy,
),
Choice::new(
"stop the most shortcuts (brute-force)",
AutoFilterHeuristic::BruteForce,
),
Choice::new("split large cells", AutoFilterHeuristic::SplitCells),
Choice::new(
"only one entrance per cell",
AutoFilterHeuristic::OnlyOneBorder,
),
]
}
pub fn apply(
self,
ctx: &mut EventCtx,
app: &mut App,
neighbourhood: &Neighbourhood,
timer: &mut Timer,
) -> Result<()> {
if neighbourhood
.cells
.iter()
.filter(|c| c.is_disconnected())
.count()
!= 0
{
bail!("This neighbourhood has a disconnected cell; fix that first");
}
match self {
AutoFilterHeuristic::Greedy => greedy(app, neighbourhood),
AutoFilterHeuristic::BruteForce => brute_force(app, neighbourhood, timer),
AutoFilterHeuristic::SplitCells => split_cells(app, neighbourhood, timer),
AutoFilterHeuristic::OnlyOneBorder => only_one_border(app, neighbourhood),
}
let empty = false;
redraw_all_icons(ctx, app);
if empty {
bail!("No new filters created");
} else {
Ok(())
}
}
}
fn greedy(app: &mut App, neighbourhood: &Neighbourhood) {
if let Some((r, _)) = neighbourhood
.shortcuts
.count_per_road
.borrow()
.iter()
.max_by_key(|pair| pair.1)
{
if try_to_filter_road(app, neighbourhood, *r).is_none() {
warn!("Filtering {} disconnects a cell, never mind", r);
}
}
}
fn brute_force(app: &mut App, neighbourhood: &Neighbourhood, timer: &mut Timer) {
let mut best: Option<(RoadID, usize)> = None;
let orig_filters = app.per_map.map.all_roads_with_modal_filter().count();
timer.start_iter(
"evaluate candidate filters",
neighbourhood.interior_roads.len(),
);
for r in &neighbourhood.interior_roads {
timer.next();
if app.per_map.map.get_r(*r).modal_filter.is_some() {
continue;
}
if let Some(new) = try_to_filter_road(app, neighbourhood, *r) {
let num_shortcuts = new.shortcuts.paths.len();
if best.map(|(_, score)| num_shortcuts < score).unwrap_or(true) {
best = Some((*r, num_shortcuts));
}
remove_filter(app, *r);
}
assert_eq!(
orig_filters,
app.per_map.map.all_roads_with_modal_filter().count()
);
}
if let Some((r, _)) = best {
try_to_filter_road(app, neighbourhood, r).unwrap();
}
}
fn split_cells(app: &mut App, neighbourhood: &Neighbourhood, timer: &mut Timer) {
let mut best: Option<(RoadID, usize)> = None;
let orig_filters = app.per_map.map.all_roads_with_modal_filter().count();
timer.start_iter(
"evaluate candidate filters",
neighbourhood.interior_roads.len(),
);
for r in &neighbourhood.interior_roads {
timer.next();
if app.per_map.map.get_r(*r).modal_filter.is_some() {
continue;
}
if let Some(new) = try_to_filter_road(app, neighbourhood, *r) {
if new.cells.len() > neighbourhood.cells.len() {
let split_cells: Vec<_> = new
.cells
.iter()
.filter(|cell| cell.roads.contains_key(r))
.collect();
if split_cells.len() == 2 {
let new_score = split_cells[0].roads.len().min(split_cells[1].roads.len());
if best
.map(|(_, old_score)| new_score > old_score)
.unwrap_or(true)
{
best = Some((*r, new_score));
}
} else {
warn!("The split cell heuristic wound up with an unexpected number of cells after trying to split {r}; skipping");
}
}
remove_filter(app, *r);
}
assert_eq!(
orig_filters,
app.per_map.map.all_roads_with_modal_filter().count()
);
}
if let Some((r, _)) = best {
try_to_filter_road(app, neighbourhood, r).unwrap();
}
}
fn only_one_border(app: &mut App, neighbourhood: &Neighbourhood) {
for cell in &neighbourhood.cells {
if cell.borders.len() > 1 {
for i in cell.borders.iter().skip(1) {
for r in cell.roads.keys() {
let road = app.per_map.map.get_r(*r);
if road.src_i == *i {
add_filter(app, *r, 0.1);
break;
} else if road.dst_i == *i {
add_filter(app, *r, 0.9);
break;
}
}
}
}
}
}
fn try_to_filter_road(
app: &mut App,
neighbourhood: &Neighbourhood,
r: RoadID,
) -> Option<Neighbourhood> {
add_filter(app, r, 0.5);
let new_neighbourhood = Neighbourhood::new(app, neighbourhood.id);
if new_neighbourhood.cells.iter().any(|c| c.is_disconnected()) {
remove_filter(app, r);
None
} else {
Some(new_neighbourhood)
}
}
fn add_filter(app: &mut App, r: RoadID, pct: f64) {
let map = &app.per_map.map;
let mut edits = map.get_edits().clone();
let road = map.get_r(r);
edits.commands.push(map.edit_road_cmd(r, |new| {
new.modal_filter = Some(RoadFilter::new(
pct * road.length(),
app.session.filter_type,
));
}));
app.apply_edits(edits);
}
fn remove_filter(app: &mut App, r: RoadID) {
let map = &app.per_map.map;
let mut edits = map.get_edits().clone();
edits.commands.push(map.edit_road_cmd(r, |new| {
new.modal_filter = None;
}));
app.apply_edits(edits);
}