mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-29 04:35:51 +03:00
use Event::Alert instead of random println's, and make better controls for handling these
This commit is contained in:
parent
cce4fbe80d
commit
597ee6c47e
@ -6,7 +6,7 @@ use crate::render::DrawMap;
|
||||
use ezgui::{Choice, EventCtx, GfxCtx, Wizard, WrappedWizard};
|
||||
use geom::Duration;
|
||||
use map_model::MapEdits;
|
||||
use sim::{ABTest, Scenario, SimFlags, SimOptions};
|
||||
use sim::{ABTest, AlertHandler, Scenario, SimFlags, SimOptions};
|
||||
|
||||
pub struct PickABTest;
|
||||
impl PickABTest {
|
||||
@ -167,6 +167,7 @@ fn launch_test(test: &ABTest, app: &mut App, ctx: &mut EventCtx) -> ABTestMode {
|
||||
.opts
|
||||
.break_turn_conflict_cycles,
|
||||
enable_pandemic_model: None,
|
||||
alerts: AlertHandler::Print,
|
||||
},
|
||||
},
|
||||
..current_flags.clone()
|
||||
|
@ -274,27 +274,22 @@ impl SpeedControls {
|
||||
if !alerts.is_empty() {
|
||||
self.pause(ctx, app);
|
||||
|
||||
// Just go to the first one, but print all messages
|
||||
let id = ID::Intersection(alerts[0].1);
|
||||
return Some(Transition::PushTwice(
|
||||
msg(
|
||||
"Alerts",
|
||||
alerts
|
||||
.into_iter()
|
||||
.map(|(_, _, msg)| {
|
||||
println!("Alert: {}", msg);
|
||||
msg
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
Warping::new(
|
||||
ctx,
|
||||
id.canonical_point(&app.primary).unwrap(),
|
||||
Some(10.0),
|
||||
None,
|
||||
&mut app.primary,
|
||||
),
|
||||
));
|
||||
let popup = msg("Alerts", alerts.iter().map(|(_, _, msg)| msg).collect());
|
||||
if let Some(i) = alerts[0].1 {
|
||||
// Just go to the first one, but print all messages
|
||||
return Some(Transition::PushTwice(
|
||||
popup,
|
||||
Warping::new(
|
||||
ctx,
|
||||
ID::Intersection(i).canonical_point(&app.primary).unwrap(),
|
||||
Some(10.0),
|
||||
None,
|
||||
&mut app.primary,
|
||||
),
|
||||
));
|
||||
} else {
|
||||
return Some(Transition::Push(popup));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
@ -517,9 +512,13 @@ impl State for TimeWarpScreen {
|
||||
));
|
||||
}
|
||||
// TODO secondary for a/b test mode
|
||||
// For now, don't stop for this
|
||||
for (t, i, msg) in app.primary.sim.clear_alerts() {
|
||||
println!("- Alert: At {}, near {}, {}", t, i, msg);
|
||||
|
||||
for (t, maybe_i, alert) in app.primary.sim.clear_alerts() {
|
||||
// TODO Just the first :(
|
||||
return Transition::Replace(msg(
|
||||
"Alert",
|
||||
vec![format!("At {}, near {:?}, {}", t, maybe_i, alert)],
|
||||
));
|
||||
}
|
||||
|
||||
// I'm covered in shame for not doing this from the start.
|
||||
|
@ -26,7 +26,7 @@ pub struct Analytics {
|
||||
pub intersection_delays: BTreeMap<IntersectionID, Vec<(Time, Duration)>>,
|
||||
// Per parking lane, when does a spot become filled (true) or free (false)
|
||||
parking_spot_changes: BTreeMap<LaneID, Vec<(Time, bool)>>,
|
||||
pub(crate) alerts: Vec<(Time, IntersectionID, String)>,
|
||||
pub(crate) alerts: Vec<(Time, Option<IntersectionID>, String)>,
|
||||
|
||||
// After we restore from a savestate, don't record anything. This is only going to make sense
|
||||
// if savestates are only used for quickly previewing against prebaked results, where we have
|
||||
|
@ -47,7 +47,8 @@ pub enum Event {
|
||||
// to plumb info into Analytics is Event.
|
||||
PathAmended(Path),
|
||||
|
||||
Alert(IntersectionID, String),
|
||||
// TODO Also buildings
|
||||
Alert(Option<IntersectionID>, String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
|
@ -23,7 +23,7 @@ pub(crate) use self::mechanics::{
|
||||
pub(crate) use self::pandemic::PandemicModel;
|
||||
pub(crate) use self::router::{ActionAtEnd, Router};
|
||||
pub(crate) use self::scheduler::{Command, Scheduler};
|
||||
pub use self::sim::{AgentProperties, Sim, SimOptions};
|
||||
pub use self::sim::{AgentProperties, AlertHandler, Sim, SimOptions};
|
||||
pub(crate) use self::transit::TransitSimState;
|
||||
pub use self::trips::{Person, PersonState, TripCount, TripResult};
|
||||
pub use self::trips::{TripEndpoint, TripMode};
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Scenario, Sim, SimOptions};
|
||||
use crate::{AlertHandler, Scenario, Sim, SimOptions};
|
||||
use abstutil::CmdArgs;
|
||||
use geom::Duration;
|
||||
use map_model::{Map, MapEdits};
|
||||
@ -41,6 +41,15 @@ impl SimFlags {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
alerts: args
|
||||
.optional("--alerts")
|
||||
.map(|x| match x.as_ref() {
|
||||
"print" => AlertHandler::Print,
|
||||
"block" => AlertHandler::Block,
|
||||
"silence" => AlertHandler::Silence,
|
||||
_ => panic!("Bad --alerts={}. Must be print|block|silence", x),
|
||||
})
|
||||
.unwrap_or(AlertHandler::Print),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -395,12 +395,10 @@ impl State {
|
||||
while !queue.is_empty() {
|
||||
let current = queue.pop().unwrap();
|
||||
if !seen.is_empty() && current == req.agent {
|
||||
if false {
|
||||
events.push(Event::Alert(
|
||||
req.turn.parent,
|
||||
format!("Turn conflict cycle involving {:?}", seen),
|
||||
));
|
||||
}
|
||||
events.push(Event::Alert(
|
||||
Some(req.turn.parent),
|
||||
format!("Turn conflict cycle involving {:?}", seen),
|
||||
));
|
||||
return true;
|
||||
}
|
||||
// Because the blocked-by relation is many-to-many, this might happen.
|
||||
@ -539,11 +537,13 @@ impl State {
|
||||
if time_to_cross > remaining_phase_time {
|
||||
// Actually, we might have bigger problems...
|
||||
if time_to_cross > phase.duration {
|
||||
println!(
|
||||
"OYYY! {:?} is impossible to fit into phase duration of {}. Allowing, but fix \
|
||||
the policy!",
|
||||
req, phase.duration
|
||||
);
|
||||
events.push(Event::Alert(
|
||||
Some(req.turn.parent),
|
||||
format!(
|
||||
"{:?} is impossible to fit into phase duration of {}",
|
||||
req, phase.duration
|
||||
),
|
||||
));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -61,6 +61,10 @@ pub struct Sim {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
check_for_gridlock: Option<(Time, Duration)>,
|
||||
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
alerts: AlertHandler,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -72,6 +76,23 @@ pub struct SimOptions {
|
||||
pub recalc_lanechanging: bool,
|
||||
pub break_turn_conflict_cycles: bool,
|
||||
pub enable_pandemic_model: Option<XorShiftRng>,
|
||||
pub alerts: AlertHandler,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum AlertHandler {
|
||||
// Just print the alert to STDOUT
|
||||
Print,
|
||||
// Print the alert to STDOUT and don't proceed until the UI calls clear_alerts()
|
||||
Block,
|
||||
// Don't do anything
|
||||
Silence,
|
||||
}
|
||||
|
||||
impl std::default::Default for AlertHandler {
|
||||
fn default() -> AlertHandler {
|
||||
AlertHandler::Print
|
||||
}
|
||||
}
|
||||
|
||||
impl SimOptions {
|
||||
@ -84,6 +105,7 @@ impl SimOptions {
|
||||
recalc_lanechanging: true,
|
||||
break_turn_conflict_cycles: false,
|
||||
enable_pandemic_model: None,
|
||||
alerts: AlertHandler::Print,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,6 +145,7 @@ impl Sim {
|
||||
step_count: 0,
|
||||
trip_positions: None,
|
||||
check_for_gridlock: None,
|
||||
alerts: opts.alerts,
|
||||
|
||||
analytics: Analytics::new(),
|
||||
}
|
||||
@ -586,10 +609,25 @@ impl Sim {
|
||||
|
||||
timer.start(format!("Advance sim to {}", end_time));
|
||||
while self.time < end_time {
|
||||
if !self.analytics.alerts.is_empty() {
|
||||
break;
|
||||
}
|
||||
self.minimal_step(map, end_time - self.time);
|
||||
if !self.analytics.alerts.is_empty() {
|
||||
match self.alerts {
|
||||
AlertHandler::Print => {
|
||||
for (t, _, msg) in self.analytics.alerts.drain(..) {
|
||||
println!("Alert at {}: {}", t, msg);
|
||||
}
|
||||
}
|
||||
AlertHandler::Block => {
|
||||
for (t, _, msg) in &self.analytics.alerts {
|
||||
println!("Alert at {}: {}", t, msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
AlertHandler::Silence => {
|
||||
self.analytics.alerts.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if Duration::realtime_elapsed(last_update) >= Duration::seconds(1.0) {
|
||||
// TODO Not timer?
|
||||
println!(
|
||||
@ -627,10 +665,25 @@ impl Sim {
|
||||
let end_time = self.time + dt;
|
||||
|
||||
while self.time < end_time && Duration::realtime_elapsed(started_at) < real_time_limit {
|
||||
if !self.analytics.alerts.is_empty() {
|
||||
break;
|
||||
}
|
||||
self.minimal_step(map, end_time - self.time);
|
||||
if !self.analytics.alerts.is_empty() {
|
||||
match self.alerts {
|
||||
AlertHandler::Print => {
|
||||
for (t, _, msg) in self.analytics.alerts.drain(..) {
|
||||
println!("Alert at {}: {}", t, msg);
|
||||
}
|
||||
}
|
||||
AlertHandler::Block => {
|
||||
for (t, _, msg) in &self.analytics.alerts {
|
||||
println!("Alert at {}: {}", t, msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
AlertHandler::Silence => {
|
||||
self.analytics.alerts.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((ref mut t, dt)) = self.check_for_gridlock {
|
||||
if self.time >= *t {
|
||||
*t += dt;
|
||||
@ -1134,7 +1187,7 @@ impl Sim {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_alerts(&mut self) -> Vec<(Time, IntersectionID, String)> {
|
||||
pub fn clear_alerts(&mut self) -> Vec<(Time, Option<IntersectionID>, String)> {
|
||||
std::mem::replace(&mut self.analytics.alerts, Vec::new())
|
||||
}
|
||||
}
|
||||
|
156
sim/src/trips.rs
156
sim/src/trips.rs
@ -195,6 +195,7 @@ impl TripManager {
|
||||
&self.people[trip.person.0],
|
||||
map,
|
||||
scheduler,
|
||||
&mut self.events,
|
||||
) {
|
||||
self.unfinished_trips -= 1;
|
||||
}
|
||||
@ -242,10 +243,13 @@ impl TripManager {
|
||||
let path = if let Some(p) = map.pathfind(req.clone()) {
|
||||
p
|
||||
} else {
|
||||
println!(
|
||||
"Aborting {} at {} because no path for the car portion! {} to {}",
|
||||
trip.id, now, start, end
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"Aborting {} because no path for the car portion! {} to {}",
|
||||
trip.id, start, end
|
||||
),
|
||||
));
|
||||
// Move the car to the destination...
|
||||
parking.remove_parked_car(parked_car.clone());
|
||||
let trip = trip.id;
|
||||
@ -324,11 +328,14 @@ impl TripManager {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"Aborting {} at {} because no path for the bike portion (or sidewalk connection \
|
||||
at the end)! {} to {}",
|
||||
trip.id, now, driving_pos, end
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"Aborting {} because no path for the bike portion (or sidewalk connection at \
|
||||
the end)! {} to {}",
|
||||
trip.id, driving_pos, end
|
||||
),
|
||||
));
|
||||
let trip = trip.id;
|
||||
self.abort_trip(now, trip, None, parking, scheduler, map);
|
||||
}
|
||||
@ -357,7 +364,14 @@ impl TripManager {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if !trip.spawn_ped(now, bike_rack, &self.people[trip.person.0], map, scheduler) {
|
||||
if !trip.spawn_ped(
|
||||
now,
|
||||
bike_rack,
|
||||
&self.people[trip.person.0],
|
||||
map,
|
||||
scheduler,
|
||||
&mut self.events,
|
||||
) {
|
||||
self.unfinished_trips -= 1;
|
||||
}
|
||||
}
|
||||
@ -478,7 +492,14 @@ impl TripManager {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if !trip.spawn_ped(now, start, &self.people[trip.person.0], map, scheduler) {
|
||||
if !trip.spawn_ped(
|
||||
now,
|
||||
start,
|
||||
&self.people[trip.person.0],
|
||||
map,
|
||||
scheduler,
|
||||
&mut self.events,
|
||||
) {
|
||||
self.unfinished_trips -= 1;
|
||||
}
|
||||
}
|
||||
@ -599,17 +620,23 @@ impl TripManager {
|
||||
.map(|(_, spot, _)| spot)
|
||||
})
|
||||
{
|
||||
println!(
|
||||
"{} had a trip aborted, and their car was warped to {:?}",
|
||||
person, spot
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"{} had a trip aborted, and their car was warped to {:?}",
|
||||
person, spot
|
||||
),
|
||||
));
|
||||
parking.reserve_spot(spot);
|
||||
parking.add_parked_car(ParkedCar { vehicle, spot });
|
||||
} else {
|
||||
println!(
|
||||
"{} had a trip aborted, but nowhere to warp their car! Sucks.",
|
||||
person
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"{} had a trip aborted, but nowhere to warp their car! Sucks.",
|
||||
person
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -798,10 +825,13 @@ impl TripManager {
|
||||
return;
|
||||
}
|
||||
let (trip, spec, maybe_req, maybe_path) = person.delayed_trips.remove(0);
|
||||
println!(
|
||||
"At {}, {} just freed up, so starting delayed trip {}",
|
||||
now, person.id, trip
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"{} just freed up, so starting delayed trip {}",
|
||||
person.id, trip
|
||||
),
|
||||
));
|
||||
self.start_trip(
|
||||
now, trip, spec, maybe_req, maybe_path, parking, scheduler, map,
|
||||
);
|
||||
@ -821,10 +851,13 @@ impl TripManager {
|
||||
let person = &mut self.people[self.trips[trip.0].person.0];
|
||||
if let PersonState::Trip(_) = person.state {
|
||||
// Previous trip isn't done. Defer this one!
|
||||
println!(
|
||||
"At {}, {} is still doing a trip, so not starting {}",
|
||||
now, person.id, trip
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"{} is still doing a trip, so not starting {}",
|
||||
person.id, trip
|
||||
),
|
||||
));
|
||||
person
|
||||
.delayed_trips
|
||||
.push((trip, spec, maybe_req, maybe_path));
|
||||
@ -864,19 +897,25 @@ impl TripManager {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"VehicleAppearing trip couldn't find the first path (or no bike->sidewalk \
|
||||
connection at the end): {}",
|
||||
req
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"VehicleAppearing trip couldn't find the first path (or no \
|
||||
bike->sidewalk connection at the end): {}",
|
||||
req
|
||||
),
|
||||
));
|
||||
self.abort_trip(now, trip, Some(vehicle), parking, scheduler, map);
|
||||
}
|
||||
}
|
||||
TripSpec::NoRoomToSpawn { i, use_vehicle, .. } => {
|
||||
println!(
|
||||
"{} couldn't spawn at border {}, just aborting",
|
||||
person.id, i
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
Some(i),
|
||||
format!(
|
||||
"{} couldn't spawn at border {}, just aborting",
|
||||
person.id, i
|
||||
),
|
||||
));
|
||||
let vehicle = person.get_vehicle(use_vehicle);
|
||||
self.abort_trip(now, trip, Some(vehicle), parking, scheduler, map);
|
||||
}
|
||||
@ -912,7 +951,10 @@ impl TripManager {
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
println!("UsingParkedCar trip couldn't find the walking path {}", req);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!("UsingParkedCar trip couldn't find the walking path {}", req),
|
||||
));
|
||||
// Move the car to the destination
|
||||
parking.remove_parked_car(parked_car.clone());
|
||||
self.abort_trip(
|
||||
@ -927,11 +969,14 @@ impl TripManager {
|
||||
} else {
|
||||
// This should only happen when a driving trip has been aborted and there was
|
||||
// absolutely no room to warp the car.
|
||||
println!(
|
||||
"At {}, {} should have {} parked somewhere, but it's unavailable, so \
|
||||
aborting {}",
|
||||
now, person.id, car, trip
|
||||
);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"{} should have {} parked somewhere, but it's unavailable, so \
|
||||
aborting {}",
|
||||
person.id, car, trip
|
||||
),
|
||||
));
|
||||
self.abort_trip(now, trip, None, parking, scheduler, map);
|
||||
}
|
||||
}
|
||||
@ -963,7 +1008,10 @@ impl TripManager {
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
println!("JustWalking trip couldn't find the first path {}", req);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!("JustWalking trip couldn't find the first path {}", req),
|
||||
));
|
||||
self.abort_trip(now, trip, None, parking, scheduler, map);
|
||||
}
|
||||
}
|
||||
@ -997,7 +1045,10 @@ impl TripManager {
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
println!("UsingBike trip couldn't find the first path {}", req);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!("UsingBike trip couldn't find the first path {}", req),
|
||||
));
|
||||
self.abort_trip(now, trip, None, parking, scheduler, map);
|
||||
}
|
||||
}
|
||||
@ -1030,7 +1081,10 @@ impl TripManager {
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
println!("UsingTransit trip couldn't find the first path {}", req);
|
||||
self.events.push(Event::Alert(
|
||||
None,
|
||||
format!("UsingTransit trip couldn't find the first path {}", req),
|
||||
));
|
||||
self.abort_trip(now, trip, None, parking, scheduler, map);
|
||||
}
|
||||
}
|
||||
@ -1061,6 +1115,7 @@ impl Trip {
|
||||
person: &Person,
|
||||
map: &Map,
|
||||
scheduler: &mut Scheduler,
|
||||
events: &mut Vec<Event>,
|
||||
) -> bool {
|
||||
let walk_to = match self.legs[0] {
|
||||
TripLeg::Walk(ref to) => to.clone(),
|
||||
@ -1075,10 +1130,13 @@ impl Trip {
|
||||
let path = if let Some(p) = map.pathfind(req.clone()) {
|
||||
p
|
||||
} else {
|
||||
println!(
|
||||
"Aborting {} at {} because no path for the walking portion! {:?} to {:?}",
|
||||
self.id, now, start, walk_to
|
||||
);
|
||||
events.push(Event::Alert(
|
||||
None,
|
||||
format!(
|
||||
"Aborting {} because no path for the walking portion! {:?} to {:?}",
|
||||
self.id, start, walk_to
|
||||
),
|
||||
));
|
||||
return false;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user