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.
This commit is contained in:
Dustin Carlino 2020-08-14 14:09:53 -07:00
parent 3ed8b9cf81
commit e6c1d960ec
3 changed files with 72 additions and 10 deletions

View File

@ -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),

View File

@ -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<BusStopID>, 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

View File

@ -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)]