1use std::cell::RefCell;
2use std::collections::BTreeMap;
3
4use anyhow::Result;
5use maplit::btreemap;
6use structopt::StructOpt;
7
8use crate::ID;
9use abstio::MapName;
10use abstutil::{Tags, Timer};
11use geom::{Bounds, Circle, Distance, Duration, FindClosest, Polygon, Pt2D, Tessellation, Time};
12use map_gui::colors::ColorScheme;
13use map_gui::options::Options;
14use map_gui::render::{DrawMap, DrawOptions};
15use map_gui::tools::CameraState;
16use map_model::AreaType;
17use map_model::{BufferType, IntersectionID, LaneType, Map, Traversable};
18use sim::{AgentID, Analytics, Sim, SimCallback, SimFlags, VehicleType};
19use synthpop::Scenario;
20use widgetry::mapspace::ToggleZoomed;
21use widgetry::{Cached, Canvas, EventCtx, GfxCtx, Prerender, SharedAppState, State};
22
23use crate::challenges::HighScore;
24use crate::common::Warping;
25use crate::edit::apply_map_edits;
26use crate::layer::Layer;
27use crate::render::{unzoomed_agent_radius, AgentCache, GameRenderable};
28use crate::sandbox::dashboards::DashTab;
29use crate::sandbox::{GameplayMode, TutorialState};
30
31pub type Transition = widgetry::Transition<App>;
33
34pub struct App {
36 pub primary: PerMap,
38 pub secondary: Option<PerMap>,
43 pub store_unedited_map_in_secondary: bool,
44
45 pub cs: ColorScheme,
46 pub opts: Options,
47
48 pub per_obj: PerObjectActions,
49
50 pub session: SessionState,
52}
53
54impl App {
55 pub fn has_prebaked(&self) -> Option<(&MapName, &String)> {
57 self.primary.prebaked.as_ref().map(|(m, s, _)| (m, s))
58 }
59 pub fn prebaked(&self) -> &Analytics {
60 &self.primary.prebaked.as_ref().unwrap().2
61 }
62 pub fn set_prebaked(&mut self, prebaked: Option<(MapName, String, Analytics)>) {
63 self.primary.prebaked = prebaked;
64
65 if false {
66 if let Some((_, _, ref a)) = self.primary.prebaked {
67 use abstutil::{prettyprint_usize, serialized_size_bytes};
68 println!(
69 "- road_thruput: {} bytes",
70 prettyprint_usize(serialized_size_bytes(&a.road_thruput))
71 );
72 println!(
73 "- intersection_thruput: {} bytes",
74 prettyprint_usize(serialized_size_bytes(&a.intersection_thruput))
75 );
76 println!(
77 "- traffic_signal_thruput: {} bytes",
78 prettyprint_usize(serialized_size_bytes(&a.traffic_signal_thruput))
79 );
80 println!(
81 "- demand : {} bytes",
82 prettyprint_usize(serialized_size_bytes(&a.demand))
83 );
84 println!(
85 "- bus_arrivals : {} bytes",
86 prettyprint_usize(serialized_size_bytes(&a.bus_arrivals))
87 );
88 println!(
89 "- passengers_boarding: {} bytes",
90 prettyprint_usize(serialized_size_bytes(&a.passengers_boarding))
91 );
92 println!(
93 "- passengers_alighting: {} bytes",
94 prettyprint_usize(serialized_size_bytes(&a.passengers_alighting))
95 );
96 println!(
97 "- started_trips: {} bytes",
98 prettyprint_usize(serialized_size_bytes(&a.started_trips))
99 );
100 println!(
101 "- finished_trips: {} bytes",
102 prettyprint_usize(serialized_size_bytes(&a.finished_trips))
103 );
104 println!(
105 "- trip_log: {} bytes",
106 prettyprint_usize(serialized_size_bytes(&a.trip_log))
107 );
108 println!(
109 "- intersection_delays: {} bytes",
110 prettyprint_usize(serialized_size_bytes(&a.intersection_delays))
111 );
112 println!(
113 "- parking_lane_changes: {} bytes",
114 prettyprint_usize(serialized_size_bytes(&a.parking_lane_changes))
115 );
116 println!(
117 "- parking_lot_changes: {} bytes",
118 prettyprint_usize(serialized_size_bytes(&a.parking_lot_changes))
119 );
120 }
121 }
122 }
123
124 pub fn draw(&self, g: &mut GfxCtx, opts: DrawOptions, show_objs: &dyn ShowObject) {
125 let map = &self.primary.map;
126 let draw_map = &self.primary.draw_map;
127
128 let mut sample_intersection: Option<String> = None;
129
130 g.clear(self.cs.void_background);
131 g.redraw(&draw_map.boundary_polygon);
132
133 if g.canvas.is_unzoomed() {
134 let layers = show_objs.layers();
135 if layers.show_areas {
136 g.redraw(&draw_map.draw_all_areas);
137 }
138 if layers.show_parking_lots {
139 g.redraw(&draw_map.draw_all_unzoomed_parking_lots);
140 }
141 if layers.show_intersections || layers.show_lanes {
142 g.redraw(
143 &self
144 .primary
145 .draw_map
146 .draw_all_unzoomed_roads_and_intersections,
147 );
148 }
149 if layers.show_buildings {
150 g.redraw(&draw_map.draw_all_buildings);
151 g.redraw(&draw_map.draw_all_building_outlines);
152 }
153
154 if let Some(ID::Area(id)) = self.primary.current_selection {
157 g.draw_polygon(self.cs.selected, draw_map.get_a(id).get_outline(map));
158 } else if let Some(ID::Road(id)) = self.primary.current_selection {
159 g.draw_polygon(self.cs.selected, draw_map.get_r(id).get_outline(map));
160 } else if let Some(ID::Intersection(id)) = self.primary.current_selection {
161 g.draw_polygon(self.cs.selected, map.get_i(id).polygon.clone());
163 } else if let Some(ID::Building(id)) = self.primary.current_selection {
164 g.draw_polygon(self.cs.selected, map.get_b(id).polygon.clone());
165 }
166
167 let mut cache = self.primary.agents.borrow_mut();
168 cache.draw_unzoomed_agents(
169 g,
170 &self.primary.map,
171 &self.primary.sim,
172 &self.cs,
173 &self.opts,
174 );
175
176 if let Some(a) = self
177 .primary
178 .current_selection
179 .as_ref()
180 .and_then(|id| id.agent_id())
181 {
182 if let Some(pt) = self.primary.sim.canonical_pt_for_agent(a, map) {
183 g.draw_polygon(
186 self.cs.selected,
187 Circle::new(pt, unzoomed_agent_radius(a.to_vehicle_type())).to_polygon(),
188 );
189 }
190 }
191 } else {
192 let mut cache = self.primary.agents.borrow_mut();
193 let objects = self.get_renderables_back_to_front(
194 g.get_screen_bounds(),
195 g.prerender,
196 &mut cache,
197 show_objs,
198 );
199
200 let mut drawn_all_buildings = false;
201 let mut drawn_all_areas = false;
202
203 for obj in objects {
204 obj.draw(g, self, &opts);
205
206 match obj.get_id() {
207 ID::Building(_) => {
208 if !drawn_all_buildings {
209 g.redraw(&draw_map.draw_all_buildings);
210 g.redraw(&draw_map.draw_all_building_outlines);
211 drawn_all_buildings = true;
212 }
213 }
214 ID::Area(_) => {
215 if !drawn_all_areas {
216 g.redraw(&draw_map.draw_all_areas);
217 drawn_all_areas = true;
218 }
219 }
220 _ => {}
221 }
222
223 if self.primary.current_selection == Some(obj.get_id()) {
224 g.draw_polygon(self.cs.selected, obj.get_outline(map));
225 }
226
227 if g.is_screencap() && sample_intersection.is_none() {
228 if let ID::Intersection(id) = obj.get_id() {
229 sample_intersection = Some(format!("_i{}", id.0));
230 }
231 }
232 }
233 }
234
235 if let Some(i) = sample_intersection {
236 g.set_screencap_naming_hint(i);
237 }
238 }
239
240 pub fn recalculate_current_selection(&mut self, ctx: &EventCtx) {
242 self.primary.current_selection =
243 self.calculate_current_selection(ctx, &ShowEverything::new(), false, false, false);
244 }
245
246 pub fn mouseover_unzoomed_roads_and_intersections(&self, ctx: &EventCtx) -> Option<ID> {
247 self.calculate_current_selection(ctx, &ShowEverything::new(), false, true, false)
248 }
249 pub fn mouseover_unzoomed_intersections(&self, ctx: &EventCtx) -> Option<ID> {
250 self.calculate_current_selection(ctx, &ShowEverything::new(), false, true, false)
251 .filter(|id| matches!(id, ID::Intersection(_)))
252 }
253 pub fn mouseover_unzoomed_buildings(&self, ctx: &EventCtx) -> Option<ID> {
254 self.calculate_current_selection(ctx, &ShowEverything::new(), false, false, true)
255 }
256 pub fn mouseover_unzoomed_everything(&self, ctx: &EventCtx) -> Option<ID> {
257 self.calculate_current_selection(ctx, &ShowEverything::new(), false, true, true)
258 }
259 pub fn mouseover_debug_mode(&self, ctx: &EventCtx, show_objs: &dyn ShowObject) -> Option<ID> {
260 self.calculate_current_selection(ctx, show_objs, true, false, false)
261 }
262
263 fn calculate_current_selection(
264 &self,
265 ctx: &EventCtx,
266 show_objs: &dyn ShowObject,
267 debug_mode: bool,
268 unzoomed_roads_and_intersections: bool,
269 unzoomed_buildings: bool,
270 ) -> Option<ID> {
271 let unzoomed = ctx.canvas.is_unzoomed();
272
273 if unzoomed && !(debug_mode || unzoomed_roads_and_intersections || unzoomed_buildings) {
275 return None;
276 }
277
278 let pt = ctx.canvas.get_cursor_in_map_space()?;
279
280 let mut cache = self.primary.agents.borrow_mut();
281 let mut objects = self.get_renderables_back_to_front(
282 Circle::new(pt, Distance::meters(3.0)).get_bounds(),
283 ctx.prerender,
284 &mut cache,
285 show_objs,
286 );
287 objects.reverse();
288
289 let mut small_agents: Vec<(ID, Pt2D)> = Vec::new();
290
291 for obj in objects {
296 let id = obj.get_id();
297 match id {
298 ID::Area(_) => {
299 if !debug_mode {
300 continue;
301 }
302 }
303 ID::Road(_) => {
304 if !unzoomed_roads_and_intersections || !unzoomed {
305 continue;
306 }
307 }
308 ID::Intersection(_) => {
309 if unzoomed && !unzoomed_roads_and_intersections {
310 continue;
311 }
312 }
313 ID::Building(_) => {
314 if unzoomed && !unzoomed_buildings {
315 continue;
316 }
317 }
318 _ => {
319 if unzoomed {
320 continue;
321 }
322 }
323 }
324 if obj.contains_pt(pt, &self.primary.map) {
325 if unzoomed {
326 return Some(id);
329 }
330
331 match id {
332 ID::Pedestrian(_) => {}
333 ID::Car(c) => {
334 if c.vehicle_type != VehicleType::Bike {
335 return Some(id);
336 }
337 }
338 _ => {
339 if small_agents.is_empty() {
343 return Some(id);
344 } else {
345 continue;
346 }
347 }
348 }
349
350 small_agents.push((id, obj.get_outline(&self.primary.map).center()));
351 }
352 }
353
354 let (id, _) = small_agents
355 .into_iter()
356 .min_by_key(|(_, center)| center.fast_dist(pt))?;
357 Some(id)
358 }
359
360 fn get_renderables_back_to_front<'a>(
363 &'a self,
364 bounds: Bounds,
365 prerender: &Prerender,
366 agents: &'a mut AgentCache,
367 show_objs: &dyn ShowObject,
368 ) -> Vec<&'a (dyn GameRenderable + 'a)> {
369 let map = &self.primary.map;
370 let draw_map = &self.primary.draw_map;
371
372 let mut areas: Vec<&dyn GameRenderable> = Vec::new();
373 let mut parking_lots: Vec<&dyn GameRenderable> = Vec::new();
374 let mut lanes: Vec<&dyn GameRenderable> = Vec::new();
375 let mut roads: Vec<&dyn GameRenderable> = Vec::new();
376 let mut intersections: Vec<&dyn GameRenderable> = Vec::new();
377 let mut buildings: Vec<&dyn GameRenderable> = Vec::new();
378 let mut transit_stops: Vec<&dyn GameRenderable> = Vec::new();
379 let mut agents_on: Vec<Traversable> = Vec::new();
380
381 for id in draw_map
382 .get_matching_objects(bounds)
383 .into_iter()
384 .map(|id| id.into())
385 {
386 if !show_objs.show(&id) {
387 continue;
388 }
389 match id {
390 ID::Area(id) => areas.push(draw_map.get_a(id)),
391 ID::Road(id) => {
392 let road = draw_map.get_r(id);
393 for lane in &road.lanes {
394 agents_on.push(Traversable::Lane(lane.id));
395 lanes.push(lane);
396 }
397 for ts in &map.get_r(id).transit_stops {
398 if show_objs.show(&ID::TransitStop(*ts)) {
399 transit_stops.push(draw_map.get_ts(*ts));
400 }
401 }
402 roads.push(road);
403 }
404 ID::Intersection(id) => {
405 intersections.push(draw_map.get_i(id));
406 for t in &map.get_i(id).turns {
407 agents_on.push(Traversable::Turn(t.id));
408 }
409 }
410 ID::Building(id) => buildings.push(draw_map.get_b(id)),
411 ID::ParkingLot(id) => {
412 parking_lots.push(draw_map.get_pl(id));
413 agents_on.push(Traversable::Lane(map.get_pl(id).driving_pos.lane()));
415 }
416
417 ID::Lane(_)
418 | ID::TransitStop(_)
419 | ID::Car(_)
420 | ID::Pedestrian(_)
421 | ID::PedCrowd(_) => {
422 panic!("{:?} shouldn't be in the quadtree", id)
423 }
424 }
425 }
426
427 let mut borrows: Vec<&dyn GameRenderable> = Vec::new();
429 borrows.extend(areas);
430 borrows.extend(parking_lots);
431 borrows.extend(lanes);
432 borrows.extend(roads);
433 borrows.extend(intersections);
434 borrows.extend(buildings);
435 borrows.extend(transit_stops);
436
437 for on in &agents_on {
439 agents.populate_if_needed(*on, map, &self.primary.sim, &self.cs, prerender);
440 }
441
442 for on in agents_on {
443 for obj in agents.get(on) {
444 borrows.push(obj);
445 }
446 }
447
448 borrows.retain(|x| x.get_zorder() <= self.primary.draw_map.show_zorder);
449
450 borrows.sort_by_key(|x| x.get_zorder());
452
453 borrows
454 }
455
456 pub fn clear_everything(&mut self, ctx: &mut EventCtx) {
458 ctx.loading_screen("reset map and sim", |ctx, timer| {
459 apply_map_edits(ctx, self, self.primary.map.new_edits());
460 self.primary.map.recalculate_pathfinding_after_edits(timer);
461
462 self.primary.clear_sim();
463 self.set_prebaked(None);
464 });
465 }
466
467 pub fn swap_map(&mut self) {
470 let secondary = self.secondary.take().expect("no secondary map set");
471 let primary = std::mem::replace(&mut self.primary, secondary);
472 self.secondary = Some(primary);
473 }
474}
475
476impl App {
477 pub fn click_on_intersection<S: Into<String>>(
479 &mut self,
480 ctx: &mut EventCtx,
481 label: S,
482 ) -> Option<IntersectionID> {
483 if let Some(ID::Intersection(i)) = self.primary.current_selection {
484 if self.per_obj.left_click(ctx, label) {
485 return Some(i);
486 }
487 }
488 None
489 }
490}
491
492impl map_gui::AppLike for App {
495 #[inline]
496 fn map(&self) -> &Map {
497 &self.primary.map
498 }
499 #[inline]
500 fn cs(&self) -> &ColorScheme {
501 &self.cs
502 }
503 #[inline]
504 fn mut_cs(&mut self) -> &mut ColorScheme {
505 &mut self.cs
506 }
507 #[inline]
508 fn draw_map(&self) -> &DrawMap {
509 &self.primary.draw_map
510 }
511 #[inline]
512 fn mut_draw_map(&mut self) -> &mut DrawMap {
513 &mut self.primary.draw_map
514 }
515 #[inline]
516 fn opts(&self) -> &Options {
517 &self.opts
518 }
519 #[inline]
520 fn mut_opts(&mut self) -> &mut Options {
521 &mut self.opts
522 }
523
524 fn map_switched(&mut self, ctx: &mut EventCtx, map: Map, timer: &mut Timer) {
525 let sim = Sim::new(&map, self.primary.current_flags.sim_flags.opts.clone());
526
527 CameraState::save(ctx.canvas, self.primary.map.get_name());
528 self.primary = PerMap::map_loaded(
529 map,
530 sim,
531 self.primary.current_flags.clone(),
532 &self.opts,
533 &self.cs,
534 ctx,
535 timer,
536 );
537 self.primary.init_camera_for_loaded_map(ctx);
538 self.secondary = None;
539
540 self.opts.units.metric = self.primary.map.get_name().city.uses_metric();
543 }
544
545 fn draw_with_opts(&self, g: &mut GfxCtx, opts: DrawOptions) {
546 self.draw(g, opts, &ShowEverything::new());
547 }
548 fn make_warper(
549 &mut self,
550 ctx: &EventCtx,
551 pt: Pt2D,
552 target_cam_zoom: Option<f64>,
553 id: Option<map_gui::ID>,
554 ) -> Box<dyn State<App>> {
555 Warping::new_state(
556 ctx,
557 pt,
558 target_cam_zoom,
559 id.map(|x| x.into()),
560 &mut self.primary,
561 )
562 }
563
564 fn sim_time(&self) -> Time {
565 self.primary.sim.time()
566 }
567
568 fn current_stage_and_remaining_time(&self, id: IntersectionID) -> (usize, Duration) {
569 self.primary.sim.current_stage_and_remaining_time(id)
570 }
571}
572
573pub struct ShowLayers {
574 pub show_buildings: bool,
575 pub show_parking_lots: bool,
576 pub show_intersections: bool,
577 pub show_lanes: bool,
578 pub show_areas: bool,
579 pub show_labels: bool,
580}
581
582impl ShowLayers {
583 pub fn new() -> ShowLayers {
584 ShowLayers {
585 show_buildings: true,
586 show_parking_lots: true,
587 show_intersections: true,
588 show_lanes: true,
589 show_areas: true,
590 show_labels: false,
591 }
592 }
593}
594
595pub trait ShowObject {
596 fn show(&self, obj: &ID) -> bool;
597 fn layers(&self) -> &ShowLayers;
598}
599
600pub struct ShowEverything {
601 layers: ShowLayers,
602}
603
604impl ShowEverything {
605 pub fn new() -> ShowEverything {
606 ShowEverything {
607 layers: ShowLayers::new(),
608 }
609 }
610}
611
612impl ShowObject for ShowEverything {
613 fn show(&self, _: &ID) -> bool {
614 true
615 }
616
617 fn layers(&self) -> &ShowLayers {
618 &self.layers
619 }
620}
621
622#[derive(Clone, StructOpt)]
623pub struct Flags {
624 #[structopt(flatten)]
625 pub sim_flags: SimFlags,
626 #[structopt(long)]
629 pub live_map_edits: bool,
630 #[structopt(long)]
633 pub study_area: Option<String>,
634}
635
636pub struct PerMap {
638 pub map: Map,
639 pub draw_map: DrawMap,
640 pub sim: Sim,
641 pub agents: RefCell<AgentCache>,
642
643 pub current_selection: Option<ID>,
644 pub current_flags: Flags,
645 pub last_warped_from: Option<(Pt2D, f64)>,
646 pub sim_cb: Option<Box<dyn SimCallback>>,
647 pub dirty_from_edits: bool,
649 pub has_modified_trips: bool,
651
652 pub unedited_map: Option<Map>,
655
656 pub layer: Option<Box<dyn Layer>>,
657 pub suspended_sim: Option<Sim>,
659 prebaked: Option<(MapName, String, Analytics)>,
663 pub scenario: Option<Scenario>,
669
670 pub is_secondary: bool,
672}
673
674impl PerMap {
675 pub fn map_loaded(
676 mut map: Map,
677 sim: Sim,
678 flags: Flags,
679 opts: &Options,
680 cs: &ColorScheme,
681 ctx: &mut EventCtx,
682 timer: &mut Timer,
683 ) -> PerMap {
684 if let Some(ref name) = flags.study_area {
685 if let Err(err) = add_study_area(&mut map, name) {
686 error!("Didn't apply study area {}: {}", name, err);
687 }
688 }
689
690 timer.start("draw_map");
691 let draw_map = DrawMap::new(ctx, &map, opts, cs, timer);
692 timer.stop("draw_map");
693
694 PerMap {
695 map,
696 draw_map,
697 sim,
698 agents: RefCell::new(AgentCache::new()),
699 current_selection: None,
700 current_flags: flags,
701 last_warped_from: None,
702 sim_cb: None,
703 dirty_from_edits: false,
704 has_modified_trips: false,
705 unedited_map: None,
706 layer: None,
707 suspended_sim: None,
708 prebaked: None,
709 scenario: None,
710 is_secondary: false,
711 }
712 }
713
714 pub fn init_camera_for_loaded_map(&mut self, ctx: &mut EventCtx) {
715 if !CameraState::load(ctx, self.map.get_name()) {
716 ctx.canvas.cam_zoom = ctx.canvas.min_zoom();
719 ctx.canvas
720 .center_on_map_pt(self.map.get_boundary_polygon().center());
721 }
722 }
723
724 pub fn clear_sim(&mut self) -> Sim {
726 self.dirty_from_edits = false;
727 std::mem::replace(
728 &mut self.sim,
729 Sim::new(&self.map, self.current_flags.sim_flags.opts.clone()),
730 )
731 }
732
733 pub fn canonical_point(&self, id: ID) -> Option<Pt2D> {
734 match id {
735 ID::Road(id) => self.map.maybe_get_r(id).map(|r| r.center_pts.first_pt()),
736 ID::Lane(id) => self.map.maybe_get_l(id).map(|l| l.first_pt()),
737 ID::Intersection(id) => self.map.maybe_get_i(id).map(|i| i.polygon.center()),
738 ID::Building(id) => self.map.maybe_get_b(id).map(|b| b.polygon.center()),
739 ID::ParkingLot(id) => self.map.maybe_get_pl(id).map(|pl| pl.polygon.center()),
740 ID::Car(id) => self.sim.canonical_pt_for_agent(AgentID::Car(id), &self.map),
741 ID::Pedestrian(id) => self
742 .sim
743 .canonical_pt_for_agent(AgentID::Pedestrian(id), &self.map),
744 ID::PedCrowd(ref members) => self
745 .sim
746 .canonical_pt_for_agent(AgentID::Pedestrian(members[0]), &self.map),
747 ID::TransitStop(id) => self
748 .map
749 .maybe_get_ts(id)
750 .map(|bs| bs.sidewalk_pos.pt(&self.map)),
751 ID::Area(id) => self.map.maybe_get_a(id).map(|a| a.polygon.center()),
752 }
753 }
754
755 pub fn get_obj_outline(
758 &self,
759 ctx: &EventCtx,
760 id: ID,
761 cs: &ColorScheme,
762 map: &Map,
763 agents: &mut AgentCache,
764 ) -> Option<Tessellation> {
765 let on = match id {
766 ID::Car(id) => {
767 self.sim.get_draw_car(id, &self.map)?.on
769 }
770 ID::Pedestrian(id) => self.sim.get_draw_ped(id, &self.map).unwrap().on,
771 ID::PedCrowd(ref members) => {
772 self.sim.get_draw_ped(members[0], &self.map)?.on
774 }
775 _ => {
776 let obj = self.draw_map.get_obj(id.to_map_gui());
778 return Some(obj.get_outline(map));
779 }
780 };
781
782 agents.populate_if_needed(on, &self.map, &self.sim, cs, ctx.prerender);
783
784 let obj = agents.get(on).into_iter().find(|r| r.get_id() == id)?;
787 Some(obj.get_outline(map))
788 }
789}
790
791pub struct SessionState {
793 pub tutorial: Option<TutorialState>,
794 pub high_scores: BTreeMap<GameplayMode, Vec<HighScore>>,
795 pub info_panel_tab: BTreeMap<&'static str, &'static str>,
796 pub last_gmns_timing_csv: Option<(String, Vec<u8>)>,
797 pub dash_tab: DashTab,
798 pub buffer_lane_type: LaneType,
799
800 pub elevation_contours: Cached<MapName, (FindClosest<Distance>, ToggleZoomed)>,
802 pub routing_preferences: crate::ungap::RoutingPreferences,
803 pub ungap_current_trip_name: Option<String>,
804 pub mode_shift: Cached<(MapName, usize), crate::ungap::ModeShiftData>,
806}
807
808impl SessionState {
809 pub fn empty() -> SessionState {
810 SessionState {
811 tutorial: None,
812 high_scores: BTreeMap::new(),
813 info_panel_tab: btreemap! {
814 "lane" => "info",
815 "intersection" => "info",
816 "bldg" => "info",
817 "person" => "trips",
818 "bus" => "status",
819 },
820 last_gmns_timing_csv: None,
821 dash_tab: DashTab::TripTable,
822 buffer_lane_type: LaneType::Buffer(BufferType::Stripes),
823
824 elevation_contours: Cached::new(),
825 routing_preferences: crate::ungap::RoutingPreferences::default(),
826 ungap_current_trip_name: None,
827 mode_shift: Cached::new(),
828 }
829 }
830}
831
832pub struct PerObjectActions {
834 pub click_action: Option<String>,
835}
836
837impl PerObjectActions {
838 pub fn new() -> PerObjectActions {
839 PerObjectActions { click_action: None }
840 }
841
842 pub fn reset(&mut self) {
843 self.click_action = None;
844 }
845
846 pub fn left_click<S: Into<String>>(&mut self, ctx: &mut EventCtx, label: S) -> bool {
847 let label = label.into();
848 if let Some(ref old) = self.click_action {
849 panic!("left_click for \"{old}\" already called; can't also do \"{label}\"");
850 }
851 self.click_action = Some(label);
852 ctx.normal_left_click()
853 }
854}
855
856pub struct FindDelayedIntersections {
857 pub halt_limit: Duration,
858 pub report_limit: Duration,
859
860 pub currently_delayed: Vec<(IntersectionID, Time)>,
861}
862
863impl SimCallback for FindDelayedIntersections {
864 fn run(&mut self, sim: &Sim, _: &Map) -> bool {
865 self.currently_delayed = sim.delayed_intersections(self.report_limit);
866 if let Some((_, t)) = self.currently_delayed.get(0) {
867 sim.time() - *t >= self.halt_limit
868 } else {
869 false
870 }
871 }
872}
873
874impl SharedAppState for App {
875 fn before_event(&mut self) {
876 self.per_obj.reset();
877 }
878
879 fn draw_default(&self, g: &mut GfxCtx) {
880 self.draw(g, DrawOptions::new(), &ShowEverything::new());
881 }
882
883 fn dump_before_abort(&self, canvas: &Canvas) {
884 println!();
885 println!(
886 "********************************************************************************"
887 );
888 CameraState::save(canvas, self.primary.map.get_name());
889 println!(
890 "Crash! Please report to https://github.com/a-b-street/abstreet/issues/ and include \
891 all output.txt; at least everything starting from the stack trace above!"
892 );
893
894 println!();
895 self.primary.sim.dump_before_abort();
896
897 println!();
898 println!("Camera:");
899 println!(
900 r#"{{ "cam_x": {}, "cam_y": {}, "cam_zoom": {} }}"#,
901 canvas.cam_x, canvas.cam_y, canvas.cam_zoom
902 );
903
904 println!();
905 if self.primary.map.get_edits().commands.is_empty() {
906 println!("No edits");
907 } else {
908 abstio::write_json(
909 "edits_during_crash.json".to_string(),
910 &self.primary.map.get_edits().to_permanent(&self.primary.map),
911 );
912 println!("Please include edits_during_crash.json in your bug report.");
913 }
914
915 println!();
917 println!(
918 "Crash! Please report to https://github.com/a-b-street/abstreet/issues/ and include \
919 all output.txt; at least everything above here until the start of the report!"
920 );
921 println!(
922 "********************************************************************************"
923 );
924 }
925
926 fn before_quit(&self, canvas: &Canvas) {
927 CameraState::save(canvas, self.primary.map.get_name());
928 }
929
930 fn free_memory(&mut self) {
931 self.primary.draw_map.free_memory();
932 }
933}
934
935fn add_study_area(map: &mut Map, name: &str) -> Result<()> {
937 let require_in_bounds = true;
938 for (polygon, tags) in Polygon::from_geojson_bytes(
939 &abstio::slurp_file(abstio::path(format!("system/study_areas/{}.geojson", name)))?,
940 map.get_gps_bounds(),
941 require_in_bounds,
942 )? {
943 map.hack_add_area(AreaType::StudyArea, polygon, Tags::new(tags));
944 }
945 Ok(())
946}