diff --git a/docs/design/traffic_sim.md b/docs/design/traffic_sim.md index 54c1e9fda9..bd50a8a4e1 100644 --- a/docs/design/traffic_sim.md +++ b/docs/design/traffic_sim.md @@ -357,6 +357,49 @@ calculate exactly when to update a car. Except for three: full, and it's hard to predict when there'll be room to inch forward at the end. +#### Notes on fixing the jump bug + +- can track a laggy leader per queue. there's 0 or 1 of these, impossible to be more. +- a car can be a laggy leader in several queues, like long bus on short lanes. last_steps is kinda equivalent to this. + + +is it possible for get_car_positions to think something should still be blocked but the rest of the state machine to not? + - dont change somebody to WaitingToAdvance until theres no laggy leader. + + +TODO impl: +- get_car_positions needs to recurse +- trim_last_steps needs to do something different + + + + +the eager impl: +- anytime we update a Car with last_steps, try to go erase those. need full distances. when we successfully erase, update followers that are Queued. + - - follower only starts moving once the laggy lead is totally out. wrong. they were Queued, so i +mmediately promote them to WaitingToAdvance. smoothly draw in the meantime by recursing +- optimistic check in the middle of Crossing, but use a different event for this to be clear. the retry should only be expensive in gridlocky cases. + - BLIND_RETRY after... similar to end_dist checking. + - note other routines dont even do checks that could hit numerical precision, we just assume events are scheduled for correct time. +- maybe v1: dont recurse in get_car_positions, just block off entire laggy leader length until they're totally out. +- v2: do have to recurse. :( + + +the lazy impl: +- when we become WaitingToAdvance on a queue of length > vehicle len, clean up last_steps if needed +- when we vanish, clean up all last_steps +- when a follower gets to ithin laggy leader length of the queue end, need to resolve earlier + - resolving early needs to get exact distances. expensive, but common case is theyre clear. if somebody gets stuck fully leaving a queue, then this will spam, but thats also gridlocky and we want to avoid that anyway + + + +other ideas: +- can we cheat and ensure that we only clip into laggy leaders briefly? + - if laggy leaders never get stuck at end of their next queue... +- alt: store front and back of each car in queues separately + - compute crossing state and stuff for those individually? double the work? + + ## A/B Street's full simulation architecture start from step(), the master event queue, how each event is dispatched, each diff --git a/docs/development.md b/docs/development.md index 386aa28dec..73aa56960e 100644 --- a/docs/development.md +++ b/docs/development.md @@ -28,7 +28,16 @@ top30 --cum ## Building releases -Cross-compilation notes: https://github.com/rust-embedded/cross +Cross-compilation notes: https://github.com/rust-embedded/cross Or use +https://github.com/japaric/trust + +Initially have to: + +```shell +rustup target add x86_64-pc-windows-gnu +``` + +Then: ``` cross build --release --target x86_64-pc-windows-gnu --bin editor diff --git a/geom/src/duration.rs b/geom/src/duration.rs index 2dffbd91a1..0ee968021c 100644 --- a/geom/src/duration.rs +++ b/geom/src/duration.rs @@ -133,6 +133,18 @@ impl Duration { hours, minutes, seconds, remainder ) } + + fn abs(self) -> Duration { + if self.0 > 0.0 { + self + } else { + Duration(-self.0) + } + } + + pub fn almost_eq(self, other: Duration, threshold: Duration) -> bool { + (self - other).abs() <= threshold + } } impl std::fmt::Display for Duration { diff --git a/sim/src/mechanics/car.rs b/sim/src/mechanics/car.rs index 0cc1d3b8a5..5061f4703c 100644 --- a/sim/src/mechanics/car.rs +++ b/sim/src/mechanics/car.rs @@ -25,15 +25,24 @@ impl Car { start_time: Duration, map: &Map, ) -> CarState { - let on = self.router.head(); let dist_int = DistanceInterval::new_driving( start_dist, if self.router.last_step() { self.router.get_end_dist() } else { - on.length(map) + self.router.head().length(map) }, ); + self.crossing_state_with_end_dist(dist_int, start_time, map) + } + + pub fn crossing_state_with_end_dist( + &self, + dist_int: DistanceInterval, + start_time: Duration, + map: &Map, + ) -> CarState { + let on = self.router.head(); let mut speed = on.speed_limit(map); if let Some(s) = self.vehicle.max_speed { speed = speed.min(s); @@ -42,19 +51,6 @@ impl Car { CarState::Crossing(TimeInterval::new(start_time, start_time + dt), dist_int) } - pub fn trim_last_steps(&mut self, map: &Map) { - let mut keep = VecDeque::new(); - let mut len = Distance::ZERO; - for on in self.last_steps.drain(..) { - len += on.length(map); - keep.push_back(on); - if len >= self.vehicle.length { - break; - } - } - self.last_steps = keep; - } - pub fn get_draw_car(&self, front: Distance, time: Duration, map: &Map) -> DrawCarInput { assert!(front >= Distance::ZERO); let raw_body = if front >= self.vehicle.length { diff --git a/sim/src/mechanics/driving.rs b/sim/src/mechanics/driving.rs index 3c7f426104..ac089f9a58 100644 --- a/sim/src/mechanics/driving.rs +++ b/sim/src/mechanics/driving.rs @@ -1,9 +1,9 @@ use crate::mechanics::car::{Car, CarState}; use crate::mechanics::queue::Queue; use crate::{ - ActionAtEnd, AgentID, CarID, Command, CreateCar, DrawCarInput, IntersectionSimState, ParkedCar, - ParkingSimState, Scheduler, TimeInterval, TransitSimState, TripManager, WalkingSimState, - BLIND_RETRY, FOLLOWING_DISTANCE, + ActionAtEnd, AgentID, CarID, Command, CreateCar, DistanceInterval, DrawCarInput, + IntersectionSimState, ParkedCar, ParkingSimState, Scheduler, TimeInterval, TransitSimState, + TripManager, WalkingSimState, BLIND_RETRY, FOLLOWING_DISTANCE, }; use abstutil::{deserialize_btreemap, serialize_btreemap}; use ezgui::{Color, GfxCtx}; @@ -212,7 +212,8 @@ impl DrivingSimState { // Immediately run update_car_with_distances. return true; } - if self.queues[&car.router.head()].cars[0] == car.vehicle.id { + let queue = &self.queues[&car.router.head()]; + if queue.cars[0] == car.vehicle.id && queue.laggy_head.is_none() { // Want to re-run, but no urgency about it happening immediately. car.state = CarState::WaitingToAdvance; scheduler.push(time, Command::UpdateCar(car.vehicle.id)); @@ -294,56 +295,31 @@ impl DrivingSimState { } } - assert_eq!( - self.queues - .get_mut(&from) - .unwrap() - .cars - .pop_front() - .unwrap(), - car.vehicle.id - ); - - // Update the follower so that they don't suddenly jump forwards. - if let Some(follower_id) = self.queues[&from].cars.front() { - let mut follower = self.cars.get_mut(&follower_id).unwrap(); - // TODO This still jumps a bit -- the lead car's back is still sticking out. Need - // to still be bound by them. - match follower.state { - CarState::Queued => { - // If they're on their last step, they might be ending early and not - // right behind us. - if !follower.router.last_step() { - follower.state = follower.crossing_state( - // Since the follower was Queued, this must be where they are. This - // update case is when the lead car was NOT on their last step, so - // they were indeed all the way at the end of 'from'. - from.length(map) - car.vehicle.length - FOLLOWING_DISTANCE, - time, - map, - ); - scheduler.update( - Command::UpdateCar(*follower_id), - follower.state.get_end_time(), - ); - } - } - CarState::WaitingToAdvance => unreachable!(), - // They weren't blocked. Note that there's no way the Crossing state could jump - // forwards here; the leader vanished from the end of the traversable. - CarState::Crossing(_, _) - | CarState::Unparking(_, _) - | CarState::Parking(_, _, _) - | CarState::Idling(_, _) => {} - } + { + let mut queue = self.queues.get_mut(&from).unwrap(); + assert_eq!(queue.cars.pop_front().unwrap(), car.vehicle.id); + queue.laggy_head = Some(car.vehicle.id); } + // We do NOT need to update the follower. If they were Queued, they'll remain that + // way, until laggy_head is None. + let last_step = car.router.advance(&car.vehicle, parking, map); car.last_steps.push_front(last_step); - car.trim_last_steps(map); car.state = car.crossing_state(Distance::ZERO, time, map); scheduler.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id)); + // TODO Doesn't handle short lanes and stopping short + scheduler.push( + car.crossing_state_with_end_dist( + DistanceInterval::new_driving(Distance::ZERO, car.vehicle.length), + time, + map, + ) + .get_end_time(), + Command::UpdateLaggyHead(car.vehicle.id), + ); + if goto.maybe_lane().is_some() { // TODO Actually, don't call turn_finished until the car is at least // vehicle.length + FOLLOWING_DISTANCE into the next lane. This'll be hard @@ -485,6 +461,10 @@ impl DrivingSimState { car.vehicle.id ); + // TODO Unless we vanished from something short, we should have already done this. When + // handling the short case, also be sure to cancel the update message... + assert!(car.last_steps.is_empty()); + // Update the follower so that they don't suddenly jump forwards. if idx != dists.len() - 1 { let (follower_id, follower_dist) = dists[idx + 1]; @@ -514,6 +494,100 @@ impl DrivingSimState { false } + // TODO Maybe also remember what the last step was in this event, to make sure we're canceling + // events and everything + pub fn update_laggy_head( + &mut self, + id: CarID, + time: Duration, + map: &Map, + scheduler: &mut Scheduler, + ) { + // TODO The impl here is pretty gross; play the same trick and remove car temporarily? + let dists = self.queues[&self.cars[&id].router.head()].get_car_positions(time, &self.cars); + // This car must be the tail. + assert_eq!(id, dists.last().unwrap().0); + let our_len = self.cars[&id].vehicle.length; + + // TODO This will update short lanes too late I think? + + // Have we made it far enough yet? Unfortunately, we have some math imprecision issues... + { + let our_dist = dists.last().unwrap().1; + let car = &self.cars[&id]; + if our_dist < our_len { + let retry_at = car + .crossing_state_with_end_dist( + DistanceInterval::new_driving(our_dist, our_len), + time, + map, + ) + .get_end_time(); + if !retry_at.almost_eq(time, Duration::seconds(0.1)) { + // TODO Smarter retry based on states and stuckness? + println!( + "{} really does need to retry {} from now, needs {} more", + id, + retry_at - time, + our_len - our_dist + ); + scheduler.push(retry_at, Command::UpdateLaggyHead(car.vehicle.id)); + return; + } + } + } + + // If we were blocking a few short lanes, should be better now. Very last one might have + // somebody to wake up. + { + let car = &self.cars[&id]; + if car.last_steps.len() != 1 { + println!("At {}, {} has last steps {:?}", time, id, car.last_steps); + } + } + assert_eq!(1, self.cars[&id].last_steps.len()); + let old_on = self + .cars + .get_mut(&id) + .unwrap() + .last_steps + .pop_front() + .unwrap(); + let old_queue = self.queues.get_mut(&old_on).unwrap(); + assert_eq!(old_queue.laggy_head, Some(id)); + old_queue.laggy_head = None; + + // Wake em up + if let Some(follower_id) = old_queue.cars.front() { + let mut follower = self.cars.get_mut(&follower_id).unwrap(); + match follower.state { + CarState::Queued => { + // If they're on their last step, they might be ending early and not right + // behind us. + if !follower.router.last_step() { + follower.state = follower.crossing_state( + // Since the follower was Queued, this must be where they are. + old_queue.geom_len - our_len - FOLLOWING_DISTANCE, + time, + map, + ); + scheduler.update( + Command::UpdateCar(*follower_id), + follower.state.get_end_time(), + ); + } + } + CarState::WaitingToAdvance => unreachable!(), + // They weren't blocked. Note that there's no way the Crossing state could jump + // forwards here; the leader vanished from the end of the traversable. + CarState::Crossing(_, _) + | CarState::Unparking(_, _) + | CarState::Parking(_, _, _) + | CarState::Idling(_, _) => {} + } + } + } + pub fn draw_unzoomed(&self, _time: Duration, g: &mut GfxCtx, map: &Map) { const FREEFLOW: Color = Color::CYAN; const WAITING: Color = Color::RED; @@ -593,7 +667,8 @@ impl DrivingSimState { let clamped_len = len.min(pl.length()); g.draw_polygon( FREEFLOW, - &pl.exact_slice(Distance::ZERO, clamped_len).make_polygons(width), + &pl.exact_slice(Distance::ZERO, clamped_len) + .make_polygons(width), ); } diff --git a/sim/src/mechanics/queue.rs b/sim/src/mechanics/queue.rs index d3e67c9b9d..356a1bd08a 100644 --- a/sim/src/mechanics/queue.rs +++ b/sim/src/mechanics/queue.rs @@ -9,6 +9,8 @@ use std::collections::{BTreeMap, VecDeque}; pub struct Queue { pub id: Traversable, pub cars: VecDeque, + // This car's back is still partly in this queue. + pub laggy_head: Option, pub geom_len: Distance, } @@ -19,6 +21,7 @@ impl Queue { Queue { id, cars: VecDeque::new(), + laggy_head: None, geom_len: len, } } @@ -40,7 +43,12 @@ impl Queue { Some((leader, last_dist)) => { *last_dist - cars[leader].vehicle.length - FOLLOWING_DISTANCE } - None => self.geom_len, + None => match self.laggy_head { + // TODO This is very basic version; really we need to recurse here + // TODO This doesn't handle short lanes + Some(id) => self.geom_len - cars[&id].vehicle.length - FOLLOWING_DISTANCE, + None => self.geom_len, + }, }; // There's spillover and a car shouldn't have been able to enter yet. @@ -87,7 +95,7 @@ impl Queue { time: Duration, cars: &BTreeMap, ) -> Option { - if self.cars.is_empty() { + if self.laggy_head.is_none() && self.cars.is_empty() { return Some(0); } @@ -98,6 +106,11 @@ impl Queue { None => dists.len(), }; + // Nope, there's not actually room at the front right now. + if self.laggy_head.is_some() && idx == 0 { + return None; + } + // Are we too close to the leader? if idx != 0 && dists[idx - 1].1 - cars[&dists[idx - 1].0].vehicle.length - FOLLOWING_DISTANCE diff --git a/sim/src/scheduler.rs b/sim/src/scheduler.rs index de69a42c13..d4cd8c9ef6 100644 --- a/sim/src/scheduler.rs +++ b/sim/src/scheduler.rs @@ -10,6 +10,8 @@ pub enum Command { SpawnCar(CreateCar), SpawnPed(CreatePedestrian), UpdateCar(CarID), + // Distinguish this from UpdateCar to avoid confusing things + UpdateLaggyHead(CarID), UpdatePed(PedestrianID), UpdateIntersection(IntersectionID), CheckForGridlock, diff --git a/sim/src/sim.rs b/sim/src/sim.rs index de0c522d2e..44c8b8f0ba 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -324,6 +324,10 @@ impl Sim { &mut self.walking, ); } + Command::UpdateLaggyHead(car) => { + self.driving + .update_laggy_head(car, self.time, map, &mut self.scheduler); + } Command::UpdatePed(ped) => { self.walking.update_ped( ped,