diff --git a/game/src/sandbox/gameplay/spawner.rs b/game/src/sandbox/gameplay/spawner.rs index a32dbef411..1575614be0 100644 --- a/game/src/sandbox/gameplay/spawner.rs +++ b/game/src/sandbox/gameplay/spawner.rs @@ -20,7 +20,8 @@ use rand::seq::SliceRandom; use rand::Rng; use rand_xorshift::XorShiftRng; use sim::{ - BorderSpawnOverTime, DrivingGoal, OriginDestination, Scenario, SidewalkSpot, Sim, TripSpec, + BorderSpawnOverTime, DrivingGoal, OriginDestination, Scenario, SidewalkSpot, Sim, TripSpawner, + TripSpec, }; const SMALL_DT: Duration = Duration::const_seconds(0.1); @@ -270,14 +271,16 @@ impl State for AgentSpawner { if self.maybe_goal.is_some() && app.per_obj.left_click(ctx, "end the agent here") { let mut rng = app.primary.current_flags.sim_flags.make_rng(); let sim = &mut app.primary.sim; + let mut spawner = sim.make_spawner(); let err = schedule_trip( &self.from, self.maybe_goal.take().unwrap().0, map, sim, + &mut spawner, &mut rng, ); - sim.spawn_all_trips(map, &mut Timer::new("spawn trip"), false); + sim.flush_spawner(spawner, map, &mut Timer::new("spawn trip"), false); sim.normal_step(map, SMALL_DT); app.recalculate_current_selection(ctx); if let Some(e) = err { @@ -309,6 +312,7 @@ pub fn spawn_agents_around(i: IntersectionID, app: &mut App) { let map = &app.primary.map; let sim = &mut app.primary.sim; let mut rng = app.primary.current_flags.sim_flags.make_rng(); + let mut spawner = sim.make_spawner(); if map.all_buildings().is_empty() { println!("No buildings, can't pick destinations"); @@ -332,7 +336,7 @@ pub fn spawn_agents_around(i: IntersectionID, app: &mut App) { if vehicle_spec.length > lane.length() { continue; } - sim.schedule_trip( + spawner.schedule_trip( sim.time(), TripSpec::CarAppearing { start_pos: Position::new( @@ -346,11 +350,12 @@ pub fn spawn_agents_around(i: IntersectionID, app: &mut App) { ped_speed: Scenario::rand_ped_speed(&mut rng), }, map, + sim, ); } } else if lane.is_sidewalk() { for _ in 0..5 { - sim.schedule_trip( + spawner.schedule_trip( sim.time(), TripSpec::JustWalking { start: SidewalkSpot::suddenly_appear( @@ -365,12 +370,13 @@ pub fn spawn_agents_around(i: IntersectionID, app: &mut App) { ped_speed: Scenario::rand_ped_speed(&mut rng), }, map, + sim, ); } } } - sim.spawn_all_trips(map, &mut timer, false); + sim.flush_spawner(spawner, map, &mut timer, false); sim.normal_step(map, SMALL_DT); } @@ -379,7 +385,8 @@ fn schedule_trip( src: &Source, raw_goal: Goal, map: &Map, - sim: &mut Sim, + sim: &Sim, + spawner: &mut TripSpawner, rng: &mut XorShiftRng, ) -> Option { match src { @@ -406,7 +413,7 @@ fn schedule_trip( map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos) { println!("Using {} from {} to {}", route, stop1, stop2); - sim.schedule_trip( + spawner.schedule_trip( sim.time(), TripSpec::UsingTransit { start, @@ -417,10 +424,11 @@ fn schedule_trip( ped_speed, }, map, + sim, ); } else { println!("Not using transit"); - sim.schedule_trip( + spawner.schedule_trip( sim.time(), TripSpec::JustWalking { start, @@ -428,6 +436,7 @@ fn schedule_trip( ped_speed, }, map, + sim, ); } } @@ -446,7 +455,7 @@ fn schedule_trip( } } }; - sim.schedule_trip( + spawner.schedule_trip( sim.time(), TripSpec::UsingBike { start: SidewalkSpot::building(*b, map), @@ -455,6 +464,7 @@ fn schedule_trip( ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); } _ => { @@ -476,7 +486,7 @@ fn schedule_trip( match src { Source::Drive(from) => { if let Some(start_pos) = TripSpec::spawn_car_at(*from, map) { - sim.schedule_trip( + spawner.schedule_trip( sim.time(), TripSpec::CarAppearing { start_pos, @@ -485,13 +495,14 @@ fn schedule_trip( ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); } else { return Some(format!("Can't make a car appear at {:?}", from)); } } Source::WalkFromBldgThenMaybeUseCar(b) => { - sim.schedule_trip( + spawner.schedule_trip( sim.time(), TripSpec::MaybeUsingParkedCar { start_bldg: *b, @@ -499,6 +510,7 @@ fn schedule_trip( ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); } _ => unreachable!(), diff --git a/sim/src/make/scenario.rs b/sim/src/make/scenario.rs index 5c635f52e6..b141493473 100644 --- a/sim/src/make/scenario.rs +++ b/sim/src/make/scenario.rs @@ -1,3 +1,4 @@ +use crate::make::TripSpawner; use crate::{ CarID, DrivingGoal, ParkingSpot, PersonID, SidewalkSpot, Sim, TripSpec, VehicleSpec, VehicleType, BIKE_LENGTH, MAX_CAR_LENGTH, MIN_CAR_LENGTH, @@ -104,6 +105,8 @@ impl Scenario { ); } + let mut spawner = sim.make_spawner(); + // Don't let two pedestrians starting from one building use the same car. let mut reserved_cars: HashSet = HashSet::new(); @@ -115,16 +118,24 @@ impl Scenario { timer.start_iter("SpawnOverTime each agent", s.num_agents); for _ in 0..s.num_agents { timer.next(); - s.spawn_agent(rng, sim, &mut reserved_cars, &neighborhoods, map, timer); + s.spawn_agent( + rng, + sim, + &mut spawner, + &mut reserved_cars, + &neighborhoods, + map, + timer, + ); } } timer.start_iter("BorderSpawnOverTime", self.border_spawn_over_time.len()); for s in &self.border_spawn_over_time { timer.next(); - s.spawn_peds(rng, sim, &neighborhoods, map, timer); - s.spawn_cars(rng, sim, &neighborhoods, map, timer); - s.spawn_bikes(rng, sim, &neighborhoods, map, timer); + s.spawn_peds(rng, &mut spawner, &neighborhoods, map, sim, timer); + s.spawn_cars(rng, &mut spawner, &neighborhoods, map, sim, timer); + s.spawn_bikes(rng, &mut spawner, &neighborhoods, map, sim, timer); } let mut individ_parked_cars: Vec<(BuildingID, usize)> = Vec::new(); @@ -140,10 +151,10 @@ impl Scenario { for t in &self.population.individ_trips { timer.next(); let spec = t.trip.clone().to_trip_spec(rng); - sim.schedule_trip(t.depart, spec, map); + spawner.schedule_trip(t.depart, spec, map, sim); } - sim.spawn_all_trips(map, timer, true); + sim.flush_spawner(spawner, map, timer, true); timer.stop(format!("Instantiating {}", self.scenario_name)); } @@ -305,7 +316,8 @@ impl SpawnOverTime { fn spawn_agent( &self, rng: &mut XorShiftRng, - sim: &mut Sim, + sim: &Sim, + spawner: &mut TripSpawner, reserved_cars: &mut HashSet, neighborhoods: &HashMap, map: &Map, @@ -331,7 +343,7 @@ impl SpawnOverTime { { reserved_cars.insert(parked_car.vehicle.id); let spot = parked_car.spot; - sim.schedule_trip( + spawner.schedule_trip( spawn_time, TripSpec::UsingParkedCar { start: SidewalkSpot::building(from_bldg, map), @@ -340,6 +352,7 @@ impl SpawnOverTime { ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); return; } @@ -366,7 +379,7 @@ impl SpawnOverTime { true }; if ok { - sim.schedule_trip( + spawner.schedule_trip( spawn_time, TripSpec::UsingBike { start: SidewalkSpot::building(from_bldg, map), @@ -375,6 +388,7 @@ impl SpawnOverTime { ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); return; } @@ -395,7 +409,7 @@ impl SpawnOverTime { if let Some((stop1, stop2, route)) = map.should_use_transit(start_spot.sidewalk_pos, goal.sidewalk_pos) { - sim.schedule_trip( + spawner.schedule_trip( spawn_time, TripSpec::UsingTransit { start: start_spot, @@ -406,12 +420,13 @@ impl SpawnOverTime { ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); return; } } - sim.schedule_trip( + spawner.schedule_trip( spawn_time, TripSpec::JustWalking { start: start_spot, @@ -419,6 +434,7 @@ impl SpawnOverTime { ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); return; } @@ -431,9 +447,10 @@ impl BorderSpawnOverTime { fn spawn_peds( &self, rng: &mut XorShiftRng, - sim: &mut Sim, + spawner: &mut TripSpawner, neighborhoods: &HashMap, map: &Map, + sim: &Sim, timer: &mut Timer, ) { if self.num_peds == 0 { @@ -461,7 +478,7 @@ impl BorderSpawnOverTime { if let Some((stop1, stop2, route)) = map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos) { - sim.schedule_trip( + spawner.schedule_trip( spawn_time, TripSpec::UsingTransit { start: start.clone(), @@ -472,12 +489,13 @@ impl BorderSpawnOverTime { ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); continue; } } - sim.schedule_trip( + spawner.schedule_trip( spawn_time, TripSpec::JustWalking { start: start.clone(), @@ -485,6 +503,7 @@ impl BorderSpawnOverTime { ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); } } @@ -493,9 +512,10 @@ impl BorderSpawnOverTime { fn spawn_cars( &self, rng: &mut XorShiftRng, - sim: &mut Sim, + spawner: &mut TripSpawner, neighborhoods: &HashMap, map: &Map, + sim: &Sim, timer: &mut Timer, ) { if self.num_cars == 0 { @@ -521,7 +541,7 @@ impl BorderSpawnOverTime { .pick_driving_goal(PathConstraints::Car, map, &neighborhoods, rng, timer) { let vehicle = Scenario::rand_car(rng); - sim.schedule_trip( + spawner.schedule_trip( spawn_time, TripSpec::CarAppearing { start_pos: Position::new(*lanes.choose(rng).unwrap(), vehicle.length), @@ -530,6 +550,7 @@ impl BorderSpawnOverTime { ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); } } @@ -538,9 +559,10 @@ impl BorderSpawnOverTime { fn spawn_bikes( &self, rng: &mut XorShiftRng, - sim: &mut Sim, + spawner: &mut TripSpawner, neighborhoods: &HashMap, map: &Map, + sim: &Sim, timer: &mut Timer, ) { if self.num_bikes == 0 { @@ -566,7 +588,7 @@ impl BorderSpawnOverTime { .pick_driving_goal(PathConstraints::Bike, map, &neighborhoods, rng, timer) { let bike = Scenario::rand_bike(rng); - sim.schedule_trip( + spawner.schedule_trip( spawn_time, TripSpec::CarAppearing { start_pos: Position::new(*lanes.choose(rng).unwrap(), bike.length), @@ -575,6 +597,7 @@ impl BorderSpawnOverTime { ped_speed: Scenario::rand_ped_speed(rng), }, map, + sim, ); } } diff --git a/sim/src/make/spawner.rs b/sim/src/make/spawner.rs index a750f75061..5f6d53c6d4 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, TripStart, - VehicleSpec, MAX_CAR_LENGTH, + PedestrianID, Scheduler, SidewalkPOI, SidewalkSpot, Sim, TripLeg, TripManager, TripStart, + VehicleSpec, VehicleType, MAX_CAR_LENGTH, }; use abstutil::Timer; use geom::{Speed, Time, EPSILON_DIST}; @@ -50,28 +50,84 @@ pub enum TripSpec { }, } -#[derive(Serialize, Deserialize, PartialEq, Clone)] +// This structure is created temporarily by a Scenario or to interactively spawn agents. +// TODO The API isn't great. Passing in Sim and having to use friend methods is awkward. +// Alternatives could be somehow consuming Sim temporarily and spitting it back out at the end +// (except the interactive spawner would have to mem::replace with a blank Sim?), or just queueing +// up commands and doing them at the end while holding onto &Sim. pub struct TripSpawner { parked_cars_claimed: BTreeSet, trips: Vec<(Time, Option, Option, TripSpec)>, + pub car_id_counter: usize, + pub ped_id_counter: usize, } impl TripSpawner { - pub fn new() -> TripSpawner { + pub fn new(car_id_counter: usize, ped_id_counter: usize) -> TripSpawner { TripSpawner { parked_cars_claimed: BTreeSet::new(), trips: Vec::new(), + car_id_counter, + ped_id_counter, } } pub fn schedule_trip( + &mut self, + start_time: Time, + spec: TripSpec, + map: &Map, + sim: &Sim, + ) -> (Option, Option) { + let (ped_id, car_id) = match spec { + TripSpec::CarAppearing { + ref vehicle_spec, + ref goal, + .. + } => { + let car = CarID(self.car_id_counter, vehicle_spec.vehicle_type); + self.car_id_counter += 1; + let ped = match goal { + DrivingGoal::ParkNear(_) => { + let id = PedestrianID(self.ped_id_counter); + self.ped_id_counter += 1; + Some(id) + } + _ => None, + }; + (ped, Some(car)) + } + TripSpec::UsingParkedCar { .. } + | TripSpec::MaybeUsingParkedCar { .. } + | TripSpec::JustWalking { .. } + | TripSpec::UsingTransit { .. } => { + let id = PedestrianID(self.ped_id_counter); + self.ped_id_counter += 1; + (Some(id), None) + } + TripSpec::UsingBike { .. } => { + let ped = PedestrianID(self.ped_id_counter); + self.ped_id_counter += 1; + let car = CarID(self.car_id_counter, VehicleType::Bike); + self.car_id_counter += 1; + (Some(ped), Some(car)) + } + }; + + self.inner_schedule_trip(start_time, ped_id, car_id, spec, map, sim); + + (ped_id, car_id) + } + + // TODO Maybe collapse this in the future + fn inner_schedule_trip( &mut self, start_time: Time, ped_id: Option, car_id: Option, spec: TripSpec, map: &Map, - parking: &ParkingSimState, + sim: &Sim, ) { // TODO We'll want to repeat this validation when we spawn stuff later for a second leg... match &spec { @@ -106,7 +162,12 @@ impl TripSpawner { } } TripSpec::UsingParkedCar { spot, .. } => { - let car_id = parking.get_car_at_spot(*spot).unwrap().vehicle.id; + let car_id = sim + .spawner_parking() + .get_car_at_spot(*spot) + .unwrap() + .vehicle + .id; if self.parked_cars_claimed.contains(&car_id) { panic!( "A TripSpec wants to use {}, which is already claimed", @@ -179,12 +240,12 @@ impl TripSpawner { self.trips.push((start_time, ped_id, car_id, spec)); } - pub fn spawn_all( - &mut self, + pub fn finalize( + mut self, map: &Map, - parking: &ParkingSimState, trips: &mut TripManager, scheduler: &mut Scheduler, + parking: &ParkingSimState, timer: &mut Timer, retry_if_no_room: bool, ) { @@ -469,10 +530,6 @@ impl TripSpawner { scheduler.finalize_batch(); timer.stop("finalize spawned trips"); } - - pub fn is_done(&self) -> bool { - self.trips.is_empty() - } } impl TripSpec { diff --git a/sim/src/sim.rs b/sim/src/sim.rs index 020463775c..d8f8f7fbcd 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -30,7 +30,6 @@ pub struct Sim { intersections: IntersectionSimState, transit: TransitSimState, trips: TripManager, - spawner: TripSpawner, scheduler: Scheduler, time: Time, car_id_counter: usize, @@ -107,7 +106,6 @@ impl Sim { ), transit: TransitSimState::new(), trips: TripManager::new(), - spawner: TripSpawner::new(), scheduler, time: Time::START_OF_DAY, car_id_counter: 0, @@ -125,62 +123,31 @@ impl Sim { } } - pub fn schedule_trip( - &mut self, - start_time: Time, - spec: TripSpec, - map: &Map, - ) -> (Option, Option) { - let (ped_id, car_id) = match spec { - TripSpec::CarAppearing { - ref vehicle_spec, - ref goal, - .. - } => { - let car = CarID(self.car_id_counter, vehicle_spec.vehicle_type); - self.car_id_counter += 1; - let ped = match goal { - DrivingGoal::ParkNear(_) => { - let id = PedestrianID(self.ped_id_counter); - self.ped_id_counter += 1; - Some(id) - } - _ => None, - }; - (ped, Some(car)) - } - TripSpec::UsingParkedCar { .. } - | TripSpec::MaybeUsingParkedCar { .. } - | TripSpec::JustWalking { .. } - | TripSpec::UsingTransit { .. } => { - let id = PedestrianID(self.ped_id_counter); - self.ped_id_counter += 1; - (Some(id), None) - } - TripSpec::UsingBike { .. } => { - let ped = PedestrianID(self.ped_id_counter); - self.ped_id_counter += 1; - let car = CarID(self.car_id_counter, VehicleType::Bike); - self.car_id_counter += 1; - (Some(ped), Some(car)) - } - }; - - self.spawner - .schedule_trip(start_time, ped_id, car_id, spec, map, &self.parking); - (ped_id, car_id) + pub fn make_spawner(&self) -> TripSpawner { + TripSpawner::new(self.car_id_counter, self.ped_id_counter) } - - pub fn spawn_all_trips(&mut self, map: &Map, timer: &mut Timer, retry_if_no_room: bool) { - self.spawner.spawn_all( + pub fn flush_spawner( + &mut self, + spawner: TripSpawner, + map: &Map, + timer: &mut Timer, + retry_if_no_room: bool, + ) { + self.car_id_counter = spawner.car_id_counter; + self.ped_id_counter = spawner.ped_id_counter; + spawner.finalize( map, - &self.parking, &mut self.trips, &mut self.scheduler, + &self.parking, timer, retry_if_no_room, ); } + // TODO Friend method pattern :( + pub(crate) fn spawner_parking(&self) -> &ParkingSimState { + &self.parking + } pub fn get_free_spots(&self, l: LaneID) -> Vec { self.parking.get_free_spots(l) @@ -372,9 +339,6 @@ impl Sim { // Advances time as minimally as possible, also limited by max_dt. fn minimal_step(&mut self, map: &Map, max_dt: Duration) { self.step_count += 1; - if !self.spawner.is_done() { - panic!("Forgot to call spawn_all_trips"); - } let max_time = if let Some(t) = self.scheduler.peek_next_time() { if t > self.time + max_dt { @@ -815,10 +779,6 @@ impl Sim { "- trips: {} bytes", abstutil::prettyprint_usize(abstutil::serialized_size_bytes(&self.trips)) ); - println!( - "- spawner: {} bytes", - abstutil::prettyprint_usize(abstutil::serialized_size_bytes(&self.spawner)) - ); println!( "- scheduler: {} bytes", abstutil::prettyprint_usize(abstutil::serialized_size_bytes(&self.scheduler)) @@ -868,7 +828,7 @@ impl Sim { } pub fn is_done(&self) -> bool { - self.spawner.is_done() && self.trips.is_done() + self.trips.is_done() } pub fn is_empty(&self) -> bool {