diff --git a/game/src/challenges.rs b/game/src/challenges.rs index 9c5e430fb4..78f22264da 100644 --- a/game/src/challenges.rs +++ b/game/src/challenges.rs @@ -7,8 +7,9 @@ use ezgui::{ VerticalAlignment, }; use geom::{Duration, DurationHistogram, DurationStats}; +use map_model::{BusRouteID, BusStopID}; use serde_derive::{Deserialize, Serialize}; -use sim::{Sim, SimFlags, SimOptions, TripMode}; +use sim::{CarID, Sim, SimFlags, SimOptions, TripMode}; use std::collections::BTreeMap; // TODO Also have some kind of screenshot to display for each challenge @@ -153,6 +154,7 @@ pub fn prebake() { let results = PrebakedResults { faster_trips: FasterTrips::from(&sim), gridlock_delays: GridlockDelays::from(&sim), + bus_arrivals: BusArrivals::from(&sim), }; abstutil::write_json("../data/prebaked_results.json", &results).unwrap(); } @@ -160,15 +162,16 @@ pub fn prebake() { // TODO Something more general? // - key by GameplayMode (which needs map name too maybe) // - different baselines/benchmarks +// TODO Actually, can we just store sim Analytics, and move all of this derived stuff there? #[derive(Serialize, Deserialize)] pub struct PrebakedResults { pub faster_trips: FasterTrips, pub gridlock_delays: GridlockDelays, + pub bus_arrivals: BusArrivals, } #[derive(Serialize, Deserialize)] pub struct FasterTrips(pub Vec<(Duration, Option, Duration)>); - impl FasterTrips { pub fn from(sim: &Sim) -> FasterTrips { FasterTrips(sim.get_analytics().finished_trips.clone()) @@ -201,13 +204,48 @@ impl FasterTrips { } } +#[derive(Serialize, Deserialize)] +pub struct BusArrivals(pub Vec<(Duration, CarID, BusRouteID, BusStopID)>); +impl BusArrivals { + pub fn from(sim: &Sim) -> BusArrivals { + BusArrivals(sim.get_analytics().bus_arrivals.clone()) + } + + pub fn to_stats(&self, r: BusRouteID, now: Duration) -> BTreeMap { + let mut per_bus: BTreeMap> = BTreeMap::new(); + for (t, car, route, stop) in &self.0 { + if *t > now { + break; + } + if *route == r { + per_bus + .entry(*car) + .or_insert_with(Vec::new) + .push((*t, *stop)); + } + } + let mut delay_to_stop: BTreeMap = BTreeMap::new(); + for events in per_bus.values() { + for pair in events.windows(2) { + delay_to_stop + .entry(pair[1].1) + .or_insert_with(DurationHistogram::new) + .add(pair[1].0 - pair[0].0); + } + } + delay_to_stop + .into_iter() + .map(|(k, v)| (k, v.to_stats())) + .collect() + } +} + #[derive(Serialize, Deserialize)] pub struct GridlockDelays { pub lt_1m: usize, pub lt_5m: usize, pub stuck: usize, } - impl GridlockDelays { pub fn from(sim: &Sim) -> GridlockDelays { let mut delays = GridlockDelays { diff --git a/game/src/sandbox/gameplay.rs b/game/src/sandbox/gameplay.rs index 0b2c9b2e57..29e4b06eb0 100644 --- a/game/src/sandbox/gameplay.rs +++ b/game/src/sandbox/gameplay.rs @@ -1,14 +1,13 @@ -use crate::challenges::{FasterTrips, GridlockDelays, PrebakedResults}; +use crate::challenges::{BusArrivals, FasterTrips, GridlockDelays, PrebakedResults}; use crate::game::{msg, Transition, WizardState}; use crate::render::AgentColorScheme; use crate::sandbox::{analytics, bus_explorer, spawner, SandboxMode}; use crate::ui::UI; use abstutil::{prettyprint_usize, Timer}; use ezgui::{hotkey, Choice, Color, EventCtx, GfxCtx, Key, Line, ModalMenu, Text, Wizard}; -use geom::{Duration, DurationHistogram, Statistic}; -use map_model::{BusRouteID, BusStopID}; -use sim::{CarID, Scenario, TripMode}; -use std::collections::BTreeMap; +use geom::{Duration, Statistic}; +use map_model::BusRouteID; +use sim::{Scenario, TripMode}; #[derive(Clone)] pub enum GameplayMode { @@ -261,7 +260,8 @@ impl GameplayState { // TODO Expensive if *time != ui.primary.sim.time() { *time = ui.primary.sim.time(); - self.menu.set_info(ctx, bus_route_panel(route, ui, *stat)); + self.menu + .set_info(ctx, bus_route_panel(route, ui, *stat, &self.prebaked)); } if self.menu.action("change statistic") { @@ -356,25 +356,9 @@ impl GameplayState { } } -fn bus_route_panel(id: BusRouteID, ui: &UI, stat: Statistic) -> Text { - let mut per_bus: BTreeMap> = BTreeMap::new(); - for (t, car, route, stop) in &ui.primary.sim.get_analytics().bus_arrivals { - if *route == id { - per_bus - .entry(*car) - .or_insert_with(Vec::new) - .push((*t, *stop)); - } - } - let mut delay_to_stop: BTreeMap = BTreeMap::new(); - for events in per_bus.values() { - for pair in events.windows(2) { - delay_to_stop - .entry(pair[1].1) - .or_insert_with(DurationHistogram::new) - .add(pair[1].0 - pair[0].0); - } - } +fn bus_route_panel(id: BusRouteID, ui: &UI, stat: Statistic, prebaked: &PrebakedResults) -> Text { + let now = BusArrivals::from(&ui.primary.sim).to_stats(id, ui.primary.sim.time()); + let baseline = prebaked.bus_arrivals.to_stats(id, ui.primary.sim.time()); let route = ui.primary.map.get_br(id); let mut txt = Text::new(); @@ -385,9 +369,24 @@ fn bus_route_panel(id: BusRouteID, ui: &UI, stat: Statistic) -> Text { } else { idx1 + 1 }; + // TODO Also display number of arrivals... txt.add(Line(format!("Stop {}->{}: ", idx1 + 1, idx2 + 1))); - if let Some(ref distrib) = delay_to_stop.get(&route.stops[idx2]) { - txt.append(Line(distrib.select(stat).minimal_tostring())); + if let Some(ref stats1) = now.get(&route.stops[idx2]) { + let us = stats1.stats[&stat]; + txt.append(Line(us.minimal_tostring())); + + if let Some(ref stats2) = baseline.get(&route.stops[idx2]) { + let vs = stats2.stats[&stat]; + if us <= vs { + txt.append(Line(" (")); + txt.append(Line((vs - us).minimal_tostring()).fg(Color::GREEN)); + txt.append(Line(" faster)")); + } else { + txt.append(Line(" (")); + txt.append(Line((us - vs).minimal_tostring()).fg(Color::RED)); + txt.append(Line(" slower)")); + } + } } else { txt.append(Line("no arrivals yet")); }