converting abtest mode

This commit is contained in:
Dustin Carlino 2019-06-22 12:26:52 -07:00
parent 6f2f6a3a75
commit 859429a493
5 changed files with 278 additions and 328 deletions

View File

@ -1,13 +1,11 @@
mod score;
mod setup;
pub mod setup;
use crate::common::{CommonState, SpeedControls};
use crate::game::{GameState, Mode};
use crate::render::{DrawOptions, MIN_ZOOM_FOR_DETAIL};
use crate::render::MIN_ZOOM_FOR_DETAIL;
use crate::state::{State, Transition};
use crate::ui::{PerMapUI, ShowEverything, UI};
use ezgui::{
hotkey, Color, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, ModalMenu, Text, Wizard,
};
use ezgui::{hotkey, Color, EventCtx, EventLoopMode, GeomBatch, GfxCtx, Key, ModalMenu, Text};
use geom::{Circle, Distance, Duration, Line, PolyLine};
use map_model::{Map, LANE_THICKNESS};
use serde_derive::{Deserialize, Serialize};
@ -16,9 +14,8 @@ use sim::{Sim, TripID};
pub struct ABTestMode {
menu: ModalMenu,
speed: SpeedControls,
pub state: State,
// TODO Urgh, hack. Need to be able to take() it to switch states sometimes.
pub secondary: Option<PerMapUI>,
secondary: Option<PerMapUI>,
diff_trip: Option<DiffOneTrip>,
diff_all: Option<DiffAllTrips>,
// TODO Not present in Setup state.
@ -26,14 +23,13 @@ pub struct ABTestMode {
test_name: String,
}
pub enum State {
Setup(setup::ABTestSetup),
Playing,
Scoreboard(score::Scoreboard),
}
impl ABTestMode {
pub fn new(ctx: &mut EventCtx, ui: &mut UI, test_name: &str) -> ABTestMode {
pub fn new(
ctx: &mut EventCtx,
ui: &mut UI,
test_name: &str,
secondary: PerMapUI,
) -> ABTestMode {
ui.primary.current_selection = None;
ABTestMode {
@ -58,148 +54,133 @@ impl ABTestMode {
ctx,
),
speed: SpeedControls::new(ctx, None),
state: State::Setup(setup::ABTestSetup::Pick(Wizard::new())),
secondary: None,
secondary: Some(secondary),
diff_trip: None,
diff_all: None,
common: CommonState::new(),
test_name: test_name.to_string(),
}
}
}
pub fn event(state: &mut GameState, ctx: &mut EventCtx) -> EventLoopMode {
match state.mode {
Mode::ABTest(ref mut mode) => {
match mode.state {
State::Setup(_) => {
setup::ABTestSetup::event(state, ctx);
EventLoopMode::InputOnly
}
State::Scoreboard(ref mut s) => {
if s.event(ctx, &state.ui.primary, mode.secondary.as_ref().unwrap()) {
mode.state = State::Playing;
mode.speed.pause();
}
EventLoopMode::InputOnly
}
State::Playing => {
let mut txt = Text::prompt("A/B Test Mode");
txt.add_line(state.ui.primary.map.get_edits().edits_name.clone());
if let Some(ref diff) = mode.diff_trip {
txt.add_line(format!("Showing diff for {}", diff.trip));
} else if let Some(ref diff) = mode.diff_all {
txt.add_line(format!(
"Showing diffs for all. {} trips same, {} differ",
diff.same_trips,
diff.lines.len()
));
}
txt.add_line(state.ui.primary.sim.summary());
mode.menu.handle_event(ctx, Some(txt));
impl State for ABTestMode {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
let mut txt = Text::prompt("A/B Test Mode");
txt.add_line(ui.primary.map.get_edits().edits_name.clone());
if let Some(ref diff) = self.diff_trip {
txt.add_line(format!("Showing diff for {}", diff.trip));
} else if let Some(ref diff) = self.diff_all {
txt.add_line(format!(
"Showing diffs for all. {} trips same, {} differ",
diff.same_trips,
diff.lines.len()
));
}
txt.add_line(ui.primary.sim.summary());
self.menu.handle_event(ctx, Some(txt));
ctx.canvas.handle_event(ctx.input);
if ctx.redo_mouseover() {
state.ui.primary.current_selection =
state.ui.recalculate_current_selection(
ctx,
&state.ui.primary.sim,
&ShowEverything::new(),
false,
);
}
if let Some(evmode) = mode.common.event(ctx, &mut state.ui, &mut mode.menu)
{
return evmode;
}
ctx.canvas.handle_event(ctx.input);
if ctx.redo_mouseover() {
ui.primary.current_selection = ui.recalculate_current_selection(
ctx,
&ui.primary.sim,
&ShowEverything::new(),
false,
);
}
if let Some(evmode) = self.common.event(ctx, ui, &mut self.menu) {
return (Transition::Keep, evmode);
}
if mode.menu.action("quit") {
// TODO This shouldn't be necessary when we plumb state around instead of
// sharing it in the old structure.
state.ui.primary.reset_sim();
// Note destroying mode.secondary has some noticeable delay.
state.mode = Mode::SplashScreen(Wizard::new(), None);
return EventLoopMode::InputOnly;
}
if self.menu.action("quit") {
// TODO Should we clear edits too?
ui.primary.reset_sim();
// Note destroying mode.secondary has some noticeable delay.
return (Transition::Pop, EventLoopMode::InputOnly);
}
if mode.menu.action("swap") {
let secondary = mode.secondary.take().unwrap();
let primary = std::mem::replace(&mut state.ui.primary, secondary);
mode.secondary = Some(primary);
mode.recalculate_stuff(&mut state.ui, ctx);
}
if self.menu.action("swap") {
let secondary = self.secondary.take().unwrap();
let primary = std::mem::replace(&mut ui.primary, secondary);
self.secondary = Some(primary);
self.recalculate_stuff(ui, ctx);
}
if mode.menu.action("scoreboard") {
mode.state = State::Scoreboard(score::Scoreboard::new(
ctx,
&state.ui.primary,
mode.secondary.as_ref().unwrap(),
));
return EventLoopMode::InputOnly;
}
if self.menu.action("scoreboard") {
self.speed.pause();
return (
Transition::Push(Box::new(score::Scoreboard::new(
ctx,
&ui.primary,
self.secondary.as_ref().unwrap(),
))),
EventLoopMode::InputOnly,
);
}
if mode.menu.action("save state") {
mode.savestate(&mut state.ui.primary);
}
if self.menu.action("save state") {
self.savestate(&mut ui.primary);
}
if mode.diff_trip.is_some() {
if mode.menu.action("stop diffing trips") {
mode.diff_trip = None;
}
} else if mode.diff_all.is_some() {
if mode.menu.action("stop diffing trips") {
mode.diff_all = None;
}
} else {
if state.ui.primary.current_selection.is_none()
&& mode.menu.action("diff all trips")
{
mode.diff_all = Some(DiffAllTrips::new(
&mut state.ui.primary,
mode.secondary.as_mut().unwrap(),
));
} else if let Some(agent) = state
.ui
.primary
.current_selection
.and_then(|id| id.agent_id())
{
if let Some(trip) = state.ui.primary.sim.agent_to_trip(agent) {
if ctx.input.contextual_action(
Key::B,
&format!("Show {}'s parallel world", agent),
) {
mode.diff_trip = Some(DiffOneTrip::new(
trip,
&state.ui.primary,
mode.secondary.as_ref().unwrap(),
));
}
}
}
}
if let Some(dt) =
mode.speed
.event(ctx, &mut mode.menu, state.ui.primary.sim.time())
{
mode.step(dt, &mut state.ui, ctx);
}
if mode.speed.is_paused() {
if mode.menu.action("step forwards 0.1s") {
mode.step(Duration::seconds(0.1), &mut state.ui, ctx);
}
EventLoopMode::InputOnly
} else {
EventLoopMode::Animation
}
if self.diff_trip.is_some() {
if self.menu.action("stop diffing trips") {
self.diff_trip = None;
}
} else if self.diff_all.is_some() {
if self.menu.action("stop diffing trips") {
self.diff_all = None;
}
} else {
if ui.primary.current_selection.is_none() && self.menu.action("diff all trips") {
self.diff_all = Some(DiffAllTrips::new(
&mut ui.primary,
self.secondary.as_mut().unwrap(),
));
} else if let Some(agent) = ui.primary.current_selection.and_then(|id| id.agent_id()) {
if let Some(trip) = ui.primary.sim.agent_to_trip(agent) {
if ctx
.input
.contextual_action(Key::B, &format!("Show {}'s parallel world", agent))
{
self.diff_trip = Some(DiffOneTrip::new(
trip,
&ui.primary,
self.secondary.as_ref().unwrap(),
));
}
}
}
_ => unreachable!(),
}
if let Some(dt) = self.speed.event(ctx, &mut self.menu, ui.primary.sim.time()) {
self.step(dt, ui, ctx);
}
if self.speed.is_paused() {
if self.menu.action("step forwards 0.1s") {
self.step(Duration::seconds(0.1), ui, ctx);
}
(Transition::Keep, EventLoopMode::InputOnly)
} else {
(Transition::Keep, EventLoopMode::Animation)
}
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
self.common.draw(g, ui);
if let Some(ref diff) = self.diff_trip {
diff.draw(g, ui);
}
if let Some(ref diff) = self.diff_all {
diff.draw(g, ui);
}
self.menu.draw(g);
self.speed.draw(g);
}
}
impl ABTestMode {
fn step(&mut self, dt: Duration, ui: &mut UI, ctx: &EventCtx) {
ui.primary.sim.step(&ui.primary.map, dt);
{
@ -228,50 +209,6 @@ impl ABTestMode {
ui.recalculate_current_selection(ctx, &ui.primary.sim, &ShowEverything::new(), false);
}
pub fn draw(state: &GameState, g: &mut GfxCtx) {
match state.mode {
Mode::ABTest(ref mode) => match mode.state {
State::Setup(ref setup) => {
state.ui.draw(
g,
DrawOptions::new(),
&state.ui.primary.sim,
&ShowEverything::new(),
);
setup.draw(g);
}
State::Scoreboard(ref s) => {
state.ui.draw(
g,
DrawOptions::new(),
&state.ui.primary.sim,
&ShowEverything::new(),
);
s.draw(g);
}
State::Playing => {
state.ui.draw(
g,
mode.common.draw_options(&state.ui),
&state.ui.primary.sim,
&ShowEverything::new(),
);
mode.common.draw(g, &state.ui);
if let Some(ref diff) = mode.diff_trip {
diff.draw(g, &state.ui);
}
if let Some(ref diff) = mode.diff_all {
diff.draw(g, &state.ui);
}
mode.menu.draw(g);
mode.speed.draw(g);
}
},
_ => unreachable!(),
}
}
fn savestate(&mut self, primary: &mut PerMapUI) {
// Temporarily move everything into this structure.
let blank_map = Map::blank();

View File

@ -1,16 +1,18 @@
use crate::state::{State, Transition};
use crate::ui::PerMapUI;
use crate::ui::UI;
use ezgui::{
hotkey, EventCtx, GfxCtx, HorizontalAlignment, Key, ModalMenu, Text, VerticalAlignment, Wizard,
WrappedWizard,
hotkey, EventCtx, EventLoopMode, GfxCtx, HorizontalAlignment, Key, ModalMenu, Text,
VerticalAlignment, Wizard, WrappedWizard,
};
use geom::Duration;
use itertools::Itertools;
use sim::{FinishedTrips, TripID, TripMode};
use std::collections::BTreeMap;
pub enum Scoreboard {
Summary(ModalMenu, Text),
BrowseTrips(CompareTrips, Wizard),
pub struct Scoreboard {
menu: ModalMenu,
summary: Text,
}
impl Scoreboard {
@ -76,52 +78,55 @@ impl Scoreboard {
}
}
Scoreboard::Summary(menu, summary)
Scoreboard { menu, summary }
}
}
impl State for Scoreboard {
fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> (Transition, EventLoopMode) {
self.menu.handle_event(ctx, None);
if self.menu.action("quit") {
return (Transition::Pop, EventLoopMode::InputOnly);
}
if self.menu.action("browse trips") {
/*self = Scoreboard::BrowseTrips(
CompareTrips::new(
primary.sim.get_finished_trips(),
secondary.sim.get_finished_trips(),
),
Wizard::new(),
);*/
}
(Transition::Keep, EventLoopMode::InputOnly)
}
// Returns true if done and we should go back to main A/B test mode.
pub fn event(&mut self, ctx: &mut EventCtx, primary: &PerMapUI, secondary: &PerMapUI) -> bool {
match self {
Scoreboard::Summary(ref mut menu, _) => {
menu.handle_event(ctx, None);
if menu.action("quit") {
return true;
}
if menu.action("browse trips") {
*self = Scoreboard::BrowseTrips(
CompareTrips::new(
primary.sim.get_finished_trips(),
secondary.sim.get_finished_trips(),
),
Wizard::new(),
);
}
}
Scoreboard::BrowseTrips(ref trips, ref mut wizard) => {
if pick_trip(trips, &mut wizard.wrap(ctx)).is_some() {
// TODO show more details...
*self = Scoreboard::new(ctx, primary, secondary);
} else if wizard.aborted() {
*self = Scoreboard::new(ctx, primary, secondary);
}
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
g.draw_blocking_text(
&self.summary,
(HorizontalAlignment::Center, VerticalAlignment::Center),
);
self.menu.draw(g);
}
}
struct BrowseTrips {
trips: CompareTrips,
wizard: Wizard,
}
impl State for BrowseTrips {
fn event(&mut self, ctx: &mut EventCtx, _: &mut UI) -> (Transition, EventLoopMode) {
if pick_trip(&self.trips, &mut self.wizard.wrap(ctx)).is_some() {
// TODO show more details...
return (Transition::Pop, EventLoopMode::InputOnly);
} else if self.wizard.aborted() {
return (Transition::Pop, EventLoopMode::InputOnly);
}
false
(Transition::Keep, EventLoopMode::InputOnly)
}
pub fn draw(&self, g: &mut GfxCtx) {
match self {
Scoreboard::Summary(ref menu, ref txt) => {
g.draw_blocking_text(
txt,
(HorizontalAlignment::Center, VerticalAlignment::Center),
);
menu.draw(g);
}
Scoreboard::BrowseTrips(_, ref wizard) => {
wizard.draw(g);
}
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
}

View File

@ -1,100 +1,114 @@
use crate::abtest::{ABTestMode, ABTestSavestate, State};
use crate::abtest::{ABTestMode, ABTestSavestate};
use crate::edit::apply_map_edits;
use crate::game::{GameState, Mode};
use crate::render::DrawMap;
use crate::state::{State, Transition};
use crate::ui::{Flags, PerMapUI, UI};
use ezgui::{hotkey, EventCtx, GfxCtx, Key, LogScroller, ModalMenu, Wizard, WrappedWizard};
use ezgui::{
hotkey, EventCtx, EventLoopMode, GfxCtx, Key, LogScroller, ModalMenu, Wizard, WrappedWizard,
};
use geom::Duration;
use map_model::{Map, MapEdits};
use sim::{ABTest, Scenario, SimFlags};
use std::path::PathBuf;
pub enum ABTestSetup {
Pick(Wizard),
Manage(ModalMenu, ABTest, LogScroller),
LoadSavestate(ABTest, Wizard),
pub struct PickABTest {
wizard: Wizard,
}
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.primary.map, wizard.wrap(ctx))
{
let scroller =
LogScroller::new(ab_test.test_name.clone(), ab_test.describe());
*setup = ABTestSetup::Manage(
ModalMenu::new(
&format!("A/B Test Editor for {}", ab_test.test_name),
vec![
(hotkey(Key::Escape), "quit"),
(hotkey(Key::R), "run A/B test"),
(hotkey(Key::L), "load savestate"),
],
ctx,
),
ab_test,
scroller,
);
} else if wizard.aborted() {
state.mode = Mode::SplashScreen(Wizard::new(), None);
}
}
ABTestSetup::LoadSavestate(ref test, ref mut wizard) => {
if let Some(ss) = pick_savestate(test, &mut wizard.wrap(ctx)) {
state.mode = launch_savestate(test, ss, &mut state.ui, ctx);
} else if wizard.aborted() {
// TODO Here's where we need to push and pop states.
let scroller =
LogScroller::new(test.test_name.clone(), test.describe());
*setup = ABTestSetup::Manage(
ModalMenu::new(
&format!("A/B Test Editor for {}", test.test_name),
vec![
(hotkey(Key::Escape), "quit"),
(hotkey(Key::R), "run A/B test"),
(hotkey(Key::L), "load savestate"),
],
ctx,
),
test.clone(),
scroller,
);
}
}
ABTestSetup::Manage(ref mut menu, test, ref mut scroller) => {
ctx.canvas.handle_event(ctx.input);
menu.handle_event(ctx, None);
if scroller.event(ctx.input) {
state.mode = Mode::SplashScreen(Wizard::new(), None);
} else if menu.action("run A/B test") {
state.mode = launch_test(test, &mut state.ui, ctx);
} else if menu.action("load savestate") {
*setup = ABTestSetup::LoadSavestate(test.clone(), Wizard::new());
}
}
},
_ => unreachable!(),
},
_ => unreachable!(),
impl PickABTest {
pub fn new() -> PickABTest {
PickABTest {
wizard: Wizard::new(),
}
}
}
pub fn draw(&self, g: &mut GfxCtx) {
match self {
ABTestSetup::Pick(wizard) => {
wizard.draw(g);
}
ABTestSetup::LoadSavestate(_, wizard) => {
wizard.draw(g);
}
ABTestSetup::Manage(ref menu, _, scroller) => {
scroller.draw(g);
menu.draw(g);
}
impl State for PickABTest {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
if let Some(ab_test) = pick_ab_test(&ui.primary.map, self.wizard.wrap(ctx)) {
let scroller = LogScroller::new(ab_test.test_name.clone(), ab_test.describe());
return (
Transition::Replace(Box::new(ABTestSetup {
menu: ModalMenu::new(
&format!("A/B Test Editor for {}", ab_test.test_name),
vec![
(hotkey(Key::Escape), "quit"),
(hotkey(Key::R), "run A/B test"),
(hotkey(Key::L), "load savestate"),
],
ctx,
),
ab_test,
scroller,
})),
EventLoopMode::InputOnly,
);
} else if self.wizard.aborted() {
return (Transition::Pop, EventLoopMode::InputOnly);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
self.wizard.draw(g);
}
}
struct ABTestSetup {
menu: ModalMenu,
ab_test: ABTest,
scroller: LogScroller,
}
impl State for ABTestSetup {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
ctx.canvas.handle_event(ctx.input);
self.menu.handle_event(ctx, None);
if self.scroller.event(ctx.input) {
return (Transition::Pop, EventLoopMode::InputOnly);
} else if self.menu.action("run A/B test") {
return (
Transition::Replace(Box::new(launch_test(&self.ab_test, ui, ctx))),
EventLoopMode::InputOnly,
);
} else if self.menu.action("load savestate") {
return (
Transition::Push(Box::new(LoadSavestate {
ab_test: self.ab_test.clone(),
wizard: Wizard::new(),
})),
EventLoopMode::InputOnly,
);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
fn draw(&self, g: &mut GfxCtx, ui: &UI) {
self.scroller.draw(g);
self.menu.draw(g);
}
}
struct LoadSavestate {
ab_test: ABTest,
wizard: Wizard,
}
impl State for LoadSavestate {
fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> (Transition, EventLoopMode) {
if let Some(ss) = pick_savestate(&self.ab_test, &mut self.wizard.wrap(ctx)) {
return (
Transition::Replace(Box::new(launch_savestate(&self.ab_test, ss, ui, ctx))),
EventLoopMode::InputOnly,
);
} else if self.wizard.aborted() {
return (Transition::Pop, EventLoopMode::InputOnly);
}
(Transition::Keep, EventLoopMode::InputOnly)
}
fn draw(&self, g: &mut GfxCtx, _: &UI) {
self.wizard.draw(g);
}
}
@ -119,7 +133,7 @@ fn pick_ab_test(map: &Map, mut wizard: WrappedWizard) -> Option<ABTest> {
}
}
fn launch_test(test: &ABTest, ui: &mut UI, ctx: &mut EventCtx) -> Mode {
fn launch_test(test: &ABTest, ui: &mut UI, ctx: &mut EventCtx) -> ABTestMode {
let secondary = ctx.loading_screen(
&format!("Launching A/B test {}", test.test_name),
|ctx, mut timer| {
@ -188,13 +202,10 @@ fn launch_test(test: &ABTest, ui: &mut UI, ctx: &mut EventCtx) -> Mode {
},
);
let mut mode = ABTestMode::new(ctx, ui, &test.test_name);
mode.state = State::Playing;
mode.secondary = Some(secondary);
Mode::ABTest(mode)
ABTestMode::new(ctx, ui, &test.test_name, secondary)
}
fn launch_savestate(test: &ABTest, ss_path: String, ui: &mut UI, ctx: &mut EventCtx) -> Mode {
fn launch_savestate(test: &ABTest, ss_path: String, ui: &mut UI, ctx: &mut EventCtx) -> ABTestMode {
ctx.loading_screen(
&format!("Launch A/B test from savestate {}", ss_path),
|ctx, mut timer| {
@ -213,9 +224,7 @@ fn launch_savestate(test: &ABTest, ss_path: String, ui: &mut UI, ctx: &mut Event
timer.stop("setup primary");
timer.start("setup secondary");
let mut mode = ABTestMode::new(ctx, ui, &test.test_name);
mode.state = State::Playing;
mode.secondary = Some(PerMapUI {
let secondary = PerMapUI {
draw_map: DrawMap::new(
&ss.secondary_map,
&ui.primary.current_flags,
@ -228,9 +237,10 @@ fn launch_savestate(test: &ABTest, ss_path: String, ui: &mut UI, ctx: &mut Event
current_selection: None,
// TODO Hack... can we just remove these?
current_flags: ui.primary.current_flags.clone(),
});
};
timer.stop("setup secondary");
Mode::ABTest(mode)
ABTestMode::new(ctx, ui, &test.test_name, secondary)
},
)
}

View File

@ -1,4 +1,4 @@
//use crate::abtest::ABTestMode;
use crate::abtest::setup::PickABTest;
use crate::debug::DebugMode;
use crate::edit::EditMode;
//use crate::mission::MissionEditMode;
@ -280,10 +280,8 @@ fn splash_screen(
x if x == edit => Some(Transition::Push(Box::new(EditMode::new(ctx, ui)))),
//x if x == tutorial => break Some(Mode::Tutorial(TutorialMode::new(ctx, ui))),
x if x == debug => Some(Transition::Push(Box::new(DebugMode::new(ctx, ui)))),
/*x if x == mission => break Some(Mode::Mission(MissionEditMode::new(ctx, ui))),
x if x == abtest => {
break Some(Mode::ABTest(ABTestMode::new(ctx, ui, "unnamed a/b test")))
}*/
//x if x == mission => break Some(Mode::Mission(MissionEditMode::new(ctx, ui))),
x if x == abtest => Some(Transition::Push(Box::new(PickABTest::new()))),
x if x == about => {
if wizard.acknowledge(
"About A/B Street",

View File

@ -1,4 +1,4 @@
//mod abtest;
mod abtest;
mod common;
mod debug;
mod edit;