eager impl to not hit the back of cars. no handling of short lanes, and

follower waits before starting to cross the end of a lane.
This commit is contained in:
Dustin Carlino 2019-03-15 15:14:31 -07:00
parent db536a04d5
commit c40c4fdff4
8 changed files with 220 additions and 66 deletions

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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),
);
}

View File

@ -9,6 +9,8 @@ use std::collections::{BTreeMap, VecDeque};
pub struct Queue {
pub id: Traversable,
pub cars: VecDeque<CarID>,
// This car's back is still partly in this queue.
pub laggy_head: Option<CarID>,
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<CarID, Car>,
) -> Option<usize> {
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

View File

@ -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,

View File

@ -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,