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 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()

View File

@ -274,27 +274,22 @@ impl SpeedControls {
if !alerts.is_empty() {
self.pause(ctx, app);
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
let id = ID::Intersection(alerts[0].1);
return Some(Transition::PushTwice(
msg(
"Alerts",
alerts
.into_iter()
.map(|(_, _, msg)| {
println!("Alert: {}", msg);
msg
})
.collect(),
),
popup,
Warping::new(
ctx,
id.canonical_point(&app.primary).unwrap(),
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.

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
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!",
events.push(Event::Alert(
Some(req.turn.parent),
format!(
"{:?} is impossible to fit into phase duration of {}",
req, phase.duration
);
),
));
} else {
return false;
}

View File

@ -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 {
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;
}
self.minimal_step(map, end_time - self.time);
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 {
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;
}
self.minimal_step(map, end_time - self.time);
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())
}
}

View File

@ -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!(
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!(
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): {}",
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!(
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 \
self.events.push(Event::Alert(
None,
format!(
"{} should have {} parked somewhere, but it's unavailable, so \
aborting {}",
now, person.id, car, trip
);
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;
};