diff --git a/game/src/edit/impact.rs b/game/src/edit/impact.rs new file mode 100644 index 0000000000..36ff44594e --- /dev/null +++ b/game/src/edit/impact.rs @@ -0,0 +1,27 @@ +use crate::ui::UI; +use abstutil::{prettyprint_usize, Timer}; +use sim::Scenario; + +// Edits have been applied. +pub fn edit_impacts(scenario: Option, ui: &mut UI, timer: &mut Timer) -> Vec { + let mut lines = Vec::new(); + + if let Some(s) = scenario { + ui.primary.clear_sim(); + s.instantiate( + &mut ui.primary.sim, + &ui.primary.map, + &mut ui.primary.current_flags.sim_flags.make_rng(), + timer, + ); + lines.push(format!( + "{} aborted trips", + prettyprint_usize(ui.primary.sim.get_finished_trips().aborted_trips) + )); + ui.primary.clear_sim(); + } else { + lines.push("No scenario, so no trips impacted".to_string()); + } + + lines +} diff --git a/game/src/edit/mod.rs b/game/src/edit/mod.rs index f9b80a82d4..0f9e384bd9 100644 --- a/game/src/edit/mod.rs +++ b/game/src/edit/mod.rs @@ -1,9 +1,10 @@ +mod impact; mod stop_signs; mod traffic_signals; use crate::common::CommonState; use crate::debug::DebugMode; -use crate::game::{State, Transition, WizardState}; +use crate::game::{msg, State, Transition, WizardState}; use crate::helpers::{ColorScheme, ID}; use crate::render::{ DrawIntersection, DrawLane, DrawMap, DrawOptions, DrawRoad, DrawTurn, Renderable, @@ -38,6 +39,7 @@ impl EditMode { (hotkey(Key::Escape), "back to sandbox mode"), (hotkey(Key::S), "save edits"), (hotkey(Key::L), "load different edits"), + (None, "measure impact of edits"), ], ctx, ), @@ -114,6 +116,15 @@ impl State for EditMode { // Parking state might've changed ui.primary.clear_sim(); return Transition::Replace(Box::new(SandboxMode::new(ctx, ui, self.mode.clone()))); + } else if self.menu.action("measure impact of edits") { + let mut timer = Timer::new("measure impact of edits"); + ui.primary + .map + .recalculate_pathfinding_after_edits(&mut timer); + return Transition::Push(msg( + "Impact of edits", + impact::edit_impacts(self.mode.scenario(ui, &mut timer), ui, &mut timer), + )); } if let Some(ID::Lane(id)) = ui.primary.current_selection { diff --git a/game/src/sandbox/gameplay/mod.rs b/game/src/sandbox/gameplay/mod.rs index 33b3ffc313..b5894b8938 100644 --- a/game/src/sandbox/gameplay/mod.rs +++ b/game/src/sandbox/gameplay/mod.rs @@ -47,6 +47,42 @@ pub trait GameplayState: downcast_rs::Downcast { } downcast_rs::impl_downcast!(GameplayState); +impl GameplayMode { + pub fn scenario(&self, ui: &UI, timer: &mut Timer) -> Option { + let name = match self { + GameplayMode::Freeform => { + return None; + } + GameplayMode::PlayScenario(ref scenario) => scenario, + _ => "weekday_typical_traffic_from_psrc", + }; + let num_agents = ui.primary.current_flags.num_agents; + let builtin = if let Some(n) = num_agents { + format!("random scenario with {} agents", n) + } else { + "random scenario with some agents".to_string() + }; + Some(if name == builtin { + if let Some(n) = num_agents { + Scenario::scaled_run(&ui.primary.map, n) + } else { + Scenario::small_run(&ui.primary.map) + } + } else if name == "just buses" { + let mut s = Scenario::empty(&ui.primary.map); + s.scenario_name = "just buses".to_string(); + s.seed_buses = true; + s + } else { + abstutil::read_binary( + &abstutil::path1_bin(&ui.primary.map.get_name(), abstutil::SCENARIOS, &name), + timer, + ) + .unwrap() + }) + } +} + impl GameplayRunner { pub fn initialize(mode: GameplayMode, ui: &mut UI, ctx: &mut EventCtx) -> GameplayRunner { let prebaked: Analytics = abstutil::read_binary( @@ -58,61 +94,19 @@ impl GameplayRunner { Analytics::new() }); - let ((menu, state), maybe_scenario) = match mode.clone() { - GameplayMode::Freeform => (freeform::Freeform::new(ctx), None), - GameplayMode::PlayScenario(scenario) => ( - play_scenario::PlayScenario::new(&scenario, ctx), - Some(scenario), - ), - GameplayMode::OptimizeBus(route_name) => ( - optimize_bus::OptimizeBus::new(route_name, ctx, ui), - Some("weekday_typical_traffic_from_psrc".to_string()), - ), - GameplayMode::CreateGridlock => ( - create_gridlock::CreateGridlock::new(ctx), - Some("weekday_typical_traffic_from_psrc".to_string()), - ), - GameplayMode::FasterTrips(trip_mode) => ( - faster_trips::FasterTrips::new(trip_mode, ctx), - Some("weekday_typical_traffic_from_psrc".to_string()), - ), + let (menu, state) = match mode.clone() { + GameplayMode::Freeform => freeform::Freeform::new(ctx), + GameplayMode::PlayScenario(scenario) => { + play_scenario::PlayScenario::new(&scenario, ctx) + } + GameplayMode::OptimizeBus(route_name) => { + optimize_bus::OptimizeBus::new(route_name, ctx, ui) + } + GameplayMode::CreateGridlock => create_gridlock::CreateGridlock::new(ctx), + GameplayMode::FasterTrips(trip_mode) => faster_trips::FasterTrips::new(trip_mode, ctx), }; - let runner = GameplayRunner { - mode, - menu: menu.disable_standalone_layout(), - state, - prebaked, - }; - if let Some(scenario_name) = maybe_scenario { - ctx.loading_screen("instantiate scenario", |_, timer| { - let num_agents = ui.primary.current_flags.num_agents; - let builtin = if let Some(n) = num_agents { - format!("random scenario with {} agents", n) - } else { - "random scenario with some agents".to_string() - }; - let scenario = if scenario_name == builtin { - if let Some(n) = num_agents { - Scenario::scaled_run(&ui.primary.map, n) - } else { - Scenario::small_run(&ui.primary.map) - } - } else if scenario_name == "just buses" { - let mut s = Scenario::empty(&ui.primary.map); - s.scenario_name = "just buses".to_string(); - s.seed_buses = true; - s - } else { - abstutil::read_binary( - &abstutil::path1_bin( - &ui.primary.map.get_name(), - abstutil::SCENARIOS, - &scenario_name, - ), - timer, - ) - .unwrap() - }; + ctx.loading_screen("instantiate scenario", |_, timer| { + if let Some(scenario) = mode.scenario(ui, timer) { scenario.instantiate( &mut ui.primary.sim, &ui.primary.map, @@ -120,9 +114,14 @@ impl GameplayRunner { timer, ); ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1)); - }); + } + }); + GameplayRunner { + mode, + menu: menu.disable_standalone_layout(), + state, + prebaked, } - runner } pub fn event( diff --git a/map_model/src/pathfind/walking.rs b/map_model/src/pathfind/walking.rs index 0ebba5e24d..bc3eaf798e 100644 --- a/map_model/src/pathfind/walking.rs +++ b/map_model/src/pathfind/walking.rs @@ -152,12 +152,15 @@ impl SidewalkPathfinder { } steps.push(PathStep::Turn(t)); current_i = Some(lane1.dst_i); - } else { + } else if let Some(t) = back_t { if current_i != Some(lane1.src_i) { steps.push(PathStep::ContraflowLane(lane1.id)); } - steps.push(PathStep::Turn(back_t.unwrap())); + steps.push(PathStep::Turn(t)); current_i = Some(lane1.src_i); + } else { + println!("WARNING! No turn between sidewalks {} and {}", lane1.id, l2); + return None; } } diff --git a/sim/src/make/spawner.rs b/sim/src/make/spawner.rs index 937213c20f..6fb81b4bcb 100644 --- a/sim/src/make/spawner.rs +++ b/sim/src/make/spawner.rs @@ -172,11 +172,6 @@ impl TripSpawner { timer.start_iter("spawn trips", paths.len()); for ((start_time, ped_id, car_id, spec), req, maybe_path) in paths { timer.next(); - if maybe_path.is_none() { - timer.warn(format!("Some trip couldn't find the first path {}", req)); - continue; - } - let path = maybe_path.unwrap(); match spec { TripSpec::CarAppearing { start_pos, @@ -203,14 +198,22 @@ impl TripSpawner { } let trip_start = TripStart::Border(map.get_l(start_pos.lane()).src_i); let trip = trips.new_trip(start_time, trip_start, legs); - let router = goal.make_router(path, map, vehicle.vehicle_type); - scheduler.quick_push( - start_time, - Command::SpawnCar( - CreateCar::for_appearing(vehicle, start_pos, router, trip), - retry_if_no_room, - ), - ); + if let Some(path) = maybe_path { + let router = goal.make_router(path, map, vehicle.vehicle_type); + scheduler.quick_push( + start_time, + Command::SpawnCar( + CreateCar::for_appearing(vehicle, start_pos, router, trip), + retry_if_no_room, + ), + ); + } else { + timer.warn(format!( + "CarAppearing trip couldn't find the first path {}", + req + )); + trips.abort_trip_failed_start(trip); + } } TripSpec::UsingParkedCar { start, @@ -243,17 +246,25 @@ impl TripSpawner { let trip = trips.new_trip(start_time, TripStart::Bldg(vehicle.owner.unwrap()), legs); - scheduler.quick_push( - start_time, - Command::SpawnPed(CreatePedestrian { - id: ped_id.unwrap(), - speed: ped_speed, - start, - goal: parking_spot, - path, - trip, - }), - ); + if let Some(path) = maybe_path { + scheduler.quick_push( + start_time, + Command::SpawnPed(CreatePedestrian { + id: ped_id.unwrap(), + speed: ped_speed, + start, + goal: parking_spot, + path, + trip, + }), + ); + } else { + timer.warn(format!( + "UsingParkedCar trip couldn't find the first path {}", + req + )); + trips.abort_trip_failed_start(trip); + } } TripSpec::MaybeUsingParkedCar { start_bldg, @@ -273,8 +284,8 @@ impl TripSpawner { speed: ped_speed, start: SidewalkSpot::building(start_bldg, map), goal: walk_to, - // This is junk - path, + // This is guaranteed to work, and is junk anyway. + path: maybe_path.unwrap(), trip, }), ); @@ -297,17 +308,25 @@ impl TripSpawner { vec![TripLeg::Walk(ped_id.unwrap(), ped_speed, goal.clone())], ); - scheduler.quick_push( - start_time, - Command::SpawnPed(CreatePedestrian { - id: ped_id.unwrap(), - speed: ped_speed, - start, - goal, - path, - trip, - }), - ); + if let Some(path) = maybe_path { + scheduler.quick_push( + start_time, + Command::SpawnPed(CreatePedestrian { + id: ped_id.unwrap(), + speed: ped_speed, + start, + goal, + path, + trip, + }), + ); + } else { + timer.warn(format!( + "JustWalking trip couldn't find the first path {}", + req + )); + trips.abort_trip_failed_start(trip); + } } TripSpec::UsingBike { start, @@ -344,17 +363,25 @@ impl TripSpawner { legs, ); - scheduler.quick_push( - start_time, - Command::SpawnPed(CreatePedestrian { - id: ped_id.unwrap(), - speed: ped_speed, - start, - goal: walk_to, - path, - trip, - }), - ); + if let Some(path) = maybe_path { + scheduler.quick_push( + start_time, + Command::SpawnPed(CreatePedestrian { + id: ped_id.unwrap(), + speed: ped_speed, + start, + goal: walk_to, + path, + trip, + }), + ); + } else { + timer.warn(format!( + "UsingBike trip couldn't find the first path {}", + req + )); + trips.abort_trip_failed_start(trip); + } } TripSpec::UsingTransit { start, @@ -382,17 +409,25 @@ impl TripSpawner { ], ); - scheduler.quick_push( - start_time, - Command::SpawnPed(CreatePedestrian { - id: ped_id.unwrap(), - speed: ped_speed, - start, - goal: walk_to, - path, - trip, - }), - ); + if let Some(path) = maybe_path { + scheduler.quick_push( + start_time, + Command::SpawnPed(CreatePedestrian { + id: ped_id.unwrap(), + speed: ped_speed, + start, + goal: walk_to, + path, + trip, + }), + ); + } else { + timer.warn(format!( + "UsingTransit trip couldn't find the first path {}", + req + )); + trips.abort_trip_failed_start(trip); + } } } }