From 58b3d6d2017765d4a8e7ca8c61b4e5f1177985bb Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Sun, 28 Apr 2019 12:50:37 -0700 Subject: [PATCH] moving a/b test edit plugin to a/b test mode as the initial state. putting secondary sim state into the mode directly --- editor/src/abtest/mod.rs | 48 ++++++--- editor/src/abtest/setup.rs | 132 +++++++++++++++++++++++++ editor/src/plugins/edit/a_b_tests.rs | 141 --------------------------- editor/src/plugins/edit/mod.rs | 1 - editor/src/plugins/mod.rs | 1 - editor/src/sandbox/mod.rs | 2 - editor/src/state.rs | 6 +- editor/src/ui.rs | 6 +- 8 files changed, 173 insertions(+), 164 deletions(-) create mode 100644 editor/src/abtest/setup.rs delete mode 100644 editor/src/plugins/edit/a_b_tests.rs delete mode 100644 editor/src/plugins/edit/mod.rs diff --git a/editor/src/abtest/mod.rs b/editor/src/abtest/mod.rs index 50c44d2d6f..270fdd3c50 100644 --- a/editor/src/abtest/mod.rs +++ b/editor/src/abtest/mod.rs @@ -1,4 +1,7 @@ +mod setup; + use crate::game::{GameState, Mode}; +use crate::state::PerMapUI; use crate::ui::ShowEverything; use abstutil::elapsed_seconds; use ezgui::{Color, EventCtx, EventLoopMode, GfxCtx, Text, Wizard}; @@ -10,11 +13,14 @@ use std::time::Instant; const ADJUST_SPEED: f64 = 0.1; pub struct ABTestMode { - desired_speed: f64, // sim seconds per real second - state: State, + pub desired_speed: f64, // sim seconds per real second + pub state: State, + // TODO Urgh, hack. Need to be able to take() it to switch states sometimes. + pub secondary: Option, } -enum State { +pub enum State { + Setup(setup::ABTestSetup), Paused, Running { last_step: Instant, @@ -27,13 +33,19 @@ impl ABTestMode { pub fn new() -> ABTestMode { ABTestMode { desired_speed: 1.0, - state: State::Paused, + state: State::Setup(setup::ABTestSetup::Pick(Wizard::new())), + secondary: None, } } pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode { match state.mode { Mode::ABTest(ref mut mode) => { + if let State::Setup(_) = mode.state { + setup::ABTestSetup::event(state, ctx); + return EventLoopMode::InputOnly; + } + ctx.canvas.handle_event(ctx.input); state.ui.state.primary.current_selection = state.ui.handle_mouseover( ctx, @@ -97,6 +109,10 @@ impl ABTestMode { }; } else if ctx.input.modal_action("run one step of sim") { state.ui.state.primary.sim.step(&state.ui.state.primary.map); + { + let s = mode.secondary.as_mut().unwrap(); + s.sim.step(&s.map); + } //*ctx.recalculate_current_selection = true; } EventLoopMode::InputOnly @@ -118,6 +134,10 @@ impl ABTestMode { 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); + { + let s = mode.secondary.as_mut().unwrap(); + s.sim.step(&s.map); + } //*ctx.recalculate_current_selection = true; *last_step = Instant::now(); @@ -131,6 +151,7 @@ impl ABTestMode { } EventLoopMode::Animation } + State::Setup(_) => unreachable!(), } } _ => unreachable!(), @@ -138,17 +159,20 @@ impl ABTestMode { } pub fn draw(state: &GameState, g: &mut GfxCtx) { + state.ui.new_draw( + g, + None, + HashMap::new(), + &state.ui.state.primary.sim, + &ShowEverything::new(), + ); + match state.mode { Mode::ABTest(ref mode) => match mode.state { - _ => { - state.ui.new_draw( - g, - None, - HashMap::new(), - &state.ui.state.primary.sim, - &ShowEverything::new(), - ); + State::Setup(ref setup) => { + setup.draw(g); } + _ => {} }, _ => unreachable!(), } diff --git a/editor/src/abtest/setup.rs b/editor/src/abtest/setup.rs new file mode 100644 index 0000000000..027e681526 --- /dev/null +++ b/editor/src/abtest/setup.rs @@ -0,0 +1,132 @@ +use crate::abtest::{ABTestMode, State}; +use crate::game::{GameState, Mode}; +use crate::plugins::{choose_edits, choose_scenario, load_ab_test}; +use crate::state::{Flags, PerMapUI, UIState}; +use ezgui::{EventCtx, GfxCtx, LogScroller, Wizard, WrappedWizard}; +use map_model::Map; +use sim::{ABTest, SimFlags}; +use std::path::PathBuf; + +pub enum ABTestSetup { + Pick(Wizard), + Manage(ABTest, LogScroller), +} + +impl ABTestSetup { + pub fn event(state: &mut GameState, ctx: &mut EventCtx) { + match state.mode { + Mode::ABTest(ref mut mode) => match mode.state { + State::Setup(ref mut setup) => match setup { + ABTestSetup::Pick(ref mut wizard) => { + if let Some(ab_test) = pick_ab_test( + &state.ui.state.primary.map, + wizard.wrap(ctx.input, ctx.canvas), + ) { + let scroller = + LogScroller::new(ab_test.test_name.clone(), ab_test.describe()); + *setup = ABTestSetup::Manage(ab_test, scroller); + } else if wizard.aborted() { + state.mode = Mode::SplashScreen(Wizard::new(), None); + } + } + ABTestSetup::Manage(test, ref mut scroller) => { + ctx.input.set_mode_with_prompt( + "A/B Test Editor", + format!("A/B Test Editor for {}", test.test_name), + &ctx.canvas, + ); + if scroller.event(ctx.input) { + state.mode = Mode::SplashScreen(Wizard::new(), None); + } else if ctx.input.modal_action("run A/B test") { + state.mode = launch_test(test, &mut state.ui.state, ctx); + } + } + }, + _ => unreachable!(), + }, + _ => unreachable!(), + } + } + + pub fn draw(&self, g: &mut GfxCtx) { + match self { + ABTestSetup::Pick(wizard) => { + wizard.draw(g); + } + ABTestSetup::Manage(_, scroller) => { + scroller.draw(g); + } + } + } +} + +fn pick_ab_test(map: &Map, mut wizard: WrappedWizard) -> Option { + let load_existing = "Load existing A/B test"; + let create_new = "Create new A/B test"; + if wizard.choose_string("What A/B test to manage?", vec![load_existing, create_new])? + == load_existing + { + load_ab_test(map, &mut wizard, "Load which A/B test?") + } else { + let test_name = wizard.input_string("Name the A/B test")?; + let ab_test = ABTest { + test_name, + map_name: map.get_name().to_string(), + scenario_name: choose_scenario(map, &mut wizard, "What scenario to run?")?, + edits1_name: choose_edits(map, &mut wizard, "For the 1st run, what map edits to use?")?, + edits2_name: choose_edits(map, &mut wizard, "For the 2nd run, what map edits to use?")?, + }; + ab_test.save(); + Some(ab_test) + } +} + +fn launch_test(test: &ABTest, state: &mut UIState, ctx: &mut EventCtx) -> Mode { + println!("Launching A/B test {}...", test.test_name); + let load = PathBuf::from(format!( + "../data/scenarios/{}/{}.json", + test.map_name, test.scenario_name + )); + let current_flags = &state.primary.current_flags; + let rng_seed = if current_flags.sim_flags.rng_seed.is_some() { + current_flags.sim_flags.rng_seed + } else { + Some(42) + }; + + // TODO Cheaper to load the edits for the map and then instantiate the scenario for the + // primary. + let (primary, _) = PerMapUI::new( + Flags { + sim_flags: SimFlags { + load: load.clone(), + rng_seed, + run_name: format!("{} with {}", test.test_name, test.edits1_name), + edits_name: test.edits1_name.clone(), + }, + ..current_flags.clone() + }, + &state.cs, + ctx.prerender, + ); + let (secondary, _) = PerMapUI::new( + Flags { + sim_flags: SimFlags { + load, + rng_seed, + run_name: format!("{} with {}", test.test_name, test.edits2_name), + edits_name: test.edits2_name.clone(), + }, + ..current_flags.clone() + }, + &state.cs, + ctx.prerender, + ); + + state.primary = primary; + Mode::ABTest(ABTestMode { + desired_speed: 1.0, + state: State::Paused, + secondary: Some(secondary), + }) +} diff --git a/editor/src/plugins/edit/a_b_tests.rs b/editor/src/plugins/edit/a_b_tests.rs deleted file mode 100644 index 55373062f4..0000000000 --- a/editor/src/plugins/edit/a_b_tests.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::colors::ColorScheme; -use crate::objects::DrawCtx; -use crate::plugins::{choose_edits, choose_scenario, load_ab_test, BlockingPlugin, PluginCtx}; -use crate::state::{Flags, PerMapUI, PluginsPerMap}; -use ezgui::{GfxCtx, LogScroller, Prerender, Wizard, WrappedWizard}; -use map_model::Map; -use sim::{ABTest, SimFlags}; -use std::path::PathBuf; - -pub enum ABTestManager { - PickABTest(Wizard), - ManageABTest(ABTest, LogScroller), -} - -impl ABTestManager { - pub fn new(ctx: &mut PluginCtx) -> Option { - if ctx.primary.current_selection.is_none() && ctx.input.action_chosen("manage A/B tests") { - return Some(ABTestManager::PickABTest(Wizard::new())); - } - None - } -} - -impl BlockingPlugin for ABTestManager { - fn blocking_event_with_plugins( - &mut self, - ctx: &mut PluginCtx, - primary_plugins: &mut PluginsPerMap, - ) -> bool { - match self { - ABTestManager::PickABTest(ref mut wizard) => { - if let Some(ab_test) = - pick_ab_test(&ctx.primary.map, wizard.wrap(ctx.input, ctx.canvas)) - { - let scroller = LogScroller::new(ab_test.test_name.clone(), ab_test.describe()); - *self = ABTestManager::ManageABTest(ab_test, scroller); - } else if wizard.aborted() { - return false; - } - } - ABTestManager::ManageABTest(test, ref mut scroller) => { - ctx.input.set_mode_with_prompt( - "A/B Test Editor", - format!("A/B Test Editor for {}", test.test_name), - &ctx.canvas, - ); - if ctx.input.modal_action("run A/B test") { - let ((new_primary, new_primary_plugins), new_secondary) = - launch_test(test, &ctx.primary.current_flags, &ctx.cs, &ctx.prerender); - *ctx.primary = new_primary; - *primary_plugins = new_primary_plugins; - *ctx.secondary = Some(new_secondary); - return false; - } - if scroller.event(ctx.input) { - return false; - } - } - } - true - } - - fn draw(&self, g: &mut GfxCtx, _ctx: &DrawCtx) { - match self { - ABTestManager::PickABTest(wizard) => { - wizard.draw(g); - } - ABTestManager::ManageABTest(_, scroller) => { - scroller.draw(g); - } - } - } -} - -fn pick_ab_test(map: &Map, mut wizard: WrappedWizard) -> Option { - let load_existing = "Load existing A/B test"; - let create_new = "Create new A/B test"; - if wizard.choose_string("What A/B test to manage?", vec![load_existing, create_new])? - == load_existing - { - load_ab_test(map, &mut wizard, "Load which A/B test?") - } else { - let test_name = wizard.input_string("Name the A/B test")?; - let ab_test = ABTest { - test_name, - map_name: map.get_name().to_string(), - scenario_name: choose_scenario(map, &mut wizard, "What scenario to run?")?, - edits1_name: choose_edits(map, &mut wizard, "For the 1st run, what map edits to use?")?, - edits2_name: choose_edits(map, &mut wizard, "For the 2nd run, what map edits to use?")?, - }; - ab_test.save(); - Some(ab_test) - } -} - -fn launch_test( - test: &ABTest, - current_flags: &Flags, - cs: &ColorScheme, - prerender: &Prerender, -) -> ((PerMapUI, PluginsPerMap), (PerMapUI, PluginsPerMap)) { - println!("Launching A/B test {}...", test.test_name); - let load = PathBuf::from(format!( - "../data/scenarios/{}/{}.json", - test.map_name, test.scenario_name - )); - let rng_seed = if current_flags.sim_flags.rng_seed.is_some() { - current_flags.sim_flags.rng_seed - } else { - Some(42) - }; - - let primary = PerMapUI::new( - Flags { - sim_flags: SimFlags { - load: load.clone(), - rng_seed, - run_name: format!("{} with {}", test.test_name, test.edits1_name), - edits_name: test.edits1_name.clone(), - }, - ..current_flags.clone() - }, - cs, - prerender, - ); - let secondary = PerMapUI::new( - Flags { - sim_flags: SimFlags { - load, - rng_seed, - run_name: format!("{} with {}", test.test_name, test.edits2_name), - edits_name: test.edits2_name.clone(), - }, - ..current_flags.clone() - }, - cs, - prerender, - ); - // That's all! The scenario will be instantiated. - (primary, secondary) -} diff --git a/editor/src/plugins/edit/mod.rs b/editor/src/plugins/edit/mod.rs deleted file mode 100644 index 4c5ef3c217..0000000000 --- a/editor/src/plugins/edit/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod a_b_tests; diff --git a/editor/src/plugins/mod.rs b/editor/src/plugins/mod.rs index 5ce1a391f6..b0dbef990e 100644 --- a/editor/src/plugins/mod.rs +++ b/editor/src/plugins/mod.rs @@ -1,4 +1,3 @@ -pub mod edit; pub mod sim; pub mod view; diff --git a/editor/src/sandbox/mod.rs b/editor/src/sandbox/mod.rs index 5fb06a2f22..7167bb23e8 100644 --- a/editor/src/sandbox/mod.rs +++ b/editor/src/sandbox/mod.rs @@ -278,8 +278,6 @@ impl SandboxMode { *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); } diff --git a/editor/src/state.rs b/editor/src/state.rs index 9b6736b6a7..fd6b3e1390 100644 --- a/editor/src/state.rs +++ b/editor/src/state.rs @@ -1,7 +1,7 @@ use crate::colors::ColorScheme; use crate::objects::{DrawCtx, RenderingHints, ID}; use crate::plugins; -use crate::plugins::{edit, view, AmbientPlugin, BlockingPlugin, NonblockingPlugin, PluginCtx}; +use crate::plugins::{view, AmbientPlugin, BlockingPlugin, NonblockingPlugin, PluginCtx}; use crate::render::DrawMap; use abstutil::MeasureMemory; use ezgui::EventCtx; @@ -120,10 +120,6 @@ impl UIState { if let Some(p) = view::warp::WarpState::new(&mut ctx) { self.exclusive_blocking_plugin = Some(Box::new(p)); - } else if ctx.secondary.is_none() { - if let Some(p) = edit::a_b_tests::ABTestManager::new(&mut ctx) { - self.exclusive_blocking_plugin = Some(Box::new(p)); - } } } diff --git a/editor/src/ui.rs b/editor/src/ui.rs index a72dc02d8c..eda674058a 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -31,7 +31,6 @@ impl GUI for UI { )); } folders.extend(vec![ - Folder::new("Edit", vec![(Some(Key::B), "manage A/B tests")]), Folder::new("Simulation", vec![(Some(Key::D), "diff all A/B trips")]), Folder::new("View", vec![(Some(Key::J), "warp to an object")]), ]); @@ -40,7 +39,6 @@ impl GUI for UI { fn modal_menus(&self) -> Vec { vec![ - ModalMenu::new("A/B Test Editor", vec![(Key::R, "run A/B test")]), ModalMenu::new("A/B Trip Explorer", vec![(Key::Enter, "quit")]), ModalMenu::new("A/B All Trips Explorer", vec![(Key::Enter, "quit")]), // The new exciting things! @@ -173,6 +171,10 @@ impl GUI for UI { (Key::V, "visualize"), ], ), + ModalMenu::new( + "A/B Test Editor", + vec![(Key::Escape, "quit"), (Key::R, "run A/B test")], + ), ] }