From 1fabf2974700448df717e69e3e250d9783fd2745 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Thu, 25 Apr 2019 09:41:19 -0700 Subject: [PATCH] start the sandbox mode, just with sim controls. remove unused sim score plugin. --- editor/src/game.rs | 25 ++-- editor/src/main.rs | 1 + editor/src/plugins/mod.rs | 4 - editor/src/plugins/sim/controls.rs | 209 --------------------------- editor/src/plugins/sim/mod.rs | 2 - editor/src/plugins/sim/show_score.rs | 84 ----------- editor/src/sandbox/mod.rs | 199 +++++++++++++++++++++++++ editor/src/state.rs | 26 +--- editor/src/ui.rs | 26 ++-- sim/src/sim.rs | 32 ++-- 10 files changed, 254 insertions(+), 354 deletions(-) delete mode 100644 editor/src/plugins/sim/controls.rs delete mode 100644 editor/src/plugins/sim/show_score.rs create mode 100644 editor/src/sandbox/mod.rs diff --git a/editor/src/game.rs b/editor/src/game.rs index 0d0fbc2c1d..aa095c58bf 100644 --- a/editor/src/game.rs +++ b/editor/src/game.rs @@ -1,4 +1,5 @@ use crate::edit::EditMode; +use crate::sandbox::SandboxMode; use crate::state::{Flags, UIState}; use crate::tutorial::TutorialMode; use crate::ui::UI; @@ -23,9 +24,10 @@ pub struct GameState { pub enum Mode { SplashScreen(Wizard, Option<(Screensaver, XorShiftRng)>), - Playing, + Legacy, Edit(EditMode), Tutorial(TutorialMode), + Sandbox(SandboxMode), } impl GameState { @@ -33,7 +35,7 @@ impl GameState { let splash = !flags.no_splash; let mut rng = flags.sim_flags.make_rng(); let mut game = GameState { - mode: Mode::Playing, + mode: Mode::Legacy, ui: UI::new(UIState::new(flags, prerender, true), canvas), }; if splash { @@ -50,8 +52,7 @@ impl GameState { } impl GUI for GameState { - // TODO Don't display this unless mode is Playing! But that probably means we have to drag the - // management of more ezgui state here. + // TODO Totally get rid of this... fn top_menu(&self, canvas: &Canvas) -> Option { self.ui.top_menu(canvas) } @@ -76,7 +77,7 @@ impl GUI for GameState { } EventLoopMode::Animation } - Mode::Playing => { + Mode::Legacy => { let (event_mode, pause) = self.ui.new_event(ctx); if pause { self.mode = Mode::SplashScreen(Wizard::new(), None); @@ -85,6 +86,7 @@ impl GUI for GameState { } Mode::Edit(_) => EditMode::event(self, ctx), Mode::Tutorial(_) => TutorialMode::event(self, ctx), + Mode::Sandbox(_) => SandboxMode::event(self, ctx), } } @@ -94,9 +96,10 @@ impl GUI for GameState { self.ui.draw(g); wizard.draw(g); } - Mode::Playing => self.ui.draw(g), + Mode::Legacy => self.ui.draw(g), Mode::Edit(_) => EditMode::draw(self, g), Mode::Tutorial(_) => TutorialMode::draw(self, g), + Mode::Sandbox(_) => SandboxMode::draw(self, g), } } @@ -165,10 +168,11 @@ fn splash_screen( maybe_screensaver: &mut Option<(Screensaver, XorShiftRng)>, ) -> Option { let mut wizard = raw_wizard.wrap(&mut ctx.input, ctx.canvas); - let play = "Play"; + let sandbox = "Sandbox mode"; let load_map = "Load another map"; let edit = "Edit map"; let tutorial = "Tutorial"; + let legacy = "Legacy mode (ignore this)"; let about = "About"; let quit = "Quit"; @@ -177,11 +181,11 @@ fn splash_screen( match wizard .choose_string( "Welcome to A/B Street!", - vec![play, load_map, edit, tutorial, about, quit], + vec![sandbox, load_map, edit, tutorial, legacy, about, quit], )? .as_str() { - x if x == play => break Some(Mode::Playing), + x if x == sandbox => break Some(Mode::Sandbox(SandboxMode::new())), x if x == load_map => { let current_map = ui.state.primary.map.get_name().to_string(); if let Some((name, _)) = wizard.choose_something::( @@ -197,7 +201,7 @@ fn splash_screen( let mut flags = ui.state.primary.current_flags.clone(); flags.sim_flags.load = PathBuf::from(format!("../data/maps/{}.abst", name)); *ui = UI::new(UIState::new(flags, ctx.prerender, true), ctx.canvas); - break Some(Mode::Playing); + break Some(Mode::Sandbox(SandboxMode::new())); } else if wizard.aborted() { break Some(Mode::SplashScreen(Wizard::new(), maybe_screensaver.take())); } else { @@ -210,6 +214,7 @@ fn splash_screen( ctx.canvas.center_to_map_pt(), ))) } + x if x == legacy => break Some(Mode::Legacy), x if x == about => { if wizard.acknowledge(LogScroller::new( "About A/B Street".to_string(), diff --git a/editor/src/main.rs b/editor/src/main.rs index 338fb356b7..cc1236d032 100644 --- a/editor/src/main.rs +++ b/editor/src/main.rs @@ -4,6 +4,7 @@ mod game; mod objects; mod plugins; mod render; +mod sandbox; mod state; mod tutorial; mod ui; diff --git a/editor/src/plugins/mod.rs b/editor/src/plugins/mod.rs index 1d8ea915f5..6eb5a0636e 100644 --- a/editor/src/plugins/mod.rs +++ b/editor/src/plugins/mod.rs @@ -50,10 +50,6 @@ pub trait AmbientPlugin { fn draw(&self, _g: &mut GfxCtx, _ctx: &DrawCtx) {} } -pub trait AmbientPluginWithPrimaryPlugins { - fn ambient_event_with_plugins(&mut self, _ctx: &mut PluginCtx, _plugins: &mut PluginsPerMap); -} - pub trait NonblockingPlugin { // True means active; false means done, please destroy. fn nonblocking_event(&mut self, _ctx: &mut PluginCtx) -> bool; diff --git a/editor/src/plugins/sim/controls.rs b/editor/src/plugins/sim/controls.rs deleted file mode 100644 index e3753190dc..0000000000 --- a/editor/src/plugins/sim/controls.rs +++ /dev/null @@ -1,209 +0,0 @@ -use crate::plugins::{AmbientPluginWithPrimaryPlugins, PluginCtx}; -use crate::state::PluginsPerMap; -use abstutil::elapsed_seconds; -use ezgui::EventLoopMode; -use geom::Duration; -use sim::{Benchmark, Sim, TIMESTEP}; -use std::mem; -use std::time::Instant; - -const ADJUST_SPEED: f64 = 0.1; - -pub struct SimControls { - desired_speed: f64, // sim seconds per real second - state: State, -} - -enum State { - Paused, - Running { - last_step: Instant, - benchmark: Benchmark, - speed: String, - }, -} - -impl SimControls { - pub fn new() -> SimControls { - SimControls { - desired_speed: 1.0, - state: State::Paused, - } - } - - pub fn run_sim(&mut self, primary_sim: &mut Sim) { - self.state = State::Running { - last_step: Instant::now(), - benchmark: primary_sim.start_benchmark(), - speed: "running".to_string(), - }; - } -} - -impl AmbientPluginWithPrimaryPlugins for SimControls { - fn ambient_event_with_plugins( - &mut self, - ctx: &mut PluginCtx, - primary_plugins: &mut PluginsPerMap, - ) { - if ctx.input.action_chosen("slow down sim") { - self.desired_speed -= ADJUST_SPEED; - self.desired_speed = self.desired_speed.max(0.0); - } - if ctx.input.action_chosen("speed up sim") { - self.desired_speed += ADJUST_SPEED; - } - if ctx.input.action_chosen("reset sim") { - // TODO Handle secondary sim - // TODO Will the sudden change mess up the state in other plugins, or will they detect - // the time change correctly? - // TODO savestate_every gets lost - ctx.primary.sim = Sim::new( - &ctx.primary.map, - ctx.primary.current_flags.sim_flags.run_name.clone(), - None, - ); - self.state = State::Paused; - } - - if ctx.secondary.is_some() && ctx.input.action_chosen("swap the primary/secondary sim") { - println!("Swapping primary/secondary sim"); - // Check out this cool little trick. :D - let (mut secondary, mut secondary_plugins) = ctx.secondary.take().unwrap(); - mem::swap(ctx.primary, &mut secondary); - mem::swap(primary_plugins, &mut secondary_plugins); - *ctx.secondary = Some((secondary, secondary_plugins)); - *ctx.recalculate_current_selection = true; - } - - match self.state { - State::Paused => { - if ctx.input.action_chosen("save sim state") { - ctx.primary.sim.save(); - if let Some((s, _)) = ctx.secondary { - s.sim.save(); - } - } - if ctx.input.action_chosen("load previous sim state") { - match ctx - .primary - .sim - .find_previous_savestate(ctx.primary.sim.time()) - .and_then(|path| Sim::load_savestate(path, None).ok()) - { - Some(new_sim) => { - // TODO From the perspective of other SimMode plugins, does this just - // look like the simulation stepping forwards? - ctx.primary.sim = new_sim; - *ctx.recalculate_current_selection = true; - - if let Some((s, _)) = ctx.secondary { - s.sim = Sim::load_savestate( - s.sim.find_previous_savestate(s.sim.time()).unwrap(), - None, - ) - .unwrap(); - } - } - None => println!( - "Couldn't load previous savestate {:?}", - ctx.primary - .sim - .find_previous_savestate(ctx.primary.sim.time()) - ), - }; - } - if ctx.input.action_chosen("load next sim state") { - match ctx - .primary - .sim - .find_next_savestate(ctx.primary.sim.time()) - .and_then(|path| Sim::load_savestate(path, None).ok()) - { - Some(new_sim) => { - ctx.primary.sim = new_sim; - *ctx.recalculate_current_selection = true; - - if let Some((s, _)) = ctx.secondary { - s.sim = Sim::load_savestate( - s.sim.find_next_savestate(s.sim.time()).unwrap(), - None, - ) - .unwrap(); - } - } - None => println!( - "Couldn't load next savestate {:?}", - ctx.primary.sim.find_next_savestate(ctx.primary.sim.time()) - ), - }; - } - - if ctx.input.action_chosen("run/pause sim") { - self.run_sim(&mut ctx.primary.sim); - } else if ctx.input.action_chosen("run one step of sim") { - ctx.primary.sim.step(&ctx.primary.map); - - *ctx.recalculate_current_selection = true; - if let Some((s, _)) = ctx.secondary { - s.sim.step(&s.map); - } - } - } - State::Running { - ref mut last_step, - ref mut benchmark, - ref mut speed, - } => { - if ctx.input.action_chosen("run/pause sim") { - self.state = State::Paused; - } else { - ctx.hints.mode = EventLoopMode::Animation; - - if ctx.input.nonblocking_is_update_event() { - // TODO https://gafferongames.com/post/fix_your_timestep/ - // TODO This doesn't interact correctly with the fixed 30 Update events - // sent per second. Even Benchmark is kind of wrong. I think we want to - // count the number of steps we've done in the last second, then stop if - // the speed says we should. - let dt_s = elapsed_seconds(*last_step); - if dt_s >= TIMESTEP.inner_seconds() / self.desired_speed { - ctx.input.use_update_event(); - ctx.primary.sim.step(&ctx.primary.map); - - *ctx.recalculate_current_selection = true; - if let Some((s, _)) = ctx.secondary { - s.sim.step(&s.map); - } - *last_step = Instant::now(); - - if benchmark.has_real_time_passed(Duration::seconds(1.0)) { - // I think the benchmark should naturally account for the delay of - // the secondary sim. - *speed = ctx.primary.sim.measure_speed(benchmark); - } - } - } - } - } - }; - - ctx.hints.osd.pad_if_nonempty(); - ctx.hints.osd.add_line(ctx.primary.sim.summary()); - if let Some((s, _)) = ctx.secondary { - ctx.hints.osd.add_line("A/B test running!".to_string()); - ctx.hints.osd.add_line(s.sim.summary()); - } - if let State::Running { ref speed, .. } = self.state { - ctx.hints.osd.add_line(format!( - "Speed: {0} / desired {1:.2}x", - speed, self.desired_speed - )); - } else { - ctx.hints.osd.add_line(format!( - "Speed: paused / desired {0:.2}x", - self.desired_speed - )); - } - } -} diff --git a/editor/src/plugins/sim/mod.rs b/editor/src/plugins/sim/mod.rs index 00cea949d2..fdbc0481d5 100644 --- a/editor/src/plugins/sim/mod.rs +++ b/editor/src/plugins/sim/mod.rs @@ -1,5 +1,3 @@ -pub mod controls; pub mod diff_all; pub mod diff_trip; -pub mod show_score; pub mod time_travel; diff --git a/editor/src/plugins/sim/show_score.rs b/editor/src/plugins/sim/show_score.rs deleted file mode 100644 index 039a0b4df3..0000000000 --- a/editor/src/plugins/sim/show_score.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::objects::DrawCtx; -use crate::plugins::{NonblockingPlugin, PluginCtx}; -use ezgui::{Color, GfxCtx, HorizontalAlignment, Text, VerticalAlignment}; -use geom::Duration; -use sim::ScoreSummary; - -pub struct ShowScoreState { - last_time: Duration, - txt: Text, -} - -impl ShowScoreState { - pub fn new(ctx: &mut PluginCtx) -> Option { - if ctx.input.action_chosen("show/hide sim info sidepanel") { - return Some(panel(ctx)); - } - None - } -} - -impl NonblockingPlugin for ShowScoreState { - fn nonblocking_event(&mut self, ctx: &mut PluginCtx) -> bool { - if ctx.input.action_chosen("show/hide sim info sidepanel") { - return false; - } - if self.last_time != ctx.primary.sim.time() { - *self = panel(ctx); - } - true - } - - fn draw(&self, g: &mut GfxCtx, _ctx: &DrawCtx) { - g.draw_blocking_text( - &self.txt, - (HorizontalAlignment::Right, VerticalAlignment::BelowTopMenu), - ); - } -} - -fn panel(ctx: &mut PluginCtx) -> ShowScoreState { - let mut txt = Text::new(); - if let Some((s, _)) = ctx.secondary { - // TODO More coloring - txt.add_line(ctx.primary.sim.get_name().to_string()); - summarize(&mut txt, ctx.primary.sim.get_score()); - txt.add_line(String::new()); - txt.add_line(s.sim.get_name().to_string()); - summarize(&mut txt, s.sim.get_score()); - } else { - summarize(&mut txt, ctx.primary.sim.get_score()); - } - ShowScoreState { - last_time: ctx.primary.sim.time(), - txt, - } -} - -fn summarize(txt: &mut Text, summary: ScoreSummary) { - txt.add_styled_line( - "Walking".to_string(), - None, - Some(Color::RED.alpha(0.8)), - None, - ); - txt.add_line(format!( - " {}/{} trips done", - (summary.total_walking_trips - summary.pending_walking_trips), - summary.pending_walking_trips - )); - txt.add_line(format!(" {} total", summary.total_walking_trip_time)); - - txt.add_styled_line( - "Driving".to_string(), - None, - Some(Color::BLUE.alpha(0.8)), - None, - ); - txt.add_line(format!( - " {}/{} trips done", - (summary.total_driving_trips - summary.pending_driving_trips), - summary.pending_driving_trips - )); - txt.add_line(format!(" {} total", summary.total_driving_trip_time)); -} diff --git a/editor/src/sandbox/mod.rs b/editor/src/sandbox/mod.rs new file mode 100644 index 0000000000..e43da32478 --- /dev/null +++ b/editor/src/sandbox/mod.rs @@ -0,0 +1,199 @@ +use crate::game::{GameState, Mode}; +use abstutil::elapsed_seconds; +use ezgui::{Color, EventCtx, EventLoopMode, GfxCtx, Text, Wizard}; +use geom::Duration; +use sim::{Benchmark, Sim, TripID}; +use std::collections::HashMap; +use std::time::Instant; + +const ADJUST_SPEED: f64 = 0.1; + +pub struct SandboxMode { + desired_speed: f64, // sim seconds per real second + state: State, + + following: Option, +} + +enum State { + Paused, + Running { + last_step: Instant, + benchmark: Benchmark, + speed: String, + }, +} + +impl SandboxMode { + pub fn new() -> SandboxMode { + SandboxMode { + desired_speed: 1.0, + state: State::Paused, + following: None, + } + } + + pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode { + ctx.canvas.handle_event(ctx.input); + + match state.mode { + // TODO confusing name? ;) + Mode::Sandbox(ref mut mode) => { + let mut txt = Text::new(); + txt.add_styled_line("Sandbox Mode".to_string(), None, Some(Color::BLUE), None); + txt.add_line(state.ui.state.primary.sim.summary()); + if let State::Running { ref speed, .. } = mode.state { + txt.add_line(format!( + "Speed: {0} / desired {1:.2}x", + speed, mode.desired_speed + )); + } else { + txt.add_line(format!( + "Speed: paused / desired {0:.2}x", + mode.desired_speed + )); + } + ctx.input + .set_mode_with_new_prompt("Sandbox Mode", txt, ctx.canvas); + + if ctx.input.modal_action("quit") { + // TODO This shouldn't be necessary when we plumb state around instead of + // sharing it in the old structure. + state.ui.state.primary.sim = Sim::new( + &state.ui.state.primary.map, + state + .ui + .state + .primary + .current_flags + .sim_flags + .run_name + .clone(), + None, + ); + state.mode = Mode::SplashScreen(Wizard::new(), None); + return EventLoopMode::InputOnly; + } + + state.ui.handle_mouseover(ctx, None); + + if ctx.input.modal_action("slow down sim") { + mode.desired_speed -= ADJUST_SPEED; + mode.desired_speed = mode.desired_speed.max(0.0); + } + if ctx.input.modal_action("speed up sim") { + mode.desired_speed += ADJUST_SPEED; + } + if ctx.input.modal_action("reset sim") { + // TODO savestate_every gets lost + state.ui.state.primary.sim = Sim::new( + &state.ui.state.primary.map, + state + .ui + .state + .primary + .current_flags + .sim_flags + .run_name + .clone(), + None, + ); + mode.state = State::Paused; + } + + match mode.state { + State::Paused => { + if ctx.input.modal_action("save sim state") { + state.ui.state.primary.sim.save(); + } + if ctx.input.modal_action("load previous sim state") { + let prev_state = state + .ui + .state + .primary + .sim + .find_previous_savestate(state.ui.state.primary.sim.time()); + match prev_state + .clone() + .and_then(|path| Sim::load_savestate(path, None).ok()) + { + Some(new_sim) => { + state.ui.state.primary.sim = new_sim; + //*ctx.recalculate_current_selection = true; + } + None => { + println!("Couldn't load previous savestate {:?}", prev_state) + } + } + } + if ctx.input.modal_action("load next sim state") { + let next_state = state + .ui + .state + .primary + .sim + .find_next_savestate(state.ui.state.primary.sim.time()); + match next_state + .clone() + .and_then(|path| Sim::load_savestate(path, None).ok()) + { + Some(new_sim) => { + state.ui.state.primary.sim = new_sim; + //*ctx.recalculate_current_selection = true; + } + None => println!("Couldn't load next savestate {:?}", next_state), + } + } + + if ctx.input.modal_action("run/pause sim") { + mode.state = State::Running { + last_step: Instant::now(), + benchmark: state.ui.state.primary.sim.start_benchmark(), + speed: "...".to_string(), + }; + } else if ctx.input.modal_action("run one step of sim") { + state.ui.state.primary.sim.step(&state.ui.state.primary.map); + //*ctx.recalculate_current_selection = true; + } + EventLoopMode::InputOnly + } + State::Running { + ref mut last_step, + ref mut benchmark, + ref mut speed, + } => { + if ctx.input.modal_action("run/pause sim") { + mode.state = State::Paused; + } else if ctx.input.nonblocking_is_update_event() { + // TODO https://gafferongames.com/post/fix_your_timestep/ + // TODO This doesn't interact correctly with the fixed 30 Update events sent + // per second. Even Benchmark is kind of wrong. I think we want to count the + // number of steps we've done in the last second, then stop if the speed says + // we should. + let dt_s = elapsed_seconds(*last_step); + if dt_s >= sim::TIMESTEP.inner_seconds() / mode.desired_speed { + ctx.input.use_update_event(); + state.ui.state.primary.sim.step(&state.ui.state.primary.map); + //*ctx.recalculate_current_selection = true; + *last_step = Instant::now(); + + if benchmark.has_real_time_passed(Duration::seconds(1.0)) { + // I think the benchmark should naturally account for the delay of + // the secondary sim. + *speed = + state.ui.state.primary.sim.measure_speed(benchmark, false); + } + } + } + EventLoopMode::Animation + } + } + } + _ => unreachable!(), + } + } + + pub fn draw(state: &GameState, g: &mut GfxCtx) { + state.ui.new_draw(g, None, HashMap::new()); + } +} diff --git a/editor/src/state.rs b/editor/src/state.rs index 3ab746b9d1..f5a88dae1a 100644 --- a/editor/src/state.rs +++ b/editor/src/state.rs @@ -2,8 +2,7 @@ use crate::colors::ColorScheme; use crate::objects::{DrawCtx, RenderingHints, ID}; use crate::plugins; use crate::plugins::{ - debug, edit, view, AmbientPlugin, AmbientPluginWithPrimaryPlugins, BlockingPlugin, - NonblockingPlugin, PluginCtx, + debug, edit, view, AmbientPlugin, BlockingPlugin, NonblockingPlugin, PluginCtx, }; use crate::render::DrawMap; use abstutil::{MeasureMemory, Timer}; @@ -59,11 +58,9 @@ pub struct UIState { // These are stackable modal plugins. They can all coexist, and they don't block other modal // plugins or ambient plugins. - show_score: Option, pub legend: Option, // Ambient plugins always exist, and they never block anything. - pub sim_controls: plugins::sim::controls::SimControls, pub layers: debug::layers::ToggleableLayers, pub enable_debug_controls: bool, @@ -83,9 +80,7 @@ impl UIState { secondary: None, exclusive_blocking_plugin: None, exclusive_nonblocking_plugin: None, - show_score: None, legend: None, - sim_controls: plugins::sim::controls::SimControls::new(), layers: debug::layers::ToggleableLayers::new(), enable_debug_controls, cs, @@ -104,7 +99,7 @@ impl UIState { // The exclusive_nonblocking_plugins don't color_obj. - // show_score, legend, hider, sim_controls, and layers don't color_obj. + // legend, hider, and layers don't color_obj. for p in &self.primary_plugins.ambient_plugins { if let Some(c) = p.color_for(id, ctx) { return Some(c); @@ -264,18 +259,6 @@ impl UIState { } // Stackable modal plugins - if self.show_score.is_some() { - if !self - .show_score - .as_mut() - .unwrap() - .nonblocking_event(&mut ctx) - { - self.show_score = None; - } - } else if let Some(p) = plugins::sim::show_score::ShowScoreState::new(&mut ctx) { - self.show_score = Some(p); - } if self.legend.is_some() { if !self.legend.as_mut().unwrap().nonblocking_event(&mut ctx) { self.legend = None; @@ -333,8 +316,6 @@ impl UIState { } // Ambient plugins - self.sim_controls - .ambient_event_with_plugins(&mut ctx, &mut self.primary_plugins); for p in self.primary_plugins.ambient_plugins.iter_mut() { p.ambient_event(&mut ctx); } @@ -360,9 +341,6 @@ impl UIState { } // Stackable modals - if let Some(ref p) = self.show_score { - p.draw(g, ctx); - } if let Some(ref p) = self.legend { p.draw(g, ctx); } diff --git a/editor/src/ui.rs b/editor/src/ui.rs index ad9cca09cd..efdcef11c9 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -59,19 +59,9 @@ impl GUI for UI { Folder::new( "Simulation", vec![ - (Some(Key::LeftBracket), "slow down sim"), - (Some(Key::RightBracket), "speed up sim"), - (Some(Key::O), "save sim state"), - (Some(Key::Y), "load previous sim state"), - (Some(Key::U), "load next sim state"), - (Some(Key::Space), "run/pause sim"), - (Some(Key::M), "run one step of sim"), - (Some(Key::Dot), "show/hide sim info sidepanel"), (Some(Key::T), "start time traveling"), (Some(Key::D), "diff all A/B trips"), (Some(Key::S), "seed the sim with agents"), - (Some(Key::LeftAlt), "swap the primary/secondary sim"), - (None, "reset sim"), ], ), Folder::new( @@ -180,7 +170,7 @@ impl GUI for UI { ), ModalMenu::new( "Stop Sign Editor", - vec![(Key::Enter, "quit"), (Key::R, "reset to default")], + vec![(Key::Escape, "quit"), (Key::R, "reset to default")], ), ModalMenu::new( "Traffic Signal Editor", @@ -197,6 +187,20 @@ impl GUI for UI { (Key::M, "add a new pedestrian scramble cycle"), ], ), + ModalMenu::new( + "Sandbox Mode", + vec![ + (Key::Escape, "quit"), + (Key::LeftBracket, "slow down sim"), + (Key::RightBracket, "speed up sim"), + (Key::O, "save sim state"), + (Key::Y, "load previous sim state"), + (Key::U, "load next sim state"), + (Key::Space, "run/pause sim"), + (Key::M, "run one step of sim"), + (Key::R, "reset sim"), + ], + ), ] } diff --git a/sim/src/sim.rs b/sim/src/sim.rs index 0342f70a2b..fd41352696 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -421,14 +421,22 @@ impl Sim { } if benchmark.has_real_time_passed(Duration::seconds(1.0)) { - println!("{}, {}", self.summary(), self.measure_speed(&mut benchmark)); + println!( + "{}, speed = {}", + self.summary(), + self.measure_speed(&mut benchmark, true) + ); } callback(self, map); if Some(self.time()) == time_limit { panic!("Time limit {} hit", self.time); } if self.is_done() { - println!("{}, {}", self.summary(), self.measure_speed(&mut benchmark)); + println!( + "{}, speed = {}", + self.summary(), + self.measure_speed(&mut benchmark, true) + ); break; } } @@ -458,7 +466,11 @@ impl Sim { } } if benchmark.has_real_time_passed(Duration::seconds(1.0)) { - println!("{}, {}", self.summary(), self.measure_speed(&mut benchmark)); + println!( + "{}, speed = {}", + self.summary(), + self.measure_speed(&mut benchmark, true) + ); } if self.time() == time_limit { panic!( @@ -530,19 +542,19 @@ impl Sim { } } - pub fn measure_speed(&self, b: &mut Benchmark) -> String { + pub fn measure_speed(&self, b: &mut Benchmark, details: bool) -> String { let dt = Duration::seconds(abstutil::elapsed_seconds(b.last_real_time)); if dt == Duration::ZERO { - return format!("speed = instantly ({})", self.scheduler.describe_stats()); + return "...".to_string(); } let speed = (self.time - b.last_sim_time) / dt; b.last_real_time = Instant::now(); b.last_sim_time = self.time; - format!( - "speed = {:.2}x ({})", - speed, - self.scheduler.describe_stats() - ) + if details { + format!("{:.2}x ({})", speed, self.scheduler.describe_stats()) + } else { + format!("{:.2}x", speed) + } } }