use std::cell::RefCell;
use std::collections::BTreeMap;
use anyhow::Result;
use maplit::btreemap;
use rand::seq::{IteratorRandom, SliceRandom};
use abstio::MapName;
use abstutil::Timer;
use geom::{Bounds, Circle, Distance, Duration, Polygon, Pt2D, Time};
use map_gui::colors::ColorScheme;
use map_gui::load::FileLoader;
use map_gui::options::Options;
use map_gui::render::{unzoomed_agent_radius, AgentCache, DrawMap, DrawOptions, Renderable};
use map_gui::tools::CameraState;
use map_gui::ID;
use map_model::AreaType;
use map_model::{IntersectionID, LaneID, Map, Traversable};
use sim::{AgentID, Analytics, Scenario, Sim, SimCallback, SimFlags, VehicleType};
use widgetry::{Canvas, EventCtx, GfxCtx, Prerender, SharedAppState, State};
use crate::challenges::HighScore;
use crate::common::Warping;
use crate::edit::apply_map_edits;
use crate::layer::Layer;
use crate::sandbox::{GameplayMode, TutorialState};
pub type Transition = widgetry::Transition<App>;
pub struct App {
pub primary: PerMap,
pub cs: ColorScheme,
pub opts: Options,
pub per_obj: PerObjectActions,
pub session: SessionState,
}
impl App {
pub fn has_prebaked(&self) -> Option<(&MapName, &String)> {
self.primary.prebaked.as_ref().map(|(m, s, _)| (m, s))
}
pub fn prebaked(&self) -> &Analytics {
&self.primary.prebaked.as_ref().unwrap().2
}
pub fn set_prebaked(&mut self, prebaked: Option<(MapName, String, Analytics)>) {
self.primary.prebaked = prebaked;
if false {
if let Some((_, _, ref a)) = self.primary.prebaked {
use abstutil::{prettyprint_usize, serialized_size_bytes};
println!(
"- road_thruput: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.road_thruput))
);
println!(
"- intersection_thruput: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.intersection_thruput))
);
println!(
"- traffic_signal_thruput: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.traffic_signal_thruput))
);
println!(
"- demand : {} bytes",
prettyprint_usize(serialized_size_bytes(&a.demand))
);
println!(
"- bus_arrivals : {} bytes",
prettyprint_usize(serialized_size_bytes(&a.bus_arrivals))
);
println!(
"- passengers_boarding: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.passengers_boarding))
);
println!(
"- passengers_alighting: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.passengers_alighting))
);
println!(
"- started_trips: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.started_trips))
);
println!(
"- finished_trips: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.finished_trips))
);
println!(
"- trip_log: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.trip_log))
);
println!(
"- intersection_delays: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.intersection_delays))
);
println!(
"- parking_lane_changes: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.parking_lane_changes))
);
println!(
"- parking_lot_changes: {} bytes",
prettyprint_usize(serialized_size_bytes(&a.parking_lot_changes))
);
}
}
}
pub fn draw(&self, g: &mut GfxCtx, opts: DrawOptions, show_objs: &dyn ShowObject) {
let map = &self.primary.map;
let draw_map = &self.primary.draw_map;
let mut sample_intersection: Option<String> = None;
g.clear(self.cs.void_background);
g.redraw(&draw_map.boundary_polygon);
if g.canvas.cam_zoom < self.opts.min_zoom_for_detail {
let layers = show_objs.layers();
if layers.show_areas {
g.redraw(&draw_map.draw_all_areas);
}
if layers.show_parking_lots {
g.redraw(&draw_map.draw_all_unzoomed_parking_lots);
}
if layers.show_intersections || layers.show_lanes {
g.redraw(
&self
.primary
.draw_map
.draw_all_unzoomed_roads_and_intersections,
);
}
if layers.show_buildings {
g.redraw(&draw_map.draw_all_buildings);
}
if let Some(ID::Area(id)) = self.primary.current_selection {
g.draw_polygon(self.cs.selected, draw_map.get_a(id).get_outline(map));
} else if let Some(ID::Road(id)) = self.primary.current_selection {
g.draw_polygon(self.cs.selected, draw_map.get_r(id).get_outline(map));
} else if let Some(ID::Intersection(id)) = self.primary.current_selection {
g.draw_polygon(self.cs.selected, map.get_i(id).polygon.clone());
} else if let Some(ID::Building(id)) = self.primary.current_selection {
g.draw_polygon(self.cs.selected, map.get_b(id).polygon.clone());
}
let mut cache = self.primary.agents.borrow_mut();
cache.draw_unzoomed_agents(g, self);
if let Some(a) = self
.primary
.current_selection
.as_ref()
.and_then(|id| id.agent_id())
{
if let Some(pt) = self.primary.sim.canonical_pt_for_agent(a, map) {
g.draw_polygon(
self.cs.selected,
Circle::new(pt, unzoomed_agent_radius(a.to_vehicle_type())).to_polygon(),
);
}
}
} else {
let mut cache = self.primary.agents.borrow_mut();
let objects = self.get_renderables_back_to_front(
g.get_screen_bounds(),
&g.prerender,
&mut cache,
show_objs,
);
let mut drawn_all_buildings = false;
let mut drawn_all_areas = false;
for obj in objects {
obj.draw(g, self, &opts);
match obj.get_id() {
ID::Building(_) => {
if !drawn_all_buildings {
if opts.show_building_driveways {
g.redraw(&draw_map.draw_all_building_driveways);
}
g.redraw(&draw_map.draw_all_buildings);
g.redraw(&draw_map.draw_all_building_outlines);
drawn_all_buildings = true;
}
}
ID::Area(_) => {
if !drawn_all_areas {
g.redraw(&draw_map.draw_all_areas);
drawn_all_areas = true;
}
}
_ => {}
}
if self.primary.current_selection == Some(obj.get_id()) {
g.draw_polygon(self.cs.selected, obj.get_outline(map));
}
if g.is_screencap() && sample_intersection.is_none() {
if let ID::Intersection(id) = obj.get_id() {
sample_intersection = Some(format!("_i{}", id.0));
}
}
}
}
if let Some(i) = sample_intersection {
g.set_screencap_naming_hint(i);
}
}
pub fn recalculate_current_selection(&mut self, ctx: &EventCtx) {
self.primary.current_selection =
self.calculate_current_selection(ctx, &ShowEverything::new(), false, false, false);
}
pub fn mouseover_unzoomed_roads_and_intersections(&self, ctx: &EventCtx) -> Option<ID> {
self.calculate_current_selection(ctx, &ShowEverything::new(), false, true, false)
}
pub fn mouseover_unzoomed_intersections(&self, ctx: &EventCtx) -> Option<ID> {
self.calculate_current_selection(ctx, &ShowEverything::new(), false, true, false)
.filter(|id| matches!(id, ID::Intersection(_)))
}
pub fn mouseover_unzoomed_buildings(&self, ctx: &EventCtx) -> Option<ID> {
self.calculate_current_selection(ctx, &ShowEverything::new(), false, false, true)
}
pub fn mouseover_unzoomed_everything(&self, ctx: &EventCtx) -> Option<ID> {
self.calculate_current_selection(ctx, &ShowEverything::new(), false, true, true)
}
pub fn mouseover_debug_mode(&self, ctx: &EventCtx, show_objs: &dyn ShowObject) -> Option<ID> {
self.calculate_current_selection(ctx, show_objs, true, false, false)
}
fn calculate_current_selection(
&self,
ctx: &EventCtx,
show_objs: &dyn ShowObject,
debug_mode: bool,
unzoomed_roads_and_intersections: bool,
unzoomed_buildings: bool,
) -> Option<ID> {
let unzoomed = ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail;
if unzoomed && !(debug_mode || unzoomed_roads_and_intersections || unzoomed_buildings) {
return None;
}
let pt = ctx.canvas.get_cursor_in_map_space()?;
let mut cache = self.primary.agents.borrow_mut();
let mut objects = self.get_renderables_back_to_front(
Circle::new(pt, Distance::meters(3.0)).get_bounds(),
ctx.prerender,
&mut cache,
show_objs,
);
objects.reverse();
let mut small_agents: Vec<(ID, Pt2D)> = Vec::new();
for obj in objects {
let id = obj.get_id();
match id {
ID::Area(_) => {
if !debug_mode {
continue;
}
}
ID::Road(_) => {
if !unzoomed_roads_and_intersections || !unzoomed {
continue;
}
}
ID::Intersection(_) => {
if unzoomed && !unzoomed_roads_and_intersections {
continue;
}
}
ID::Building(_) => {
if unzoomed && !unzoomed_buildings {
continue;
}
}
_ => {
if unzoomed {
continue;
}
}
}
if obj.contains_pt(pt, &self.primary.map) {
if unzoomed {
return Some(id);
}
match id {
ID::Pedestrian(_) => {}
ID::Car(c) => {
if c.vehicle_type != VehicleType::Bike {
return Some(id);
}
}
_ => {
if small_agents.is_empty() {
return Some(id);
} else {
continue;
}
}
}
small_agents.push((id, obj.get_outline(&self.primary.map).center()));
}
}
let (id, _) = small_agents
.into_iter()
.min_by_key(|(_, center)| center.fast_dist(pt))?;
Some(id)
}
fn get_renderables_back_to_front<'a>(
&'a self,
bounds: Bounds,
prerender: &Prerender,
agents: &'a mut AgentCache,
show_objs: &dyn ShowObject,
) -> Vec<&'a (dyn Renderable + 'a)> {
let map = &self.primary.map;
let draw_map = &self.primary.draw_map;
let mut areas: Vec<&dyn Renderable> = Vec::new();
let mut parking_lots: Vec<&dyn Renderable> = Vec::new();
let mut lanes: Vec<&dyn Renderable> = Vec::new();
let mut roads: Vec<&dyn Renderable> = Vec::new();
let mut intersections: Vec<&dyn Renderable> = Vec::new();
let mut buildings: Vec<&dyn Renderable> = Vec::new();
let mut bus_stops: Vec<&dyn Renderable> = Vec::new();
let mut agents_on: Vec<Traversable> = Vec::new();
for id in draw_map.get_matching_objects(bounds) {
if !show_objs.show(&id) {
continue;
}
match id {
ID::Area(id) => areas.push(draw_map.get_a(id)),
ID::Lane(id) => {
lanes.push(draw_map.get_l(id));
agents_on.push(Traversable::Lane(id));
for bs in &map.get_l(id).bus_stops {
if show_objs.show(&ID::BusStop(*bs)) {
bus_stops.push(draw_map.get_bs(*bs));
}
}
}
ID::Road(id) => {
roads.push(draw_map.get_r(id));
}
ID::Intersection(id) => {
intersections.push(draw_map.get_i(id));
for t in &map.get_i(id).turns {
agents_on.push(Traversable::Turn(*t));
}
}
ID::Building(id) => buildings.push(draw_map.get_b(id)),
ID::ParkingLot(id) => {
parking_lots.push(draw_map.get_pl(id));
agents_on.push(Traversable::Lane(map.get_pl(id).driving_pos.lane()));
}
ID::BusStop(_) | ID::Car(_) | ID::Pedestrian(_) | ID::PedCrowd(_) => {
panic!("{:?} shouldn't be in the quadtree", id)
}
}
}
let mut borrows: Vec<&dyn Renderable> = Vec::new();
borrows.extend(areas);
borrows.extend(parking_lots);
borrows.extend(lanes);
borrows.extend(roads);
borrows.extend(intersections);
borrows.extend(buildings);
borrows.extend(bus_stops);
{
for on in &agents_on {
agents.populate_if_needed(*on, map, &self.primary.sim, &self.cs, prerender);
}
}
for on in agents_on {
for obj in agents.get(on) {
borrows.push(obj);
}
}
borrows.retain(|x| x.get_zorder() <= self.primary.draw_map.show_zorder);
borrows.sort_by_key(|x| x.get_zorder());
borrows
}
pub fn clear_everything(&mut self, ctx: &mut EventCtx) {
ctx.loading_screen("reset map and sim", |ctx, mut timer| {
apply_map_edits(ctx, self, self.primary.map.new_edits());
self.primary
.map
.recalculate_pathfinding_after_edits(&mut timer);
self.primary.clear_sim();
self.set_prebaked(None);
});
}
}
impl App {
pub fn click_on_intersection<S: Into<String>>(
&mut self,
ctx: &mut EventCtx,
label: S,
) -> Option<IntersectionID> {
if let Some(ID::Intersection(i)) = self.primary.current_selection {
if self.per_obj.left_click(ctx, label) {
return Some(i);
}
}
None
}
pub fn click_on_lane<S: Into<String>>(
&mut self,
ctx: &mut EventCtx,
label: S,
) -> Option<LaneID> {
if let Some(ID::Lane(l)) = self.primary.current_selection {
if self.per_obj.left_click(ctx, label) {
return Some(l);
}
}
None
}
}
impl map_gui::AppLike for App {
#[inline]
fn map(&self) -> &Map {
&self.primary.map
}
#[inline]
fn sim(&self) -> &Sim {
&self.primary.sim
}
#[inline]
fn cs(&self) -> &ColorScheme {
&self.cs
}
#[inline]
fn mut_cs(&mut self) -> &mut ColorScheme {
&mut self.cs
}
#[inline]
fn draw_map(&self) -> &DrawMap {
&self.primary.draw_map
}
#[inline]
fn mut_draw_map(&mut self) -> &mut DrawMap {
&mut self.primary.draw_map
}
#[inline]
fn opts(&self) -> &Options {
&self.opts
}
#[inline]
fn mut_opts(&mut self) -> &mut Options {
&mut self.opts
}
fn map_switched(&mut self, ctx: &mut EventCtx, map: Map, timer: &mut Timer) {
let sim = Sim::new(&map, self.primary.current_flags.sim_flags.opts.clone());
CameraState::save(ctx.canvas, self.primary.map.get_name());
self.primary = PerMap::map_loaded(
map,
sim,
self.primary.current_flags.clone(),
&self.opts,
&self.cs,
ctx,
timer,
);
self.primary.init_camera_for_loaded_map(ctx, false);
}
fn draw_with_opts(&self, g: &mut GfxCtx, opts: DrawOptions) {
self.draw(g, opts, &ShowEverything::new());
}
fn make_warper(
&mut self,
ctx: &EventCtx,
pt: Pt2D,
target_cam_zoom: Option<f64>,
id: Option<ID>,
) -> Box<dyn State<App>> {
Warping::new_state(ctx, pt, target_cam_zoom, id, &mut self.primary)
}
}
pub struct ShowLayers {
pub show_buildings: bool,
pub show_parking_lots: bool,
pub show_intersections: bool,
pub show_lanes: bool,
pub show_areas: bool,
pub show_labels: bool,
}
impl ShowLayers {
pub fn new() -> ShowLayers {
ShowLayers {
show_buildings: true,
show_parking_lots: true,
show_intersections: true,
show_lanes: true,
show_areas: true,
show_labels: false,
}
}
}
pub trait ShowObject {
fn show(&self, obj: &ID) -> bool;
fn layers(&self) -> &ShowLayers;
}
pub struct ShowEverything {
layers: ShowLayers,
}
impl ShowEverything {
pub fn new() -> ShowEverything {
ShowEverything {
layers: ShowLayers::new(),
}
}
}
impl ShowObject for ShowEverything {
fn show(&self, _: &ID) -> bool {
true
}
fn layers(&self) -> &ShowLayers {
&self.layers
}
}
#[derive(Clone)]
pub struct Flags {
pub sim_flags: SimFlags,
pub live_map_edits: bool,
pub study_area: Option<String>,
}
pub struct PerMap {
pub map: Map,
pub draw_map: DrawMap,
pub sim: Sim,
pub agents: RefCell<AgentCache>,
pub current_selection: Option<ID>,
pub current_flags: Flags,
pub last_warped_from: Option<(Pt2D, f64)>,
pub sim_cb: Option<Box<dyn SimCallback>>,
pub dirty_from_edits: bool,
pub has_modified_trips: bool,
pub unedited_map: RefCell<Option<Map>>,
pub layer: Option<Box<dyn Layer>>,
pub suspended_sim: Option<Sim>,
prebaked: Option<(MapName, String, Analytics)>,
pub scenario: Option<Scenario>,
}
impl PerMap {
pub fn map_loaded(
mut map: Map,
sim: Sim,
flags: Flags,
opts: &Options,
cs: &ColorScheme,
ctx: &mut EventCtx,
timer: &mut Timer,
) -> PerMap {
if let Some(ref name) = flags.study_area {
if let Err(err) = add_study_area(&mut map, name) {
error!("Didn't apply study area {}: {}", name, err);
}
}
timer.start("draw_map");
let draw_map = DrawMap::new(ctx, &map, opts, cs, timer);
timer.stop("draw_map");
PerMap {
map,
draw_map,
sim,
agents: RefCell::new(AgentCache::new_state()),
current_selection: None,
current_flags: flags,
last_warped_from: None,
sim_cb: None,
dirty_from_edits: false,
has_modified_trips: false,
unedited_map: RefCell::new(None),
layer: None,
suspended_sim: None,
prebaked: None,
scenario: None,
}
}
pub fn init_camera_for_loaded_map(&mut self, ctx: &mut EventCtx, splash: bool) {
let mut rng = self.current_flags.sim_flags.make_rng();
let rand_focus_pt = self
.map
.all_buildings()
.choose(&mut rng)
.and_then(|b| self.canonical_point(ID::Building(b.id)))
.or_else(|| {
self.map
.all_lanes()
.keys()
.choose(&mut rng)
.and_then(|l| self.canonical_point(ID::Lane(*l)))
})
.unwrap_or_else(|| self.map.get_bounds().center());
if splash {
ctx.canvas.center_on_map_pt(rand_focus_pt);
} else if !CameraState::load(ctx, self.map.get_name()) {
info!("Couldn't load camera state, just focusing on an arbitrary building");
ctx.canvas.center_on_map_pt(rand_focus_pt);
}
}
pub fn clear_sim(&mut self) -> Sim {
self.dirty_from_edits = false;
std::mem::replace(
&mut self.sim,
Sim::new(&self.map, self.current_flags.sim_flags.opts.clone()),
)
}
pub fn calculate_unedited_map(&self, ctx: &mut EventCtx) -> Option<Transition> {
if self.map.get_edits().commands.is_empty() || self.unedited_map.borrow().is_some() {
return None;
}
Some(Transition::Push(FileLoader::<App, Map>::new_state(
ctx,
self.map.get_name().path(),
Box::new(|_, app, _, map| {
let map = map.unwrap();
*app.primary.unedited_map.borrow_mut() = Some(map);
Transition::Pop
}),
)))
}
pub fn canonical_point(&self, id: ID) -> Option<Pt2D> {
match id {
ID::Road(id) => self.map.maybe_get_r(id).map(|r| r.center_pts.first_pt()),
ID::Lane(id) => self.map.maybe_get_l(id).map(|l| l.first_pt()),
ID::Intersection(id) => self.map.maybe_get_i(id).map(|i| i.polygon.center()),
ID::Building(id) => self.map.maybe_get_b(id).map(|b| b.polygon.center()),
ID::ParkingLot(id) => self.map.maybe_get_pl(id).map(|pl| pl.polygon.center()),
ID::Car(id) => self.sim.canonical_pt_for_agent(AgentID::Car(id), &self.map),
ID::Pedestrian(id) => self
.sim
.canonical_pt_for_agent(AgentID::Pedestrian(id), &self.map),
ID::PedCrowd(ref members) => self
.sim
.canonical_pt_for_agent(AgentID::Pedestrian(members[0]), &self.map),
ID::BusStop(id) => self
.map
.maybe_get_bs(id)
.map(|bs| bs.sidewalk_pos.pt(&self.map)),
ID::Area(id) => self.map.maybe_get_a(id).map(|a| a.polygon.center()),
}
}
}
pub struct SessionState {
pub tutorial: Option<TutorialState>,
pub high_scores: BTreeMap<GameplayMode, Vec<HighScore>>,
pub info_panel_tab: BTreeMap<&'static str, &'static str>,
pub last_gmns_timing_csv: Option<String>,
}
impl SessionState {
pub fn empty() -> SessionState {
SessionState {
tutorial: None,
high_scores: BTreeMap::new(),
info_panel_tab: btreemap! {
"lane" => "info",
"intersection" => "info",
"bldg" => "info",
"person" => "trips",
"bus" => "status",
},
last_gmns_timing_csv: None,
}
}
}
pub struct PerObjectActions {
pub click_action: Option<String>,
}
impl PerObjectActions {
pub fn new() -> PerObjectActions {
PerObjectActions { click_action: None }
}
pub fn reset(&mut self) {
self.click_action = None;
}
pub fn left_click<S: Into<String>>(&mut self, ctx: &mut EventCtx, label: S) -> bool {
assert!(self.click_action.is_none());
self.click_action = Some(label.into());
ctx.normal_left_click()
}
}
pub struct FindDelayedIntersections {
pub halt_limit: Duration,
pub report_limit: Duration,
pub currently_delayed: Vec<(IntersectionID, Time)>,
}
impl SimCallback for FindDelayedIntersections {
fn run(&mut self, sim: &Sim, _: &Map) -> bool {
self.currently_delayed = sim.delayed_intersections(self.report_limit);
if let Some((_, t)) = self.currently_delayed.get(0) {
sim.time() - *t >= self.halt_limit
} else {
false
}
}
}
impl SharedAppState for App {
fn before_event(&mut self) {
self.per_obj.reset();
}
fn draw_default(&self, g: &mut GfxCtx) {
self.draw(g, DrawOptions::new(), &ShowEverything::new());
}
fn dump_before_abort(&self, canvas: &Canvas) {
println!();
println!(
"********************************************************************************"
);
CameraState::save(canvas, self.primary.map.get_name());
println!(
"Crash! Please report to https://github.com/a-b-street/abstreet/issues/ and include \
all output.txt; at least everything starting from the stack trace above!"
);
println!();
self.primary.sim.dump_before_abort();
println!();
println!("Camera:");
println!(
r#"{{ "cam_x": {}, "cam_y": {}, "cam_zoom": {} }}"#,
canvas.cam_x, canvas.cam_y, canvas.cam_zoom
);
println!();
if self.primary.map.get_edits().commands.is_empty() {
println!("No edits");
} else {
abstio::write_json(
"edits_during_crash.json".to_string(),
&self.primary.map.get_edits().to_permanent(&self.primary.map),
);
println!("Please include edits_during_crash.json in your bug report.");
}
println!();
println!(
"Crash! Please report to https://github.com/a-b-street/abstreet/issues/ and include \
all output.txt; at least everything above here until the start of the report!"
);
println!(
"********************************************************************************"
);
}
fn before_quit(&self, canvas: &Canvas) {
CameraState::save(canvas, self.primary.map.get_name());
}
}
fn add_study_area(map: &mut Map, name: &str) -> Result<()> {
let require_in_bounds = true;
for (polygon, tags) in Polygon::from_geojson_bytes(
&abstio::slurp_file(abstio::path(format!("system/study_areas/{}.geojson", name)))?,
map.get_gps_bounds(),
require_in_bounds,
)? {
map.hack_add_area(AreaType::StudyArea, polygon, tags);
}
Ok(())
}