use Event::Alert instead of random println's, and make better controls for handling these

This commit is contained in:
Dustin Carlino 2020-04-23 16:09:32 -07:00
parent cce4fbe80d
commit 597ee6c47e
9 changed files with 217 additions and 96 deletions

View File

@ -6,7 +6,7 @@ use crate::render::DrawMap;
use ezgui::{Choice, EventCtx, GfxCtx, Wizard, WrappedWizard}; use ezgui::{Choice, EventCtx, GfxCtx, Wizard, WrappedWizard};
use geom::Duration; use geom::Duration;
use map_model::MapEdits; use map_model::MapEdits;
use sim::{ABTest, Scenario, SimFlags, SimOptions}; use sim::{ABTest, AlertHandler, Scenario, SimFlags, SimOptions};
pub struct PickABTest; pub struct PickABTest;
impl PickABTest { impl PickABTest {
@ -167,6 +167,7 @@ fn launch_test(test: &ABTest, app: &mut App, ctx: &mut EventCtx) -> ABTestMode {
.opts .opts
.break_turn_conflict_cycles, .break_turn_conflict_cycles,
enable_pandemic_model: None, enable_pandemic_model: None,
alerts: AlertHandler::Print,
}, },
}, },
..current_flags.clone() ..current_flags.clone()

View File

@ -274,27 +274,22 @@ impl SpeedControls {
if !alerts.is_empty() { if !alerts.is_empty() {
self.pause(ctx, app); self.pause(ctx, app);
// Just go to the first one, but print all messages let popup = msg("Alerts", alerts.iter().map(|(_, _, msg)| msg).collect());
let id = ID::Intersection(alerts[0].1); if let Some(i) = alerts[0].1 {
return Some(Transition::PushTwice( // Just go to the first one, but print all messages
msg( return Some(Transition::PushTwice(
"Alerts", popup,
alerts Warping::new(
.into_iter() ctx,
.map(|(_, _, msg)| { ID::Intersection(i).canonical_point(&app.primary).unwrap(),
println!("Alert: {}", msg); Some(10.0),
msg None,
}) &mut app.primary,
.collect(), ),
), ));
Warping::new( } else {
ctx, return Some(Transition::Push(popup));
id.canonical_point(&app.primary).unwrap(), }
Some(10.0),
None,
&mut app.primary,
),
));
} }
None None
@ -517,9 +512,13 @@ impl State for TimeWarpScreen {
)); ));
} }
// TODO secondary for a/b test mode // TODO secondary for a/b test mode
// For now, don't stop for this
for (t, i, msg) in app.primary.sim.clear_alerts() { for (t, maybe_i, alert) in app.primary.sim.clear_alerts() {
println!("- Alert: At {}, near {}, {}", t, i, msg); // 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. // I'm covered in shame for not doing this from the start.

View File

@ -26,7 +26,7 @@ pub struct Analytics {
pub intersection_delays: BTreeMap<IntersectionID, Vec<(Time, Duration)>>, pub intersection_delays: BTreeMap<IntersectionID, Vec<(Time, Duration)>>,
// Per parking lane, when does a spot become filled (true) or free (false) // Per parking lane, when does a spot become filled (true) or free (false)
parking_spot_changes: BTreeMap<LaneID, Vec<(Time, bool)>>, 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 // 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 // if savestates are only used for quickly previewing against prebaked results, where we have

View File

@ -47,7 +47,8 @@ pub enum Event {
// to plumb info into Analytics is Event. // to plumb info into Analytics is Event.
PathAmended(Path), PathAmended(Path),
Alert(IntersectionID, String), // TODO Also buildings
Alert(Option<IntersectionID>, String),
} }
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]

View File

@ -23,7 +23,7 @@ pub(crate) use self::mechanics::{
pub(crate) use self::pandemic::PandemicModel; pub(crate) use self::pandemic::PandemicModel;
pub(crate) use self::router::{ActionAtEnd, Router}; pub(crate) use self::router::{ActionAtEnd, Router};
pub(crate) use self::scheduler::{Command, Scheduler}; 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(crate) use self::transit::TransitSimState;
pub use self::trips::{Person, PersonState, TripCount, TripResult}; pub use self::trips::{Person, PersonState, TripCount, TripResult};
pub use self::trips::{TripEndpoint, TripMode}; pub use self::trips::{TripEndpoint, TripMode};

View File

@ -1,4 +1,4 @@
use crate::{Scenario, Sim, SimOptions}; use crate::{AlertHandler, Scenario, Sim, SimOptions};
use abstutil::CmdArgs; use abstutil::CmdArgs;
use geom::Duration; use geom::Duration;
use map_model::{Map, MapEdits}; use map_model::{Map, MapEdits};
@ -41,6 +41,15 @@ impl SimFlags {
} else { } else {
None 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),
}, },
} }
} }

View File

@ -395,12 +395,10 @@ impl State {
while !queue.is_empty() { while !queue.is_empty() {
let current = queue.pop().unwrap(); let current = queue.pop().unwrap();
if !seen.is_empty() && current == req.agent { if !seen.is_empty() && current == req.agent {
if false { events.push(Event::Alert(
events.push(Event::Alert( Some(req.turn.parent),
req.turn.parent, format!("Turn conflict cycle involving {:?}", seen),
format!("Turn conflict cycle involving {:?}", seen), ));
));
}
return true; return true;
} }
// Because the blocked-by relation is many-to-many, this might happen. // 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 { if time_to_cross > remaining_phase_time {
// Actually, we might have bigger problems... // Actually, we might have bigger problems...
if time_to_cross > phase.duration { if time_to_cross > phase.duration {
println!( events.push(Event::Alert(
"OYYY! {:?} is impossible to fit into phase duration of {}. Allowing, but fix \ Some(req.turn.parent),
the policy!", format!(
req, phase.duration "{:?} is impossible to fit into phase duration of {}",
); req, phase.duration
),
));
} else { } else {
return false; return false;
} }

View File

@ -61,6 +61,10 @@ pub struct Sim {
#[derivative(PartialEq = "ignore")] #[derivative(PartialEq = "ignore")]
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
check_for_gridlock: Option<(Time, Duration)>, check_for_gridlock: Option<(Time, Duration)>,
#[derivative(PartialEq = "ignore")]
#[serde(skip_serializing, skip_deserializing)]
alerts: AlertHandler,
} }
#[derive(Clone)] #[derive(Clone)]
@ -72,6 +76,23 @@ pub struct SimOptions {
pub recalc_lanechanging: bool, pub recalc_lanechanging: bool,
pub break_turn_conflict_cycles: bool, pub break_turn_conflict_cycles: bool,
pub enable_pandemic_model: Option<XorShiftRng>, 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 { impl SimOptions {
@ -84,6 +105,7 @@ impl SimOptions {
recalc_lanechanging: true, recalc_lanechanging: true,
break_turn_conflict_cycles: false, break_turn_conflict_cycles: false,
enable_pandemic_model: None, enable_pandemic_model: None,
alerts: AlertHandler::Print,
} }
} }
} }
@ -123,6 +145,7 @@ impl Sim {
step_count: 0, step_count: 0,
trip_positions: None, trip_positions: None,
check_for_gridlock: None, check_for_gridlock: None,
alerts: opts.alerts,
analytics: Analytics::new(), analytics: Analytics::new(),
} }
@ -586,10 +609,25 @@ impl Sim {
timer.start(format!("Advance sim to {}", end_time)); timer.start(format!("Advance sim to {}", end_time));
while self.time < end_time { while self.time < end_time {
if !self.analytics.alerts.is_empty() {
break;
}
self.minimal_step(map, end_time - self.time); 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) { if Duration::realtime_elapsed(last_update) >= Duration::seconds(1.0) {
// TODO Not timer? // TODO Not timer?
println!( println!(
@ -627,10 +665,25 @@ impl Sim {
let end_time = self.time + dt; let end_time = self.time + dt;
while self.time < end_time && Duration::realtime_elapsed(started_at) < real_time_limit { 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); 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 let Some((ref mut t, dt)) = self.check_for_gridlock {
if self.time >= *t { if self.time >= *t {
*t += dt; *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()) std::mem::replace(&mut self.analytics.alerts, Vec::new())
} }
} }

View File

@ -195,6 +195,7 @@ impl TripManager {
&self.people[trip.person.0], &self.people[trip.person.0],
map, map,
scheduler, scheduler,
&mut self.events,
) { ) {
self.unfinished_trips -= 1; self.unfinished_trips -= 1;
} }
@ -242,10 +243,13 @@ impl TripManager {
let path = if let Some(p) = map.pathfind(req.clone()) { let path = if let Some(p) = map.pathfind(req.clone()) {
p p
} else { } else {
println!( self.events.push(Event::Alert(
"Aborting {} at {} because no path for the car portion! {} to {}", None,
trip.id, now, start, end format!(
); "Aborting {} because no path for the car portion! {} to {}",
trip.id, start, end
),
));
// Move the car to the destination... // Move the car to the destination...
parking.remove_parked_car(parked_car.clone()); parking.remove_parked_car(parked_car.clone());
let trip = trip.id; let trip = trip.id;
@ -324,11 +328,14 @@ impl TripManager {
), ),
); );
} else { } else {
println!( self.events.push(Event::Alert(
"Aborting {} at {} because no path for the bike portion (or sidewalk connection \ None,
at the end)! {} to {}", format!(
trip.id, now, driving_pos, end "Aborting {} because no path for the bike portion (or sidewalk connection at \
); the end)! {} to {}",
trip.id, driving_pos, end
),
));
let trip = trip.id; let trip = trip.id;
self.abort_trip(now, trip, None, parking, scheduler, map); self.abort_trip(now, trip, None, parking, scheduler, map);
} }
@ -357,7 +364,14 @@ impl TripManager {
_ => unreachable!(), _ => 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; self.unfinished_trips -= 1;
} }
} }
@ -478,7 +492,14 @@ impl TripManager {
_ => unreachable!(), _ => 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; self.unfinished_trips -= 1;
} }
} }
@ -599,17 +620,23 @@ impl TripManager {
.map(|(_, spot, _)| spot) .map(|(_, spot, _)| spot)
}) })
{ {
println!( self.events.push(Event::Alert(
"{} had a trip aborted, and their car was warped to {:?}", None,
person, spot format!(
); "{} had a trip aborted, and their car was warped to {:?}",
person, spot
),
));
parking.reserve_spot(spot); parking.reserve_spot(spot);
parking.add_parked_car(ParkedCar { vehicle, spot }); parking.add_parked_car(ParkedCar { vehicle, spot });
} else { } else {
println!( self.events.push(Event::Alert(
"{} had a trip aborted, but nowhere to warp their car! Sucks.", None,
person format!(
); "{} had a trip aborted, but nowhere to warp their car! Sucks.",
person
),
));
} }
} }
} }
@ -798,10 +825,13 @@ impl TripManager {
return; return;
} }
let (trip, spec, maybe_req, maybe_path) = person.delayed_trips.remove(0); let (trip, spec, maybe_req, maybe_path) = person.delayed_trips.remove(0);
println!( self.events.push(Event::Alert(
"At {}, {} just freed up, so starting delayed trip {}", None,
now, person.id, trip format!(
); "{} just freed up, so starting delayed trip {}",
person.id, trip
),
));
self.start_trip( self.start_trip(
now, trip, spec, maybe_req, maybe_path, parking, scheduler, map, 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]; let person = &mut self.people[self.trips[trip.0].person.0];
if let PersonState::Trip(_) = person.state { if let PersonState::Trip(_) = person.state {
// Previous trip isn't done. Defer this one! // Previous trip isn't done. Defer this one!
println!( self.events.push(Event::Alert(
"At {}, {} is still doing a trip, so not starting {}", None,
now, person.id, trip format!(
); "{} is still doing a trip, so not starting {}",
person.id, trip
),
));
person person
.delayed_trips .delayed_trips
.push((trip, spec, maybe_req, maybe_path)); .push((trip, spec, maybe_req, maybe_path));
@ -864,19 +897,25 @@ impl TripManager {
), ),
); );
} else { } else {
println!( self.events.push(Event::Alert(
"VehicleAppearing trip couldn't find the first path (or no bike->sidewalk \ None,
connection at the end): {}", format!(
req "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); self.abort_trip(now, trip, Some(vehicle), parking, scheduler, map);
} }
} }
TripSpec::NoRoomToSpawn { i, use_vehicle, .. } => { TripSpec::NoRoomToSpawn { i, use_vehicle, .. } => {
println!( self.events.push(Event::Alert(
"{} couldn't spawn at border {}, just aborting", Some(i),
person.id, i format!(
); "{} couldn't spawn at border {}, just aborting",
person.id, i
),
));
let vehicle = person.get_vehicle(use_vehicle); let vehicle = person.get_vehicle(use_vehicle);
self.abort_trip(now, trip, Some(vehicle), parking, scheduler, map); self.abort_trip(now, trip, Some(vehicle), parking, scheduler, map);
} }
@ -912,7 +951,10 @@ impl TripManager {
}), }),
); );
} else { } 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 // Move the car to the destination
parking.remove_parked_car(parked_car.clone()); parking.remove_parked_car(parked_car.clone());
self.abort_trip( self.abort_trip(
@ -927,11 +969,14 @@ impl TripManager {
} else { } else {
// This should only happen when a driving trip has been aborted and there was // This should only happen when a driving trip has been aborted and there was
// absolutely no room to warp the car. // absolutely no room to warp the car.
println!( self.events.push(Event::Alert(
"At {}, {} should have {} parked somewhere, but it's unavailable, so \ None,
aborting {}", format!(
now, person.id, car, trip "{} should have {} parked somewhere, but it's unavailable, so \
); aborting {}",
person.id, car, trip
),
));
self.abort_trip(now, trip, None, parking, scheduler, map); self.abort_trip(now, trip, None, parking, scheduler, map);
} }
} }
@ -963,7 +1008,10 @@ impl TripManager {
}), }),
); );
} else { } 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); self.abort_trip(now, trip, None, parking, scheduler, map);
} }
} }
@ -997,7 +1045,10 @@ impl TripManager {
}), }),
); );
} else { } 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); self.abort_trip(now, trip, None, parking, scheduler, map);
} }
} }
@ -1030,7 +1081,10 @@ impl TripManager {
}), }),
); );
} else { } 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); self.abort_trip(now, trip, None, parking, scheduler, map);
} }
} }
@ -1061,6 +1115,7 @@ impl Trip {
person: &Person, person: &Person,
map: &Map, map: &Map,
scheduler: &mut Scheduler, scheduler: &mut Scheduler,
events: &mut Vec<Event>,
) -> bool { ) -> bool {
let walk_to = match self.legs[0] { let walk_to = match self.legs[0] {
TripLeg::Walk(ref to) => to.clone(), TripLeg::Walk(ref to) => to.clone(),
@ -1075,10 +1130,13 @@ impl Trip {
let path = if let Some(p) = map.pathfind(req.clone()) { let path = if let Some(p) = map.pathfind(req.clone()) {
p p
} else { } else {
println!( events.push(Event::Alert(
"Aborting {} at {} because no path for the walking portion! {:?} to {:?}", None,
self.id, now, start, walk_to format!(
); "Aborting {} because no path for the walking portion! {:?} to {:?}",
self.id, start, walk_to
),
));
return false; return false;
}; };