From e6c1d960ec957b175aa938e930df726aa3d3d187 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 14 Aug 2020 14:09:53 -0700 Subject: [PATCH] In traffic=none mode, allow recording (most of) the manually specified trips as a Scenario to later re-run. This is useful for quickly defining "test cases" for development, and it's a start to a UI for letting players specify (and eventually share) traffic patterns they define. --- game/src/sandbox/gameplay/freeform.rs | 36 ++++++++++++++++++++++++--- sim/src/sim.rs | 10 +++++--- sim/src/trips.rs | 36 ++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/game/src/sandbox/gameplay/freeform.rs b/game/src/sandbox/gameplay/freeform.rs index 7f0fff6289..2db772286f 100644 --- a/game/src/sandbox/gameplay/freeform.rs +++ b/game/src/sandbox/gameplay/freeform.rs @@ -1,7 +1,7 @@ use crate::app::{App, ShowEverything}; use crate::common::{CityPicker, CommonState}; use crate::edit::EditMode; -use crate::game::{ChooseSomething, State, Transition}; +use crate::game::{ChooseSomething, PopupMsg, PromptInput, State, Transition}; use crate::helpers::{nice_map_name, ID}; use crate::sandbox::gameplay::{GameplayMode, GameplayState}; use crate::sandbox::SandboxControls; @@ -72,6 +72,32 @@ impl GameplayState for Freeform { GameplayMode::Freeform(abstutil::path_map(app.primary.map.get_name())), ))), "Start a new trip" => Some(Transition::Push(AgentSpawner::new(ctx, None))), + "Record trips as a scenario" => Some(Transition::Push(PromptInput::new( + ctx, + "Name this scenario", + Box::new(|name, ctx, app| { + if abstutil::file_exists(abstutil::path_scenario( + app.primary.map.get_name(), + &name, + )) { + Transition::Push(PopupMsg::new( + ctx, + "Error", + vec![format!( + "A scenario called \"{}\" already exists, please pick another \ + name", + name + )], + )) + } else { + app.primary + .sim + .generate_scenario(&app.primary.map, name) + .save(); + Transition::Pop + } + }), + ))), _ => unreachable!(), }, _ => None, @@ -99,9 +125,11 @@ fn make_top_center(ctx: &mut EventCtx, app: &App) -> Composite { Btn::svg_def("system/assets/tools/edit_map.svg").build(ctx, "edit map", lctrl(Key::E)), ]) .centered(), - Btn::text_fg("Start a new trip") - .build_def(ctx, None) - .centered_horiz(), + Widget::row(vec![ + Btn::text_fg("Start a new trip").build_def(ctx, None), + Btn::text_fg("Record trips as a scenario").build_def(ctx, None), + ]) + .centered(), Text::from_all(vec![ Line("Select an intersection and press "), Line(Key::Z.describe()).fg(ctx.style().hotkey_color), diff --git a/sim/src/sim.rs b/sim/src/sim.rs index 6fd69d1a72..fe8a27b656 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -3,9 +3,9 @@ use crate::{ AgentID, AgentType, AlertLocation, Analytics, CarID, Command, CreateCar, DrawCarInput, DrawPedCrowdInput, DrawPedestrianInput, DrivingSimState, Event, GetDrawAgents, IntersectionSimState, OrigPersonID, PandemicModel, ParkedCar, ParkingSimState, ParkingSpot, - PedestrianID, Person, PersonID, PersonState, Router, Scheduler, SidewalkPOI, SidewalkSpot, - TransitSimState, TripID, TripInfo, TripManager, TripPhaseType, TripResult, TripSpawner, - UnzoomedAgent, Vehicle, VehicleSpec, VehicleType, WalkingSimState, BUS_LENGTH, + PedestrianID, Person, PersonID, PersonState, Router, Scenario, Scheduler, SidewalkPOI, + SidewalkSpot, TransitSimState, TripID, TripInfo, TripManager, TripPhaseType, TripResult, + TripSpawner, UnzoomedAgent, Vehicle, VehicleSpec, VehicleType, WalkingSimState, BUS_LENGTH, LIGHT_RAIL_LENGTH, MIN_CAR_LENGTH, SPAWN_DIST, }; use abstutil::{prettyprint_usize, serialized_size_bytes, Counter, Parallelism, Timer}; @@ -1192,6 +1192,10 @@ impl Sim { ) -> &Vec<(PedestrianID, BusRouteID, Option, Time)> { self.transit.get_people_waiting_at_stop(at) } + + pub fn generate_scenario(&self, map: &Map, name: String) -> Scenario { + self.trips.generate_scenario(map, name) + } } // Invasive debugging diff --git a/sim/src/trips.rs b/sim/src/trips.rs index f9f65df528..0e493f61ec 100644 --- a/sim/src/trips.rs +++ b/sim/src/trips.rs @@ -1,8 +1,9 @@ use crate::{ AgentID, AgentType, AlertLocation, CarID, Command, CreateCar, CreatePedestrian, DrivingGoal, - Event, OffMapLocation, OrigPersonID, ParkedCar, ParkingSimState, ParkingSpot, PedestrianID, - PersonID, Scheduler, SidewalkPOI, SidewalkSpot, TransitSimState, TripID, TripPhaseType, - TripSpec, Vehicle, VehicleSpec, VehicleType, WalkingSimState, + Event, IndividTrip, OffMapLocation, OrigPersonID, ParkedCar, ParkingSimState, ParkingSpot, + PedestrianID, PersonID, PersonSpec, Scenario, Scheduler, SidewalkPOI, SidewalkSpot, SpawnTrip, + TransitSimState, TripID, TripPhaseType, TripSpec, Vehicle, VehicleSpec, VehicleType, + WalkingSimState, }; use abstutil::{deserialize_btreemap, serialize_btreemap, Counter}; use geom::{Duration, Speed, Time}; @@ -1326,6 +1327,35 @@ impl TripManager { times.sort(); times } + + // TODO This could be lossy. There are a few layers in spawning trips, and things like + // spawn_agents_around reach into one of the middle layers directly. So here in TripManager, we + // might not have retained enough state to create a proper scenario. But this should work + // reasonably for most cases. + pub fn generate_scenario(&self, map: &Map, name: String) -> Scenario { + let mut scenario = Scenario::empty(map, &name); + for p in &self.people { + scenario.people.push(PersonSpec { + id: p.id, + orig_id: p.orig_id, + trips: p + .trips + .iter() + .filter_map(|t| { + let trip = &self.trips[t.0]; + SpawnTrip::new( + trip.info.start.clone(), + trip.info.end.clone(), + trip.info.mode, + map, + ) + .map(|spawn| IndividTrip::new(trip.info.departure, spawn)) + }) + .collect(), + }); + } + scenario + } } #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]