mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 15:33:44 +03:00
mvp of graphing finished trips over time
This commit is contained in:
parent
71096cf122
commit
6f75cf32eb
@ -170,6 +170,7 @@ fn launch_test(test: &ABTest, ui: &mut UI, ctx: &mut EventCtx) -> ABTestMode {
|
|||||||
savestate_every: None,
|
savestate_every: None,
|
||||||
freeform_policy: current_flags.sim_flags.freeform_policy,
|
freeform_policy: current_flags.sim_flags.freeform_policy,
|
||||||
disable_block_the_box: current_flags.sim_flags.disable_block_the_box,
|
disable_block_the_box: current_flags.sim_flags.disable_block_the_box,
|
||||||
|
record_stats: false,
|
||||||
},
|
},
|
||||||
..current_flags.clone()
|
..current_flags.clone()
|
||||||
},
|
},
|
||||||
|
@ -91,7 +91,7 @@ impl State for EditMode {
|
|||||||
return Transition::Pop;
|
return Transition::Pop;
|
||||||
}
|
}
|
||||||
if self.menu.action("sandbox mode") {
|
if self.menu.action("sandbox mode") {
|
||||||
return Transition::Replace(Box::new(SandboxMode::new(ctx)));
|
return Transition::Replace(Box::new(SandboxMode::new(ctx, ui)));
|
||||||
}
|
}
|
||||||
if self.menu.action("debug mode") {
|
if self.menu.action("debug mode") {
|
||||||
return Transition::Push(Box::new(DebugMode::new(ctx, ui)));
|
return Transition::Push(Box::new(DebugMode::new(ctx, ui)));
|
||||||
|
@ -21,7 +21,7 @@ impl Game {
|
|||||||
} else {
|
} else {
|
||||||
vec![
|
vec![
|
||||||
Box::new(SplashScreen::new_without_screensaver()),
|
Box::new(SplashScreen::new_without_screensaver()),
|
||||||
Box::new(SandboxMode::new(ctx)),
|
Box::new(SandboxMode::new(ctx, &ui)),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
Game { states, ui }
|
Game { states, ui }
|
||||||
|
@ -78,7 +78,7 @@ impl State for MissionEditMode {
|
|||||||
.sim
|
.sim
|
||||||
.step(&ui.primary.map, Duration::const_seconds(0.1));
|
.step(&ui.primary.map, Duration::const_seconds(0.1));
|
||||||
});
|
});
|
||||||
return Transition::Replace(Box::new(SandboxMode::new(ctx)));
|
return Transition::Replace(Box::new(SandboxMode::new(ctx, ui)));
|
||||||
} else if self.menu.action("create scenario from PSRC trips") {
|
} else if self.menu.action("create scenario from PSRC trips") {
|
||||||
return Transition::Push(WizardState::new(Box::new(convert_trips_to_scenario)));
|
return Transition::Push(WizardState::new(Box::new(convert_trips_to_scenario)));
|
||||||
} else if self.menu.action("manage neighborhoods") {
|
} else if self.menu.action("manage neighborhoods") {
|
||||||
|
@ -198,7 +198,7 @@ impl State for ScenarioManager {
|
|||||||
);
|
);
|
||||||
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
|
ui.primary.sim.step(&ui.primary.map, Duration::seconds(0.1));
|
||||||
});
|
});
|
||||||
return Transition::Replace(Box::new(SandboxMode::new(ctx)));
|
return Transition::Replace(Box::new(SandboxMode::new(ctx, ui)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ID::Building(b)) = ui.primary.current_selection {
|
if let Some(ID::Building(b)) = ui.primary.current_selection {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
mod score;
|
mod score;
|
||||||
mod spawner;
|
mod spawner;
|
||||||
|
mod stats;
|
||||||
mod time_travel;
|
mod time_travel;
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
@ -24,6 +25,7 @@ pub struct SandboxMode {
|
|||||||
speed: SpeedControls,
|
speed: SpeedControls,
|
||||||
agent_tools: AgentTools,
|
agent_tools: AgentTools,
|
||||||
pub time_travel: time_travel::InactiveTimeTravel,
|
pub time_travel: time_travel::InactiveTimeTravel,
|
||||||
|
stats: stats::TripStats,
|
||||||
common: CommonState,
|
common: CommonState,
|
||||||
parking_heatmap: Option<(Duration, RoadColorer)>,
|
parking_heatmap: Option<(Duration, RoadColorer)>,
|
||||||
intersection_delay_heatmap: Option<(Duration, ObjectColorer)>,
|
intersection_delay_heatmap: Option<(Duration, ObjectColorer)>,
|
||||||
@ -31,11 +33,12 @@ pub struct SandboxMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SandboxMode {
|
impl SandboxMode {
|
||||||
pub fn new(ctx: &mut EventCtx) -> SandboxMode {
|
pub fn new(ctx: &mut EventCtx, ui: &UI) -> SandboxMode {
|
||||||
SandboxMode {
|
SandboxMode {
|
||||||
speed: SpeedControls::new(ctx, None),
|
speed: SpeedControls::new(ctx, None),
|
||||||
agent_tools: AgentTools::new(ctx),
|
agent_tools: AgentTools::new(ctx),
|
||||||
time_travel: time_travel::InactiveTimeTravel::new(),
|
time_travel: time_travel::InactiveTimeTravel::new(),
|
||||||
|
stats: stats::TripStats::new(ui.primary.current_flags.sim_flags.record_stats),
|
||||||
common: CommonState::new(),
|
common: CommonState::new(),
|
||||||
parking_heatmap: None,
|
parking_heatmap: None,
|
||||||
intersection_delay_heatmap: None,
|
intersection_delay_heatmap: None,
|
||||||
@ -64,6 +67,7 @@ impl SandboxMode {
|
|||||||
(hotkey(Key::I), "show/hide intersection delay"),
|
(hotkey(Key::I), "show/hide intersection delay"),
|
||||||
(hotkey(Key::T), "start time traveling"),
|
(hotkey(Key::T), "start time traveling"),
|
||||||
(hotkey(Key::Q), "scoreboard"),
|
(hotkey(Key::Q), "scoreboard"),
|
||||||
|
(None, "trip stats"),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(hotkey(Key::Escape), "quit"),
|
(hotkey(Key::Escape), "quit"),
|
||||||
@ -84,6 +88,7 @@ impl SandboxMode {
|
|||||||
impl State for SandboxMode {
|
impl State for SandboxMode {
|
||||||
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
|
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition {
|
||||||
self.time_travel.record(ui);
|
self.time_travel.record(ui);
|
||||||
|
self.stats.record(ui);
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut txt = Text::prompt("Sandbox Mode");
|
let mut txt = Text::prompt("Sandbox Mode");
|
||||||
@ -118,6 +123,9 @@ impl State for SandboxMode {
|
|||||||
if self.menu.action("scoreboard") {
|
if self.menu.action("scoreboard") {
|
||||||
return Transition::Push(Box::new(score::Scoreboard::new(ctx, ui)));
|
return Transition::Push(Box::new(score::Scoreboard::new(ctx, ui)));
|
||||||
}
|
}
|
||||||
|
if self.menu.action("trip stats") {
|
||||||
|
return Transition::Push(Box::new(stats::ShowStats::new(&self.stats, ui, ctx)));
|
||||||
|
}
|
||||||
if self.menu.action("show/hide parking availability") {
|
if self.menu.action("show/hide parking availability") {
|
||||||
if self.parking_heatmap.is_some() {
|
if self.parking_heatmap.is_some() {
|
||||||
self.parking_heatmap = None;
|
self.parking_heatmap = None;
|
||||||
@ -206,7 +214,7 @@ impl State for SandboxMode {
|
|||||||
if self.speed.is_paused() {
|
if self.speed.is_paused() {
|
||||||
if !ui.primary.sim.is_empty() && self.menu.action("reset sim") {
|
if !ui.primary.sim.is_empty() && self.menu.action("reset sim") {
|
||||||
ui.primary.reset_sim();
|
ui.primary.reset_sim();
|
||||||
return Transition::Replace(Box::new(SandboxMode::new(ctx)));
|
return Transition::Replace(Box::new(SandboxMode::new(ctx, ui)));
|
||||||
}
|
}
|
||||||
if self.menu.action("save sim state") {
|
if self.menu.action("save sim state") {
|
||||||
ctx.loading_screen("savestate", |_, timer| {
|
ctx.loading_screen("savestate", |_, timer| {
|
||||||
|
190
game/src/sandbox/stats.rs
Normal file
190
game/src/sandbox/stats.rs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
use crate::common::ColorLegend;
|
||||||
|
use crate::game::{State, Transition};
|
||||||
|
use crate::ui::UI;
|
||||||
|
use ezgui::{hotkey, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, ModalMenu, Text};
|
||||||
|
use geom::{Distance, Duration, PolyLine, Polygon, Pt2D};
|
||||||
|
use sim::TripMode;
|
||||||
|
|
||||||
|
pub struct TripStats {
|
||||||
|
should_record: bool,
|
||||||
|
samples: Vec<StateAtTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StateAtTime {
|
||||||
|
time: Duration,
|
||||||
|
// These're all cumulative
|
||||||
|
finished_walk_trips: usize,
|
||||||
|
finished_bike_trips: usize,
|
||||||
|
finished_transit_trips: usize,
|
||||||
|
finished_drive_trips: usize,
|
||||||
|
aborted_trips: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TripStats {
|
||||||
|
pub fn new(should_record: bool) -> TripStats {
|
||||||
|
TripStats {
|
||||||
|
should_record,
|
||||||
|
samples: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record(&mut self, ui: &UI) {
|
||||||
|
if !self.should_record {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref state) = self.samples.last() {
|
||||||
|
// Already have this
|
||||||
|
if ui.primary.sim.time() == state.time {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We just loaded a new savestate or reset or something. Clear out our memory.
|
||||||
|
if ui.primary.sim.time() < state.time {
|
||||||
|
self.samples.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = ui.primary.sim.get_finished_trips();
|
||||||
|
let mut state = StateAtTime {
|
||||||
|
time: ui.primary.sim.time(),
|
||||||
|
finished_walk_trips: 0,
|
||||||
|
finished_bike_trips: 0,
|
||||||
|
finished_transit_trips: 0,
|
||||||
|
finished_drive_trips: 0,
|
||||||
|
aborted_trips: t.aborted_trips,
|
||||||
|
};
|
||||||
|
for (_, m, _) in t.finished_trips {
|
||||||
|
match m {
|
||||||
|
TripMode::Walk => {
|
||||||
|
state.finished_walk_trips += 1;
|
||||||
|
}
|
||||||
|
TripMode::Bike => {
|
||||||
|
state.finished_bike_trips += 1;
|
||||||
|
}
|
||||||
|
TripMode::Transit => {
|
||||||
|
state.finished_transit_trips += 1;
|
||||||
|
}
|
||||||
|
TripMode::Drive => {
|
||||||
|
state.finished_drive_trips += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.samples.push(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ShowStats {
|
||||||
|
menu: ModalMenu,
|
||||||
|
draw: Drawable,
|
||||||
|
legend: ColorLegend,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State for ShowStats {
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> Transition {
|
||||||
|
self.menu.handle_event(ctx, None);
|
||||||
|
if self.menu.action("quit") {
|
||||||
|
return Transition::Pop;
|
||||||
|
}
|
||||||
|
Transition::Keep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, g: &mut GfxCtx, _: &UI) {
|
||||||
|
self.menu.draw(g);
|
||||||
|
self.legend.draw(g);
|
||||||
|
|
||||||
|
g.fork_screenspace();
|
||||||
|
g.redraw(&self.draw);
|
||||||
|
g.unfork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShowStats {
|
||||||
|
pub fn new(stats: &TripStats, ui: &UI, ctx: &mut EventCtx) -> ShowStats {
|
||||||
|
let mut batch = GeomBatch::new();
|
||||||
|
|
||||||
|
let x1 = 0.2 * ctx.canvas.window_width;
|
||||||
|
let x2 = 0.8 * ctx.canvas.window_width;
|
||||||
|
let y1 = 0.2 * ctx.canvas.window_height;
|
||||||
|
let y2 = 0.8 * ctx.canvas.window_height;
|
||||||
|
batch.push(
|
||||||
|
Color::grey(0.8),
|
||||||
|
Polygon::rectangle_topleft(
|
||||||
|
Pt2D::new(x1, y1),
|
||||||
|
Distance::meters(x2 - x1),
|
||||||
|
Distance::meters(y2 - y1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let lines: Vec<(&str, Color, Box<dyn Fn(&StateAtTime) -> usize>)> = vec![
|
||||||
|
(
|
||||||
|
"walking",
|
||||||
|
ui.cs.get("unzoomed pedestrian").alpha(1.0),
|
||||||
|
Box::new(|s| s.finished_walk_trips),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"biking",
|
||||||
|
ui.cs.get("unzoomed bike").alpha(1.0),
|
||||||
|
Box::new(|s| s.finished_bike_trips),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"transit",
|
||||||
|
ui.cs.get("unzoomed bus").alpha(1.0),
|
||||||
|
Box::new(|s| s.finished_transit_trips),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"driving",
|
||||||
|
ui.cs.get("unzoomed car").alpha(1.0),
|
||||||
|
Box::new(|s| s.finished_drive_trips),
|
||||||
|
),
|
||||||
|
("aborted", Color::PURPLE, Box::new(|s| s.aborted_trips)),
|
||||||
|
];
|
||||||
|
let legend = ColorLegend::new(
|
||||||
|
"finished trips",
|
||||||
|
lines
|
||||||
|
.iter()
|
||||||
|
.map(|(name, color, _)| (*name, *color))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
for (_, color, getter) in lines {
|
||||||
|
if stats.samples.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let max_y = stats.samples.iter().map(&getter).max().unwrap();
|
||||||
|
let mut pts = Vec::new();
|
||||||
|
if max_y == 0 {
|
||||||
|
pts.push(Pt2D::new(x1, y2));
|
||||||
|
pts.push(Pt2D::new(x2, y2));
|
||||||
|
} else {
|
||||||
|
let num_pts = stats.samples.len().min(10);
|
||||||
|
for i in 0..num_pts {
|
||||||
|
let percent_x = (i as f64) / ((num_pts - 1) as f64);
|
||||||
|
let value = getter(
|
||||||
|
&stats.samples[(percent_x * ((stats.samples.len() - 1) as f64)) as usize],
|
||||||
|
);
|
||||||
|
let percent_y = (value as f64) / (max_y as f64);
|
||||||
|
pts.push(Pt2D::new(
|
||||||
|
x1 + (x2 - x1) * percent_x,
|
||||||
|
// Y inversion! :D
|
||||||
|
y2 - (y2 - y1) * percent_y,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batch.push(
|
||||||
|
color,
|
||||||
|
PolyLine::new(pts).make_polygons(Distance::meters(5.0)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut txt = Text::prompt("Trip Stats");
|
||||||
|
txt.add(Line(format!(
|
||||||
|
"{} samples",
|
||||||
|
abstutil::prettyprint_usize(stats.samples.len())
|
||||||
|
)));
|
||||||
|
ShowStats {
|
||||||
|
menu: ModalMenu::new("Trip Stats", vec![vec![(hotkey(Key::Escape), "quit")]], ctx)
|
||||||
|
.set_prompt(ctx, txt),
|
||||||
|
draw: ctx.prerender.upload(batch),
|
||||||
|
legend,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -156,7 +156,7 @@ fn splash_screen(
|
|||||||
.0
|
.0
|
||||||
.as_str()
|
.as_str()
|
||||||
{
|
{
|
||||||
x if x == sandbox => Some(Transition::Push(Box::new(SandboxMode::new(ctx)))),
|
x if x == sandbox => Some(Transition::Push(Box::new(SandboxMode::new(ctx, ui)))),
|
||||||
x if x == load_map => {
|
x if x == load_map => {
|
||||||
if let Some(name) = wizard.choose_string("Load which map?", || {
|
if let Some(name) = wizard.choose_string("Load which map?", || {
|
||||||
let current_map = ui.primary.map.get_name();
|
let current_map = ui.primary.map.get_name();
|
||||||
@ -171,7 +171,7 @@ fn splash_screen(
|
|||||||
flags.sim_flags.load = abstutil::path_map(&name);
|
flags.sim_flags.load = abstutil::path_map(&name);
|
||||||
*ui = UI::new(flags, ctx, false);
|
*ui = UI::new(flags, ctx, false);
|
||||||
// TODO want to clear wizard and screensaver as we leave this state.
|
// TODO want to clear wizard and screensaver as we leave this state.
|
||||||
Some(Transition::Push(Box::new(SandboxMode::new(ctx))))
|
Some(Transition::Push(Box::new(SandboxMode::new(ctx, ui))))
|
||||||
} else if wizard.aborted() {
|
} else if wizard.aborted() {
|
||||||
Some(Transition::ReplaceWithMode(
|
Some(Transition::ReplaceWithMode(
|
||||||
Box::new(SplashScreen {
|
Box::new(SplashScreen {
|
||||||
|
@ -488,6 +488,7 @@ impl PerMapUI {
|
|||||||
savestate_every: flags.savestate_every,
|
savestate_every: flags.savestate_every,
|
||||||
use_freeform_policy_everywhere: flags.freeform_policy,
|
use_freeform_policy_everywhere: flags.freeform_policy,
|
||||||
disable_block_the_box: flags.disable_block_the_box,
|
disable_block_the_box: flags.disable_block_the_box,
|
||||||
|
record_stats: flags.record_stats,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ pub struct SimFlags {
|
|||||||
pub savestate_every: Option<Duration>,
|
pub savestate_every: Option<Duration>,
|
||||||
pub freeform_policy: bool,
|
pub freeform_policy: bool,
|
||||||
pub disable_block_the_box: bool,
|
pub disable_block_the_box: bool,
|
||||||
|
pub record_stats: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimFlags {
|
impl SimFlags {
|
||||||
@ -26,6 +27,7 @@ impl SimFlags {
|
|||||||
savestate_every: args.optional_parse("--savestate_every", Duration::parse),
|
savestate_every: args.optional_parse("--savestate_every", Duration::parse),
|
||||||
freeform_policy: args.enabled("--freeform_policy"),
|
freeform_policy: args.enabled("--freeform_policy"),
|
||||||
disable_block_the_box: args.enabled("--disable_block_the_box"),
|
disable_block_the_box: args.enabled("--disable_block_the_box"),
|
||||||
|
record_stats: args.enabled("--record_stats"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ impl SimFlags {
|
|||||||
savestate_every: None,
|
savestate_every: None,
|
||||||
freeform_policy: false,
|
freeform_policy: false,
|
||||||
disable_block_the_box: false,
|
disable_block_the_box: false,
|
||||||
|
record_stats: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +68,7 @@ impl SimFlags {
|
|||||||
savestate_every: self.savestate_every,
|
savestate_every: self.savestate_every,
|
||||||
use_freeform_policy_everywhere: self.freeform_policy,
|
use_freeform_policy_everywhere: self.freeform_policy,
|
||||||
disable_block_the_box: self.disable_block_the_box,
|
disable_block_the_box: self.disable_block_the_box,
|
||||||
|
record_stats: self.record_stats,
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.load.starts_with("../data/save/") {
|
if self.load.starts_with("../data/save/") {
|
||||||
|
@ -62,6 +62,7 @@ pub struct SimOptions {
|
|||||||
pub savestate_every: Option<Duration>,
|
pub savestate_every: Option<Duration>,
|
||||||
pub use_freeform_policy_everywhere: bool,
|
pub use_freeform_policy_everywhere: bool,
|
||||||
pub disable_block_the_box: bool,
|
pub disable_block_the_box: bool,
|
||||||
|
pub record_stats: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimOptions {
|
impl SimOptions {
|
||||||
@ -71,6 +72,7 @@ impl SimOptions {
|
|||||||
savestate_every: None,
|
savestate_every: None,
|
||||||
use_freeform_policy_everywhere: false,
|
use_freeform_policy_everywhere: false,
|
||||||
disable_block_the_box: false,
|
disable_block_the_box: false,
|
||||||
|
record_stats: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user