From 067cc5b3072b23b2a69a8cfb5135c37efc681583 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Tue, 21 Apr 2020 12:17:01 -0700 Subject: [PATCH] plumb through alerts from the sim that pause the UI, use for gridlock --- game/src/game.rs | 1 - game/src/sandbox/speed.rs | 22 ++++++++++++++++++++++ sim/src/analytics.rs | 5 +++++ sim/src/events.rs | 2 ++ sim/src/mechanics/intersection.rs | 24 ++++++++++++++---------- sim/src/sim.rs | 10 ++++++++++ 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/game/src/game.rs b/game/src/game.rs index 16dada7ab8..48617bac4e 100644 --- a/game/src/game.rs +++ b/game/src/game.rs @@ -135,7 +135,6 @@ impl GUI for Game { .on_suspend(ctx, &mut self.app); self.states.push(s1); self.states.push(s2); - return EventLoopMode::InputOnly; } }; // Let the new state initialize with a fake event. Usually these just return diff --git a/game/src/sandbox/speed.rs b/game/src/sandbox/speed.rs index 9062f9dcf3..9918524c2f 100644 --- a/game/src/sandbox/speed.rs +++ b/game/src/sandbox/speed.rs @@ -269,6 +269,28 @@ impl SpeedControls { } } + // TODO Need to do this anywhere that steps the sim, like TimeWarpScreen. + let alerts = app.primary.sim.clear_alerts(); + 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)| msg).collect(), + ), + Warping::new( + ctx, + id.canonical_point(&app.primary).unwrap(), + Some(10.0), + None, + &mut app.primary, + ), + )); + } + None } diff --git a/sim/src/analytics.rs b/sim/src/analytics.rs index 81fbe134f3..37b65c7e26 100644 --- a/sim/src/analytics.rs +++ b/sim/src/analytics.rs @@ -26,6 +26,7 @@ pub struct Analytics { pub intersection_delays: BTreeMap>, // Per parking lane, when does a spot become filled (true) or free (false) parking_spot_changes: BTreeMap>, + pub(crate) alerts: Vec<(Time, 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 @@ -65,6 +66,7 @@ impl Analytics { trip_log: Vec::new(), intersection_delays: BTreeMap::new(), parking_spot_changes: BTreeMap::new(), + alerts: Vec::new(), record_anything: true, } } @@ -192,6 +194,9 @@ impl Analytics { Event::PathAmended(path) => { self.record_demand(&path, map); } + Event::Alert(i, msg) => { + self.alerts.push((time, i, msg)); + } _ => {} } } diff --git a/sim/src/events.rs b/sim/src/events.rs index 49faabc378..e168b6d402 100644 --- a/sim/src/events.rs +++ b/sim/src/events.rs @@ -46,6 +46,8 @@ pub enum Event { // Just use for parking replanning. Not happy about copying the full path in here, but the way // to plumb info into Analytics is Event. PathAmended(Path), + + Alert(IntersectionID, String), } #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)] diff --git a/sim/src/mechanics/intersection.rs b/sim/src/mechanics/intersection.rs index fe43578990..62f07ab9cf 100644 --- a/sim/src/mechanics/intersection.rs +++ b/sim/src/mechanics/intersection.rs @@ -219,8 +219,8 @@ impl IntersectionSimState { let allowed = if self.use_freeform_policy_everywhere { state.freeform_policy( &req, - now, map, + &mut self.events, if self.break_turn_conflict_cycles { Some(&mut self.blocked_by) } else { @@ -235,6 +235,7 @@ impl IntersectionSimState { now, map, scheduler, + &mut self.events, if self.break_turn_conflict_cycles { Some(&mut self.blocked_by) } else { @@ -248,6 +249,7 @@ impl IntersectionSimState { now, map, scheduler, + &mut self.events, if self.break_turn_conflict_cycles { Some(&mut self.blocked_by) } else { @@ -372,8 +374,8 @@ impl State { fn handle_accepted_conflicts( &self, req: &Request, - now: Time, map: &Map, + events: &mut Vec, mut maybe_blocked_by: Option<&mut Vec<(AgentID, AgentID)>>, ) -> bool { let turn = map.get_t(req.turn); @@ -393,10 +395,10 @@ impl State { while !queue.is_empty() { let current = queue.pop().unwrap(); if seen.contains(¤t) { - println!( - "!!! At {}, gridlock near {}. Allowing {:?}!", - now, req.turn.parent, req.agent - ); + events.push(Event::Alert( + req.turn.parent, + format!("Turn conflict cycle involving {:?}", seen), + )); return true; } seen.insert(current); @@ -416,12 +418,12 @@ impl State { fn freeform_policy( &self, req: &Request, - now: Time, map: &Map, + events: &mut Vec, maybe_blocked_by: Option<&mut Vec<(AgentID, AgentID)>>, ) -> bool { // Allow concurrent turns that don't conflict - self.handle_accepted_conflicts(req, now, map, maybe_blocked_by) + self.handle_accepted_conflicts(req, map, events, maybe_blocked_by) } fn stop_sign_policy( @@ -431,9 +433,10 @@ impl State { now: Time, map: &Map, scheduler: &mut Scheduler, + events: &mut Vec, maybe_blocked_by: Option<&mut Vec<(AgentID, AgentID)>>, ) -> bool { - if !self.handle_accepted_conflicts(req, now, map, maybe_blocked_by) { + if !self.handle_accepted_conflicts(req, map, events, maybe_blocked_by) { return false; } @@ -478,6 +481,7 @@ impl State { now: Time, map: &Map, scheduler: &mut Scheduler, + events: &mut Vec, maybe_blocked_by: Option<&mut Vec<(AgentID, AgentID)>>, ) -> bool { let turn = map.get_t(req.turn); @@ -496,7 +500,7 @@ impl State { } // Somebody might already be doing a Yield turn that conflicts with this one. - if !self.handle_accepted_conflicts(req, now, map, maybe_blocked_by) { + if !self.handle_accepted_conflicts(req, map, events, maybe_blocked_by) { return false; } diff --git a/sim/src/sim.rs b/sim/src/sim.rs index e47d56b18f..ac200bc09a 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -681,6 +681,9 @@ 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 Duration::realtime_elapsed(last_update) >= Duration::seconds(1.0) { // TODO Not timer? @@ -719,6 +722,9 @@ 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 let Some((ref mut t, dt)) = self.check_for_gridlock { if self.time >= *t { @@ -1218,6 +1224,10 @@ impl Sim { println!("{} has no trip?!", id); } } + + pub fn clear_alerts(&mut self) -> Vec<(Time, IntersectionID, String)> { + std::mem::replace(&mut self.analytics.alerts, Vec::new()) + } } pub struct AgentProperties {