diff --git a/sim/src/lib.rs b/sim/src/lib.rs index d15eafdb5b..4229a5cb0b 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -6,6 +6,7 @@ mod render; mod router; mod scheduler; mod sim; +mod transit; mod trips; pub use self::events::Event; @@ -20,6 +21,7 @@ pub use self::query::{Benchmark, ScoreSummary, SimStats, Summary}; 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(crate) use self::trips::{TripLeg, TripManager}; pub use crate::render::{CarStatus, DrawCarInput, DrawPedestrianInput, GetDrawAgents}; use abstutil::Cloneable; diff --git a/sim/src/router.rs b/sim/src/router.rs index 3d100de6e9..535de058a1 100644 --- a/sim/src/router.rs +++ b/sim/src/router.rs @@ -32,6 +32,9 @@ enum Goal { BikeThenStop { end_dist: Distance, }, + FollowBusRoute { + end_dist: Distance, + }, } impl Router { @@ -59,6 +62,13 @@ impl Router { } } + pub fn follow_bus_route(path: Path, end_dist: Distance) -> Router { + Router { + path, + goal: Goal::FollowBusRoute { end_dist }, + } + } + pub fn head(&self) -> Traversable { self.path.current_step().as_traversable() } @@ -78,6 +88,7 @@ impl Router { Goal::EndAtBorder { end_dist, .. } => end_dist, Goal::ParkNearBuilding { spot, .. } => spot.unwrap().1, Goal::BikeThenStop { end_dist } => end_dist, + Goal::FollowBusRoute { end_dist } => end_dist, } } @@ -157,6 +168,14 @@ impl Router { None } } + Goal::FollowBusRoute { end_dist } => { + if end_dist == front { + // TODO mark when they reach the stop, start waiting, replan, etc + None + } else { + None + } + } } } diff --git a/sim/src/sim.rs b/sim/src/sim.rs index e29f896ae1..d206f9dbce 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -1,8 +1,9 @@ use crate::{ - AgentID, Benchmark, CarID, DrawCarInput, DrawPedestrianInput, DrivingGoal, DrivingSimState, - Event, GetDrawAgents, IntersectionSimState, ParkedCar, ParkingSimState, ParkingSpot, - PedestrianID, Scheduler, ScoreSummary, SimStats, Summary, TripID, TripManager, TripSpawner, - TripSpec, VehicleSpec, VehicleType, WalkingSimState, TIMESTEP, + AgentID, Benchmark, CarID, CreateCar, DrawCarInput, DrawPedestrianInput, DrivingGoal, + DrivingSimState, Event, GetDrawAgents, IntersectionSimState, ParkedCar, ParkingSimState, + ParkingSpot, PedestrianID, Router, Scheduler, ScoreSummary, SimStats, Summary, TransitSimState, + TripID, TripLeg, TripManager, TripSpawner, TripSpec, VehicleSpec, VehicleType, WalkingSimState, + BUS_LENGTH, TIMESTEP, }; use abstutil::Timer; use derivative::Derivative; @@ -23,6 +24,7 @@ pub struct Sim { parking: ParkingSimState, walking: WalkingSimState, intersections: IntersectionSimState, + transit: TransitSimState, trips: TripManager, scheduler: Scheduler, spawner: TripSpawner, @@ -53,6 +55,7 @@ impl Sim { parking: ParkingSimState::new(map), walking: WalkingSimState::new(), intersections: IntersectionSimState::new(map), + transit: TransitSimState::new(), trips: TripManager::new(), scheduler: Scheduler::new(), spawner: TripSpawner::new(), @@ -145,8 +148,66 @@ impl Sim { } pub fn seed_bus_route(&mut self, route: &BusRoute, map: &Map, timer: &mut Timer) -> Vec { - // TODO implement - Vec::new() + let mut results: Vec = Vec::new(); + + // Try to spawn a bus at each stop + for (next_stop_idx, start_dist, path, end_dist) in + self.transit.create_empty_route(route, map).into_iter() + { + // For now, no desire for randomness. Caller can pass in list of specs if that ever + // changes. + let vehicle_spec = VehicleSpec { + vehicle_type: VehicleType::Bus, + length: BUS_LENGTH, + max_speed: None, + }; + + // TODO Do this validation more up-front in the map layer + if start_dist < vehicle_spec.length { + timer.warn(format!( + "Stop at {:?} is too short to spawn a bus there; giving up on one bus for {}", + path.current_step(), + route.id + )); + continue; + } + + let id = CarID(self.car_id_counter, VehicleType::Bus); + self.car_id_counter += 1; + + // 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, None, vec![TripLeg::ServeBusRoute(id, route.id)]); + if self.driving.start_car_on_lane( + self.time, + CreateCar { + vehicle: vehicle_spec.make(id, None), + router: Router::follow_bus_route(path, end_dist), + start_dist, + maybe_parked_car: None, + trip, + }, + map, + &self.intersections, + ) { + self.trips.agent_starting_trip_leg(AgentID::Car(id), trip); + self.transit.bus_created(id, route.id, next_stop_idx); + timer.note(format!( + "Spawned bus {} for route {} ({})", + id, route.name, route.id + )); + results.push(id); + } else { + timer.warn(format!( + "No room for a bus headed towards stop {} of {} ({}), giving up", + next_stop_idx, route.name, route.id + )); + } + } + results } } diff --git a/sim/src/transit.rs b/sim/src/transit.rs new file mode 100644 index 0000000000..0dc4e44073 --- /dev/null +++ b/sim/src/transit.rs @@ -0,0 +1,248 @@ +use crate::{CarID, Event, PedestrianID}; +use abstutil::{deserialize_btreemap, serialize_btreemap}; +use geom::{Distance, Duration}; +use map_model::{BusRoute, BusRouteID, BusStop, Map, Path, PathRequest, Pathfinder}; +use serde_derive::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +// These index stops along a route, not stops along a single sidewalk. +type StopIdx = usize; + +#[derive(Serialize, Deserialize, PartialEq)] +struct Route { + // Just copy the info over here from map_model for convenience + id: BusRouteID, + name: String, + stops: Vec, + + buses: Vec, + // TODO info on schedules +} + +impl Route { + fn next_stop(&self, idx: StopIdx) -> StopIdx { + if idx + 1 == self.stops.len() { + 0 + } else { + idx + 1 + } + } +} + +#[derive(Serialize, Deserialize, PartialEq)] +struct Bus { + car: CarID, + route: BusRouteID, + passengers: Vec, + state: BusState, +} + +#[derive(Serialize, Deserialize, PartialEq)] +enum BusState { + DrivingToStop(StopIdx), + // When do we leave? + AtStop(StopIdx, Duration), +} + +// This kind of acts like TripManager, managing transitions... but a bit more statefully. +#[derive(Serialize, Deserialize, PartialEq)] +pub struct TransitSimState { + #[serde( + serialize_with = "serialize_btreemap", + deserialize_with = "deserialize_btreemap" + )] + buses: BTreeMap, + #[serde( + serialize_with = "serialize_btreemap", + deserialize_with = "deserialize_btreemap" + )] + routes: BTreeMap, +} + +impl TransitSimState { + pub fn new() -> TransitSimState { + TransitSimState { + buses: BTreeMap::new(), + routes: BTreeMap::new(), + } + } + + // Returns (next stop, start distance on the driving lane, first path, end distance for next + // stop) for all of the stops in the route. + pub fn create_empty_route( + &mut self, + route: &BusRoute, + map: &Map, + ) -> Vec<(StopIdx, Distance, Path, Distance)> { + assert!(route.stops.len() > 1); + let route = Route { + id: route.id, + name: route.name.clone(), + stops: route.stops.iter().map(|s| map.get_bs(*s).clone()).collect(), + buses: Vec::new(), + }; + + let stops = route + .stops + .iter() + .enumerate() + .map(|(idx, stop1)| { + let next_stop = route.next_stop(idx); + let stop2 = &route.stops[next_stop]; + let path = Pathfinder::shortest_distance( + map, + PathRequest { + start: stop1.driving_pos, + end: stop2.driving_pos, + can_use_bike_lanes: false, + can_use_bus_lanes: true, + }, + ) + .expect(&format!( + "No route between bus stops {:?} and {:?}", + stop1, stop2 + )); + ( + next_stop, + stop1.driving_pos.dist_along(), + path, + stop2.driving_pos.dist_along(), + ) + }) + .collect(); + + self.routes.insert(route.id, route); + stops + } + + pub fn bus_created(&mut self, bus: CarID, route: BusRouteID, next_stop_idx: StopIdx) { + self.routes.get_mut(&route).unwrap().buses.push(bus); + self.buses.insert( + bus, + Bus { + car: bus, + route, + passengers: Vec::new(), + state: BusState::DrivingToStop(next_stop_idx), + }, + ); + } + + /* + // Returns (should idle, new path) + pub fn get_action_when_stopped_at_end( + &mut self, + events: &mut Vec, + view: &AgentView, + time: Tick, + map: &Map, + ) -> (bool, Option) { + let car = view.id.as_car(); + let route = &self.routes[&self.buses[&car].route]; + match self.buses[&car].state { + BusState::DrivingToStop(stop_idx) => { + let stop = &route.stops[stop_idx]; + assert_eq!(stop.driving_pos.lane(), view.on.as_lane()); + if stop.driving_pos.dist_along() == view.dist_along { + if !view.speed.is_zero(TIMESTEP) { + panic!( + "{} arrived at stop {}, but speed is {}", + car, stop.id, view.speed + ); + } + // TODO constant for stop time + self.buses.get_mut(&car).unwrap().state = + BusState::AtStop(stop_idx, time + Duration::seconds(10.0)); + events.push(Event::BusArrivedAtStop(car, stop.id)); + return (true, None); + } + // No, keep creeping forwards + (false, None) + } + BusState::AtStop(stop_idx, wait_until) => { + let stop = &route.stops[stop_idx]; + assert_eq!(stop.driving_pos.lane(), view.on.as_lane()); + if stop.driving_pos.dist_along() != view.dist_along { + panic!( + "{} stopped at {}, but dist_along is {}, not {}. Speed is {}", + car, + stop.id, + view.dist_along, + stop.driving_pos.dist_along(), + view.speed + ); + } + assert_eq!(stop.driving_pos.dist_along(), view.dist_along); + + if time == wait_until { + let next_stop = route.next_stop(stop_idx); + self.buses.get_mut(&car).unwrap().state = BusState::DrivingToStop(next_stop); + events.push(Event::BusDepartedFromStop(car, stop.id)); + + let new_path = Pathfinder::shortest_distance( + map, + PathRequest { + start: stop.driving_pos, + end: route.stops[next_stop].driving_pos, + can_use_bike_lanes: false, + can_use_bus_lanes: true, + }, + ) + .expect(&format!( + "No route between bus stops {:?} and {:?}", + stop, route.stops[next_stop] + )); + + return (true, Some(new_path)); + } + + (true, None) + } + } + } + + pub fn step( + &mut self, + now: Tick, + events: &mut Vec, + walking_sim: &mut WalkingSimState, + trips: &mut TripManager, + spawner: &mut Spawner, + map: &Map, + ) { + for b in self.buses.values_mut() { + if let BusState::AtStop(stop_idx, _) = b.state { + let stop = &self.routes[&b.route].stops[stop_idx]; + + // Let anybody new on? + for p in walking_sim.get_peds_waiting_at_stop(stop.id).into_iter() { + if trips.should_ped_board_bus(p, b.route) { + events.push(Event::PedEntersBus(p, b.car)); + b.passengers.push(p); + walking_sim.ped_joined_bus(p, stop.id); + } + } + + // Let anybody off? + // TODO ideally dont even ask if they just got on, but the trip planner things + // should be fine with this + // TODO only do this if we JUST arrived at the stop, and in fact, wait for everyone + // to leave, since it may take time. + // so actually, we shouldnt statechange mutably in get_action_when_stopped_at_end, + // which is called by router! thats convoluted + let car = b.car; + b.passengers.retain(|p| { + if trips.should_ped_leave_bus(*p, stop.id) { + events.push(Event::PedLeavesBus(*p, car)); + // TODO would be a little cleaner to return this info up to sim and have it + // plumb through to spawner? not sure + spawner.ped_finished_bus_ride(now, *p, stop.id, trips, map); + false + } else { + true + } + }); + } + } + }*/ +}