From 334081b844012ae3af96d45fdc4bda29602e5809 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Thu, 27 Jun 2019 10:42:33 -0700 Subject: [PATCH] very basic tool to explore an agent's full trip --- editor/src/abtest/mod.rs | 7 ++- editor/src/common/mod.rs | 2 + editor/src/common/trip_explorer.rs | 92 ++++++++++++++++++++++++++++++ editor/src/sandbox/mod.rs | 9 ++- sim/src/lib.rs | 2 +- sim/src/make/load.rs | 6 +- sim/src/make/spawner.rs | 39 +++++++++++-- sim/src/sim.rs | 14 +++-- sim/src/trips.rs | 46 ++++++++++++++- 9 files changed, 197 insertions(+), 20 deletions(-) create mode 100644 editor/src/common/trip_explorer.rs diff --git a/editor/src/abtest/mod.rs b/editor/src/abtest/mod.rs index 292b9855d3..0804190f29 100644 --- a/editor/src/abtest/mod.rs +++ b/editor/src/abtest/mod.rs @@ -1,7 +1,9 @@ mod score; pub mod setup; -use crate::common::{time_controls, AgentTools, CommonState, RouteExplorer, SpeedControls}; +use crate::common::{ + time_controls, AgentTools, CommonState, RouteExplorer, SpeedControls, TripExplorer, +}; use crate::game::{State, Transition}; use crate::render::MIN_ZOOM_FOR_DETAIL; use crate::ui::{PerMapUI, UI}; @@ -119,6 +121,9 @@ impl State for ABTestMode { if let Some(explorer) = RouteExplorer::new(ctx, ui) { return Transition::Push(Box::new(explorer)); } + if let Some(explorer) = TripExplorer::new(ctx, ui) { + return Transition::Push(Box::new(explorer)); + } self.primary_agent_tools.event(ctx, ui, &mut self.menu); diff --git a/editor/src/common/mod.rs b/editor/src/common/mod.rs index eb63e1b058..ae46895268 100644 --- a/editor/src/common/mod.rs +++ b/editor/src/common/mod.rs @@ -5,6 +5,7 @@ mod route_explorer; mod route_viewer; mod speed; mod time; +mod trip_explorer; mod turn_cycler; mod warp; @@ -12,6 +13,7 @@ pub use self::agent::AgentTools; pub use self::route_explorer::RouteExplorer; pub use self::speed::SpeedControls; pub use self::time::time_controls; +pub use self::trip_explorer::TripExplorer; use crate::game::Transition; use crate::helpers::ID; use crate::render::DrawOptions; diff --git a/editor/src/common/trip_explorer.rs b/editor/src/common/trip_explorer.rs new file mode 100644 index 0000000000..cd04be678c --- /dev/null +++ b/editor/src/common/trip_explorer.rs @@ -0,0 +1,92 @@ +use crate::common::CommonState; +use crate::game::{State, Transition}; +use crate::helpers::ID; +use crate::ui::UI; +use ezgui::{EventCtx, GfxCtx, Key, Text, WarpingItemSlider}; +use geom::Pt2D; +use sim::{TripEnd, TripStart}; + +// TODO More info, like each leg of the trip, times, separate driving leg for looking for +// parking... +pub struct TripExplorer { + slider: WarpingItemSlider, +} + +impl TripExplorer { + pub fn new(ctx: &mut EventCtx, ui: &UI) -> Option { + let map = &ui.primary.map; + let agent = ui.primary.current_selection.and_then(|id| id.agent_id())?; + let trip = ui.primary.sim.agent_to_trip(agent)?; + let status = ui.primary.sim.trip_status(trip)?; + if !ctx.input.contextual_action(Key::T, "explore trip") { + return None; + } + + let steps: Vec<(Pt2D, ID, Text)> = vec![ + match status.start { + TripStart::Bldg(b) => ( + map.get_b(b).front_path.line.pt1(), + ID::Building(b), + Text::from_line(format!("start at {}", map.get_b(b).get_name())), + ), + TripStart::Appearing(pos) => ( + pos.pt(map), + ID::Lane(pos.lane()), + Text::from_line(format!( + "start by appearing at {}", + map.get_parent(pos.lane()).get_name() + )), + ), + }, + ( + ui.primary.sim.get_canonical_pt_per_trip(trip, map).unwrap(), + ID::from_agent(agent), + Text::from_line("currently here".to_string()), + ), + match status.end { + TripEnd::Bldg(b) => ( + map.get_b(b).front_path.line.pt1(), + ID::Building(b), + Text::from_line(format!("end at {}", map.get_b(b).get_name())), + ), + TripEnd::Border(i) => ( + map.get_i(i).polygon.center(), + ID::Intersection(i), + Text::from_line(format!("leave map via {}", i)), + ), + }, + ]; + + Some(TripExplorer { + slider: WarpingItemSlider::new( + steps, + &format!("Trip Explorer for {}", trip), + "step", + ctx, + ), + }) + } +} + +impl State for TripExplorer { + fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Transition { + if ctx.redo_mouseover() { + ui.recalculate_current_selection(ctx); + } + ctx.canvas.handle_event(ctx.input); + + if let Some((evmode, done_warping)) = self.slider.event(ctx) { + if done_warping { + ui.primary.current_selection = Some(*self.slider.get().1); + } + Transition::KeepWithMode(evmode) + } else { + Transition::Pop + } + } + + fn draw(&self, g: &mut GfxCtx, ui: &UI) { + self.slider.draw(g); + CommonState::draw_osd(g, ui, ui.primary.current_selection); + } +} diff --git a/editor/src/sandbox/mod.rs b/editor/src/sandbox/mod.rs index eb9c9ee768..d0a700537a 100644 --- a/editor/src/sandbox/mod.rs +++ b/editor/src/sandbox/mod.rs @@ -3,7 +3,9 @@ mod show_activity; mod spawner; mod time_travel; -use crate::common::{time_controls, AgentTools, CommonState, RouteExplorer, SpeedControls}; +use crate::common::{ + time_controls, AgentTools, CommonState, RouteExplorer, SpeedControls, TripExplorer, +}; use crate::debug::DebugMode; use crate::edit::EditMode; use crate::game::{State, Transition}; @@ -99,10 +101,13 @@ impl State for SandboxMode { if let Some(explorer) = RouteExplorer::new(ctx, ui) { return Transition::Push(Box::new(explorer)); } + if let Some(explorer) = TripExplorer::new(ctx, ui) { + return Transition::Push(Box::new(explorer)); + } self.agent_tools.event(ctx, ui, &mut self.menu); self.show_activity.event(ctx, ui, &mut self.menu); - if self.menu.action("start time traveling") { + if ui.primary.current_selection.is_none() && self.menu.action("start time traveling") { return self.time_travel.start(ctx, ui); } if self.menu.action("scoreboard") { diff --git a/sim/src/lib.rs b/sim/src/lib.rs index d7d165e971..80ff720ad5 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -20,7 +20,7 @@ pub(crate) use self::router::{ActionAtEnd, Router}; pub(crate) use self::scheduler::{Command, Scheduler}; pub use self::sim::Sim; pub(crate) use self::transit::TransitSimState; -pub use self::trips::{FinishedTrips, TripMode}; +pub use self::trips::{FinishedTrips, TripEnd, TripMode, TripStart, TripStatus}; pub(crate) use self::trips::{TripLeg, TripManager}; pub use crate::render::{CarStatus, DrawCarInput, DrawPedestrianInput, GetDrawAgents}; use abstutil::Cloneable; diff --git a/sim/src/make/load.rs b/sim/src/make/load.rs index b25136652d..ace913bf89 100644 --- a/sim/src/make/load.rs +++ b/sim/src/make/load.rs @@ -60,10 +60,8 @@ impl SimFlags { if self.load.starts_with(Path::new("../data/save/")) { timer.note(format!("Resuming from {}", self.load.display())); - timer.start("read sim savestate"); - let sim: Sim = - abstutil::read_json(self.load.to_str().unwrap()).expect("loading sim state failed"); - timer.stop("read sim savestate"); + let sim: Sim = abstutil::read_binary(self.load.to_str().unwrap(), timer) + .expect("loading sim state failed"); let mut map: Map = abstutil::read_binary(&format!("../data/maps/{}.bin", sim.map_name), timer) diff --git a/sim/src/make/spawner.rs b/sim/src/make/spawner.rs index ba5bed0dc9..aa65edfb05 100644 --- a/sim/src/make/spawner.rs +++ b/sim/src/make/spawner.rs @@ -1,7 +1,7 @@ use crate::{ CarID, Command, CreateCar, CreatePedestrian, DrivingGoal, ParkingSimState, ParkingSpot, - PedestrianID, Scheduler, SidewalkPOI, SidewalkSpot, TripLeg, TripManager, VehicleSpec, - VehicleType, MAX_CAR_LENGTH, + PedestrianID, Scheduler, SidewalkPOI, SidewalkSpot, TripLeg, TripManager, TripStart, + VehicleSpec, VehicleType, MAX_CAR_LENGTH, }; use abstutil::Timer; use geom::{Duration, Speed, EPSILON_DIST}; @@ -187,7 +187,8 @@ impl TripSpawner { SidewalkSpot::building(b, map), )); } - let trip = trips.new_trip(start_time, legs); + let trip = + trips.new_trip(start_time, Some(TripStart::Appearing(start_pos)), legs); let router = goal.make_router(path, map, vehicle.vehicle_type); scheduler.quick_push( start_time, @@ -225,7 +226,11 @@ impl TripSpawner { } DrivingGoal::Border(_, _) => {} } - let trip = trips.new_trip(start_time, legs); + let trip = trips.new_trip( + start_time, + Some(TripStart::Bldg(vehicle.owner.unwrap())), + legs, + ); scheduler.quick_push( start_time, @@ -246,6 +251,13 @@ impl TripSpawner { } => { let trip = trips.new_trip( start_time, + match start.connection { + SidewalkPOI::Building(b) => Some(TripStart::Bldg(b)), + SidewalkPOI::SuddenlyAppear | SidewalkPOI::Border(_) => { + Some(TripStart::Appearing(start.sidewalk_pos)) + } + _ => unreachable!(), + }, vec![TripLeg::Walk(ped_id.unwrap(), ped_speed, goal.clone())], ); @@ -282,7 +294,17 @@ impl TripSpawner { } DrivingGoal::Border(_, _) => {} }; - let trip = trips.new_trip(start_time, legs); + let trip = trips.new_trip( + start_time, + match start.connection { + SidewalkPOI::Building(b) => Some(TripStart::Bldg(b)), + SidewalkPOI::SuddenlyAppear | SidewalkPOI::Border(_) => { + Some(TripStart::Appearing(start.sidewalk_pos)) + } + _ => unreachable!(), + }, + legs, + ); scheduler.quick_push( start_time, @@ -307,6 +329,13 @@ impl TripSpawner { let walk_to = SidewalkSpot::bus_stop(stop1, map); let trip = trips.new_trip( start_time, + match start.connection { + SidewalkPOI::Building(b) => Some(TripStart::Bldg(b)), + SidewalkPOI::SuddenlyAppear | SidewalkPOI::Border(_) => { + Some(TripStart::Appearing(start.sidewalk_pos)) + } + _ => unreachable!(), + }, vec![ TripLeg::Walk(ped_id.unwrap(), ped_speed, walk_to.clone()), TripLeg::RideBus(ped_id.unwrap(), route, stop2), diff --git a/sim/src/sim.rs b/sim/src/sim.rs index f018292cde..edf8c0d1c1 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -2,8 +2,8 @@ use crate::{ AgentID, CarID, Command, CreateCar, DrawCarInput, DrawPedestrianInput, DrivingGoal, DrivingSimState, Event, FinishedTrips, GetDrawAgents, IntersectionSimState, ParkedCar, ParkingSimState, ParkingSpot, PedestrianID, Router, Scheduler, TransitSimState, TripID, - TripLeg, TripManager, TripPositions, TripSpawner, TripSpec, VehicleSpec, VehicleType, - WalkingSimState, BUS_LENGTH, + TripLeg, TripManager, TripPositions, TripSpawner, TripSpec, TripStatus, VehicleSpec, + VehicleType, WalkingSimState, BUS_LENGTH, }; use abstutil::{elapsed_seconds, Timer}; use derivative::Derivative; @@ -198,9 +198,9 @@ impl Sim { // Bypass some layers of abstraction that don't make sense for buses. // TODO Aww, we create an orphan trip if the bus can't spawn. - let trip = self - .trips - .new_trip(self.time, vec![TripLeg::ServeBusRoute(id, route.id)]); + let trip = + self.trips + .new_trip(self.time, None, vec![TripLeg::ServeBusRoute(id, route.id)]); if self.driving.start_car_on_lane( self.time, CreateCar { @@ -698,6 +698,10 @@ impl Sim { self.trips.trip_to_agent(id) } + pub fn trip_status(&self, id: TripID) -> Option { + self.trips.trip_status(id) + } + pub fn lookup_car_id(&self, idx: usize) -> Option { for vt in &[VehicleType::Car, VehicleType::Bike, VehicleType::Bus] { let id = CarID(idx, *vt); diff --git a/sim/src/trips.rs b/sim/src/trips.rs index 8743a96b1b..8f3b89b909 100644 --- a/sim/src/trips.rs +++ b/sim/src/trips.rs @@ -5,7 +5,7 @@ use crate::{ }; use abstutil::{deserialize_btreemap, serialize_btreemap}; use geom::{Duration, Speed}; -use map_model::{BuildingID, BusRouteID, BusStopID, IntersectionID, Map, PathRequest}; +use map_model::{BuildingID, BusRouteID, BusStopID, IntersectionID, Map, PathRequest, Position}; use serde_derive::{Deserialize, Serialize}; use std::collections::{BTreeMap, VecDeque}; @@ -35,7 +35,12 @@ impl TripManager { } } - pub fn new_trip(&mut self, spawned_at: Duration, legs: Vec) -> TripID { + pub fn new_trip( + &mut self, + spawned_at: Duration, + start: Option, + legs: Vec, + ) -> TripID { assert!(!legs.is_empty()); // TODO Make sure the legs constitute a valid state machine. @@ -63,6 +68,7 @@ impl TripManager { finished_at: None, mode, legs: VecDeque::from(legs), + start, }; if !trip.is_bus_trip() { self.unfinished_trips += 1; @@ -419,6 +425,23 @@ impl TripManager { pub fn collect_events(&mut self) -> Vec { self.events.drain(..).collect() } + + pub fn trip_status(&self, id: TripID) -> Option { + let trip = &self.trips[id.0]; + let start = trip.start.clone()?; + let end = match trip.legs.back() { + Some(TripLeg::Walk(_, _, ref spot)) => match spot.connection { + SidewalkPOI::Building(b) => TripEnd::Bldg(b), + _ => unreachable!(), + }, + Some(TripLeg::Drive(_, ref goal)) => match goal { + DrivingGoal::ParkNear(b) => TripEnd::Bldg(*b), + DrivingGoal::Border(i, _) => TripEnd::Border(*i), + }, + _ => unreachable!(), + }; + Some(TripStatus { start, end }) + } } #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -428,6 +451,7 @@ struct Trip { finished_at: Option, legs: VecDeque, mode: TripMode, + start: Option, } impl Trip { @@ -516,3 +540,21 @@ pub enum TripMode { Transit, Drive, } + +// TODO Argh no, not more of these variants! + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub enum TripStart { + Bldg(BuildingID), + Appearing(Position), +} + +pub enum TripEnd { + Bldg(BuildingID), + Border(IntersectionID), +} + +pub struct TripStatus { + pub start: TripStart, + pub end: TripEnd, +}