sim/make/
spawner.rs

1//! Intermediate structures used to instantiate a Scenario. Badly needs simplification:
2//! https://github.com/a-b-street/abstreet/issues/258
3
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6
7use map_model::{BuildingID, Map, PathConstraints, Position, TransitRouteID, TransitStopID};
8use synthpop::{TripEndpoint, TripMode};
9
10use crate::{CarID, DrivingGoal, SidewalkSpot, TripLeg, VehicleType, SPAWN_DIST};
11
12/// We need to remember a few things from scenario instantiation that're used for starting the
13/// trip.
14#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
15pub(crate) struct StartTripArgs {
16    pub retry_if_no_room: bool,
17    pub use_vehicle: Option<CarID>,
18}
19
20// TODO Some of these fields are unused now that we separately pass TripEndpoint
21#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
22pub(crate) enum TripSpec {
23    /// Can be used to spawn from a border or anywhere for interactive debugging.
24    VehicleAppearing {
25        start_pos: Position,
26        goal: DrivingGoal,
27        /// This must be a currently off-map vehicle owned by the person.
28        use_vehicle: CarID,
29        retry_if_no_room: bool,
30    },
31    /// Something went wrong spawning the trip.
32    SpawningFailure {
33        use_vehicle: Option<CarID>,
34        error: String,
35    },
36    UsingParkedCar {
37        /// This must be a currently parked vehicle owned by the person.
38        car: CarID,
39        start_bldg: BuildingID,
40        goal: DrivingGoal,
41    },
42    JustWalking {
43        start: SidewalkSpot,
44        goal: SidewalkSpot,
45    },
46    UsingBike {
47        bike: CarID,
48        start: BuildingID,
49        goal: DrivingGoal,
50    },
51    UsingTransit {
52        start: SidewalkSpot,
53        goal: SidewalkSpot,
54        route: TransitRouteID,
55        stop1: TransitStopID,
56        maybe_stop2: Option<TransitStopID>,
57    },
58}
59
60impl TripSpec {
61    pub fn into_plan(self, map: &Map) -> (TripSpec, Vec<TripLeg>) {
62        // TODO We'll want to repeat this validation when we spawn stuff later for a second leg...
63        let mut legs = Vec::new();
64        match &self {
65            TripSpec::VehicleAppearing {
66                start_pos,
67                goal,
68                use_vehicle,
69                ..
70            } => {
71                if start_pos.dist_along() >= map.get_l(start_pos.lane()).length() {
72                    panic!("Can't spawn at {}; it isn't that long", start_pos);
73                }
74                if let DrivingGoal::Border(_, end_lane) = goal {
75                    if start_pos.lane() == *end_lane
76                        && start_pos.dist_along() == map.get_l(*end_lane).length()
77                    {
78                        panic!(
79                            "Can't start at {}; it's the edge of a border already",
80                            start_pos
81                        );
82                    }
83                }
84
85                let constraints = if use_vehicle.vehicle_type == VehicleType::Bike {
86                    PathConstraints::Bike
87                } else {
88                    PathConstraints::Car
89                };
90
91                legs.push(TripLeg::Drive(*use_vehicle, goal.clone()));
92                if let DrivingGoal::ParkNear(b) = goal {
93                    legs.push(TripLeg::Walk(SidewalkSpot::building(*b, map)));
94                }
95
96                if goal.goal_pos(constraints, map).is_none() {
97                    return TripSpec::SpawningFailure {
98                        use_vehicle: Some(*use_vehicle),
99                        error: format!("goal_pos to {:?} for a {:?} failed", goal, constraints),
100                    }
101                    .into_plan(map);
102                }
103            }
104            TripSpec::SpawningFailure { .. } => {
105                // TODO The legs are a lie. Since the trip gets cancelled, this doesn't matter.
106                // I'm not going to bother doing better because I think TripLeg will get
107                // revamped soon anyway.
108                legs.push(TripLeg::RideBus(TransitRouteID(0), None));
109            }
110            TripSpec::UsingParkedCar { car, goal, .. } => {
111                legs.push(TripLeg::Walk(SidewalkSpot::deferred_parking_spot()));
112                legs.push(TripLeg::Drive(*car, goal.clone()));
113                match goal {
114                    DrivingGoal::ParkNear(b) => {
115                        legs.push(TripLeg::Walk(SidewalkSpot::building(*b, map)));
116                    }
117                    DrivingGoal::Border(_, _) => {}
118                }
119            }
120            TripSpec::JustWalking { start, goal, .. } => {
121                if start == goal {
122                    panic!(
123                        "A trip just walking from {:?} to {:?} doesn't make sense",
124                        start, goal
125                    );
126                }
127                legs.push(TripLeg::Walk(goal.clone()));
128            }
129            TripSpec::UsingBike { start, goal, bike } => {
130                // TODO Might not be possible to walk to the same border if there's no sidewalk
131                let backup_plan = match goal {
132                    DrivingGoal::ParkNear(b) => Some(TripSpec::JustWalking {
133                        start: SidewalkSpot::building(*start, map),
134                        goal: SidewalkSpot::building(*b, map),
135                    }),
136                    DrivingGoal::Border(i, _) => {
137                        SidewalkSpot::end_at_border(*i, map).map(|goal| TripSpec::JustWalking {
138                            start: SidewalkSpot::building(*start, map),
139                            goal,
140                        })
141                    }
142                };
143
144                if let Some(start_spot) = SidewalkSpot::bike_rack(*start, map) {
145                    if let DrivingGoal::ParkNear(b) = goal {
146                        if let Some(goal_spot) = SidewalkSpot::bike_rack(*b, map) {
147                            if start_spot.sidewalk_pos.lane() == goal_spot.sidewalk_pos.lane() {
148                                info!(
149                                    "Bike trip from {} to {} will just walk; it's the same \
150                                     sidewalk!",
151                                    start, b
152                                );
153                                return backup_plan.unwrap().into_plan(map);
154                            }
155                        } else {
156                            info!(
157                                "Can't find biking connection for goal {}, walking instead",
158                                b
159                            );
160                            return backup_plan.unwrap().into_plan(map);
161                        }
162                    }
163
164                    legs.push(TripLeg::Walk(start_spot));
165                    legs.push(TripLeg::Drive(*bike, goal.clone()));
166                    match goal {
167                        DrivingGoal::ParkNear(b) => {
168                            legs.push(TripLeg::Walk(SidewalkSpot::building(*b, map)));
169                        }
170                        DrivingGoal::Border(_, _) => {}
171                    }
172                } else if let Some(plan) = backup_plan {
173                    info!("Can't start biking from {}. Walking instead", start);
174                    return plan.into_plan(map);
175                } else {
176                    return TripSpec::SpawningFailure {
177                        use_vehicle: Some(*bike),
178                        error: format!(
179                            "Can't start biking from {} and can't walk either! Goal is {:?}",
180                            start, goal
181                        ),
182                    }
183                    .into_plan(map);
184                }
185            }
186            TripSpec::UsingTransit {
187                route,
188                stop1,
189                maybe_stop2,
190                goal,
191                ..
192            } => {
193                let walk_to = SidewalkSpot::bus_stop(*stop1, map);
194                if let Some(stop2) = maybe_stop2 {
195                    legs = vec![
196                        TripLeg::Walk(walk_to),
197                        TripLeg::RideBus(*route, Some(*stop2)),
198                        TripLeg::Walk(goal.clone()),
199                    ];
200                } else {
201                    legs = vec![TripLeg::Walk(walk_to), TripLeg::RideBus(*route, None)];
202                }
203            }
204        };
205
206        (self, legs)
207    }
208
209    /// Turn an origin/destination pair and mode into a specific plan for instantiating a trip.
210    /// Decisions like how to use public transit happen here.
211    pub fn maybe_new(
212        from: TripEndpoint,
213        to: TripEndpoint,
214        mode: TripMode,
215        use_vehicle: Option<CarID>,
216        retry_if_no_room: bool,
217        map: &Map,
218    ) -> Result<TripSpec> {
219        Ok(match mode {
220            TripMode::Drive | TripMode::Bike => {
221                let constraints = if mode == TripMode::Drive {
222                    PathConstraints::Car
223                } else {
224                    PathConstraints::Bike
225                };
226                let goal = driving_goal(to, constraints, map)?;
227                match from {
228                    TripEndpoint::Building(start_bldg) => {
229                        if mode == TripMode::Drive {
230                            TripSpec::UsingParkedCar {
231                                start_bldg,
232                                goal,
233                                car: use_vehicle.unwrap(),
234                            }
235                        } else {
236                            TripSpec::UsingBike {
237                                start: start_bldg,
238                                goal,
239                                bike: use_vehicle.unwrap(),
240                            }
241                        }
242                    }
243                    TripEndpoint::Border(i) => {
244                        let start_lane = map
245                            .get_i(i)
246                            .some_outgoing_road(map)
247                            // TODO Since we're now doing this right when the trip is starting,
248                            // pick the least loaded lane or similar.
249                            .and_then(|dr| dr.lanes(constraints, map).pop())
250                            .ok_or_else(|| {
251                                anyhow!("can't start a {} trip from {}", mode.ongoing_verb(), i)
252                            })?;
253                        TripSpec::VehicleAppearing {
254                            start_pos: Position::new(start_lane, SPAWN_DIST),
255                            goal,
256                            use_vehicle: use_vehicle.unwrap(),
257                            retry_if_no_room,
258                        }
259                    }
260                    TripEndpoint::SuddenlyAppear(start_pos) => TripSpec::VehicleAppearing {
261                        start_pos,
262                        goal,
263                        use_vehicle: use_vehicle.unwrap(),
264                        retry_if_no_room,
265                    },
266                }
267            }
268            TripMode::Walk => TripSpec::JustWalking {
269                start: start_sidewalk_spot(from, map)?,
270                goal: end_sidewalk_spot(to, map)?,
271            },
272            TripMode::Transit => {
273                let start = start_sidewalk_spot(from, map)?;
274                let goal = end_sidewalk_spot(to, map)?;
275                if let Some((stop1, maybe_stop2, route)) =
276                    map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos)
277                {
278                    TripSpec::UsingTransit {
279                        start,
280                        goal,
281                        route,
282                        stop1,
283                        maybe_stop2,
284                    }
285                } else {
286                    //warn!("{:?} not actually using transit, because pathfinding didn't find any
287                    // useful route", trip);
288                    TripSpec::JustWalking { start, goal }
289                }
290            }
291        })
292    }
293}
294
295fn start_sidewalk_spot(endpt: TripEndpoint, map: &Map) -> Result<SidewalkSpot> {
296    match endpt {
297        TripEndpoint::Building(b) => Ok(SidewalkSpot::building(b, map)),
298        TripEndpoint::Border(i) => SidewalkSpot::start_at_border(i, map)
299            .ok_or_else(|| anyhow!("can't start walking from {}", i)),
300        TripEndpoint::SuddenlyAppear(pos) => Ok(SidewalkSpot::suddenly_appear(pos, map)),
301    }
302}
303
304fn end_sidewalk_spot(endpt: TripEndpoint, map: &Map) -> Result<SidewalkSpot> {
305    match endpt {
306        TripEndpoint::Building(b) => Ok(SidewalkSpot::building(b, map)),
307        TripEndpoint::Border(i) => {
308            SidewalkSpot::end_at_border(i, map).ok_or_else(|| anyhow!("can't end walking at {}", i))
309        }
310        TripEndpoint::SuddenlyAppear(_) => unreachable!(),
311    }
312}
313
314fn driving_goal(
315    endpt: TripEndpoint,
316    constraints: PathConstraints,
317    map: &Map,
318) -> Result<DrivingGoal> {
319    match endpt {
320        TripEndpoint::Building(b) => Ok(DrivingGoal::ParkNear(b)),
321        // TODO Duplicates some logic from TripEndpoint::pos
322        TripEndpoint::Border(i) => map
323            .get_i(i)
324            .some_incoming_road(map)
325            .and_then(|dr| {
326                let lanes = dr.lanes(constraints, map);
327                if lanes.is_empty() {
328                    None
329                } else {
330                    // TODO ideally could use any
331                    Some(DrivingGoal::Border(dr.dst_i(map), lanes[0]))
332                }
333            })
334            .ok_or_else(|| anyhow!("can't end at {} for {:?}", i, constraints)),
335        TripEndpoint::SuddenlyAppear(_) => unreachable!(),
336    }
337}