mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-26 16:02:23 +03:00
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:
parent
db536a04d5
commit
c40c4fdff4
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user