moving a/b test edit plugin to a/b test mode as the initial state. putting secondary sim state into the mode directly

This commit is contained in:
Dustin Carlino 2019-04-28 12:50:37 -07:00
parent 8cc86f623a
commit 58b3d6d201
8 changed files with 173 additions and 164 deletions

View File

@ -1,4 +1,7 @@
mod setup;
use crate::game::{GameState, Mode}; use crate::game::{GameState, Mode};
use crate::state::PerMapUI;
use crate::ui::ShowEverything; use crate::ui::ShowEverything;
use abstutil::elapsed_seconds; use abstutil::elapsed_seconds;
use ezgui::{Color, EventCtx, EventLoopMode, GfxCtx, Text, Wizard}; use ezgui::{Color, EventCtx, EventLoopMode, GfxCtx, Text, Wizard};
@ -10,11 +13,14 @@ use std::time::Instant;
const ADJUST_SPEED: f64 = 0.1; const ADJUST_SPEED: f64 = 0.1;
pub struct ABTestMode { pub struct ABTestMode {
desired_speed: f64, // sim seconds per real second pub desired_speed: f64, // sim seconds per real second
state: State, pub state: State,
// TODO Urgh, hack. Need to be able to take() it to switch states sometimes.
pub secondary: Option<PerMapUI>,
} }
enum State { pub enum State {
Setup(setup::ABTestSetup),
Paused, Paused,
Running { Running {
last_step: Instant, last_step: Instant,
@ -27,13 +33,19 @@ impl ABTestMode {
pub fn new() -> ABTestMode { pub fn new() -> ABTestMode {
ABTestMode { ABTestMode {
desired_speed: 1.0, 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 { pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode {
match state.mode { match state.mode {
Mode::ABTest(ref mut 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); ctx.canvas.handle_event(ctx.input);
state.ui.state.primary.current_selection = state.ui.handle_mouseover( state.ui.state.primary.current_selection = state.ui.handle_mouseover(
ctx, ctx,
@ -97,6 +109,10 @@ impl ABTestMode {
}; };
} else if ctx.input.modal_action("run one step of sim") { } else if ctx.input.modal_action("run one step of sim") {
state.ui.state.primary.sim.step(&state.ui.state.primary.map); 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; //*ctx.recalculate_current_selection = true;
} }
EventLoopMode::InputOnly EventLoopMode::InputOnly
@ -118,6 +134,10 @@ impl ABTestMode {
if dt_s >= sim::TIMESTEP.inner_seconds() / mode.desired_speed { if dt_s >= sim::TIMESTEP.inner_seconds() / mode.desired_speed {
ctx.input.use_update_event(); ctx.input.use_update_event();
state.ui.state.primary.sim.step(&state.ui.state.primary.map); 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; //*ctx.recalculate_current_selection = true;
*last_step = Instant::now(); *last_step = Instant::now();
@ -131,6 +151,7 @@ impl ABTestMode {
} }
EventLoopMode::Animation EventLoopMode::Animation
} }
State::Setup(_) => unreachable!(),
} }
} }
_ => unreachable!(), _ => unreachable!(),
@ -138,17 +159,20 @@ impl ABTestMode {
} }
pub fn draw(state: &GameState, g: &mut GfxCtx) { 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 { match state.mode {
Mode::ABTest(ref mode) => match mode.state { Mode::ABTest(ref mode) => match mode.state {
_ => { State::Setup(ref setup) => {
state.ui.new_draw( setup.draw(g);
g,
None,
HashMap::new(),
&state.ui.state.primary.sim,
&ShowEverything::new(),
);
} }
_ => {}
}, },
_ => unreachable!(), _ => unreachable!(),
} }

132
editor/src/abtest/setup.rs Normal file
View File

@ -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<ABTest> {
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),
})
}

View File

@ -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<ABTestManager> {
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<ABTest> {
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)
}

View File

@ -1 +0,0 @@
pub mod a_b_tests;

View File

@ -1,4 +1,3 @@
pub mod edit;
pub mod sim; pub mod sim;
pub mod view; pub mod view;

View File

@ -278,8 +278,6 @@ impl SandboxMode {
*last_step = Instant::now(); *last_step = Instant::now();
if benchmark.has_real_time_passed(Duration::seconds(1.0)) { 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 = *speed =
state.ui.state.primary.sim.measure_speed(benchmark, false); state.ui.state.primary.sim.measure_speed(benchmark, false);
} }

View File

@ -1,7 +1,7 @@
use crate::colors::ColorScheme; use crate::colors::ColorScheme;
use crate::objects::{DrawCtx, RenderingHints, ID}; use crate::objects::{DrawCtx, RenderingHints, ID};
use crate::plugins; 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 crate::render::DrawMap;
use abstutil::MeasureMemory; use abstutil::MeasureMemory;
use ezgui::EventCtx; use ezgui::EventCtx;
@ -120,10 +120,6 @@ impl UIState {
if let Some(p) = view::warp::WarpState::new(&mut ctx) { if let Some(p) = view::warp::WarpState::new(&mut ctx) {
self.exclusive_blocking_plugin = Some(Box::new(p)); 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));
}
} }
} }

View File

@ -31,7 +31,6 @@ impl GUI for UI {
)); ));
} }
folders.extend(vec![ 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("Simulation", vec![(Some(Key::D), "diff all A/B trips")]),
Folder::new("View", vec![(Some(Key::J), "warp to an object")]), Folder::new("View", vec![(Some(Key::J), "warp to an object")]),
]); ]);
@ -40,7 +39,6 @@ impl GUI for UI {
fn modal_menus(&self) -> Vec<ModalMenu> { fn modal_menus(&self) -> Vec<ModalMenu> {
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 Trip Explorer", vec![(Key::Enter, "quit")]),
ModalMenu::new("A/B All Trips Explorer", vec![(Key::Enter, "quit")]), ModalMenu::new("A/B All Trips Explorer", vec![(Key::Enter, "quit")]),
// The new exciting things! // The new exciting things!
@ -173,6 +171,10 @@ impl GUI for UI {
(Key::V, "visualize"), (Key::V, "visualize"),
], ],
), ),
ModalMenu::new(
"A/B Test Editor",
vec![(Key::Escape, "quit"), (Key::R, "run A/B test")],
),
] ]
} }