generalizing the old gridlock checker. removing unused savestate_every

support. reimpl it later using the callbacks if needed.
This commit is contained in:
Dustin Carlino 2020-06-09 14:29:35 -07:00
parent 2445bddeb9
commit 287d56efd9
14 changed files with 171 additions and 103 deletions

1
Cargo.lock generated
View File

@ -2852,6 +2852,7 @@ version = "0.1.0"
dependencies = [
"abstutil 0.1.0",
"derivative 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"geom 0.1.0",
"instant 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libm 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -7,10 +7,10 @@ use crate::render::{AgentCache, AgentColorScheme, DrawMap, DrawOptions, Renderab
use crate::sandbox::{GameplayMode, TutorialState};
use abstutil::{MeasureMemory, Timer};
use ezgui::{EventCtx, GfxCtx, Prerender};
use geom::{Bounds, Circle, Distance, Pt2D};
use map_model::{Map, Traversable};
use geom::{Bounds, Circle, Distance, Duration, Pt2D, Time};
use map_model::{IntersectionID, Map, Traversable};
use rand::seq::SliceRandom;
use sim::{Analytics, GetDrawAgents, Sim, SimFlags};
use sim::{Analytics, GetDrawAgents, Sim, SimCallback, SimFlags};
use std::collections::BTreeMap;
pub struct App {
@ -508,6 +508,7 @@ pub struct PerMap {
pub current_selection: Option<ID>,
pub current_flags: Flags,
pub last_warped_from: Option<(Pt2D, f64)>,
pub sim_cb: Option<Box<dyn SimCallback>>,
}
impl PerMap {
@ -528,6 +529,7 @@ impl PerMap {
current_selection: None,
current_flags: flags.clone(),
last_warped_from: None,
sim_cb: None,
}
}
@ -579,3 +581,21 @@ impl PerObjectActions {
ctx.normal_left_click()
}
}
pub struct FindDelayedIntersections {
pub halt_limit: Duration,
pub report_limit: Duration,
pub currently_delayed: Vec<(IntersectionID, Time)>,
}
impl SimCallback for FindDelayedIntersections {
fn run(&mut self, sim: &Sim, _: &Map) -> bool {
self.currently_delayed = sim.delayed_intersections(self.report_limit);
if let Some((_, t)) = self.currently_delayed.get(0) {
sim.time() - *t >= self.halt_limit
} else {
false
}
}
}

View File

@ -362,9 +362,14 @@ fn prebake(map: &Map, scenario: Scenario, time_limit: Option<Duration>, timer: &
let mut rng = SimFlags::for_test("prebaked").make_rng();
scenario.instantiate(&mut sim, &map, &mut rng, timer);
if let Some(dt) = time_limit {
sim.timed_step(&map, dt, timer);
sim.timed_step(&map, dt, &mut None, timer);
} else {
sim.timed_step(&map, sim.get_end_of_day() - Time::START_OF_DAY, timer);
sim.timed_step(
&map,
sim.get_end_of_day() - Time::START_OF_DAY,
&mut None,
timer,
);
}
abstutil::write_binary(

View File

@ -479,7 +479,9 @@ impl ContextualActions for Actions {
}
(ID::Car(c), "forcibly kill this car") => {
app.primary.sim.kill_stuck_car(c, &app.primary.map);
app.primary.sim.tiny_step(&app.primary.map);
app.primary
.sim
.tiny_step(&app.primary.map, &mut app.primary.sim_cb);
app.primary.current_selection = None;
Transition::Keep
}

View File

@ -621,9 +621,12 @@ fn make_previewer(i: IntersectionID, phase: usize) -> Box<dyn State> {
for idx in 0..phase {
step += signal.phases[idx].duration;
}
app.primary
.sim
.timed_step(&app.primary.map, step, &mut Timer::throwaway());
app.primary.sim.timed_step(
&app.primary.map,
step,
&mut app.primary.sim_cb,
&mut Timer::throwaway(),
);
spawn_agents_around(i, app);
}

View File

@ -290,7 +290,7 @@ impl State for AgentSpawner {
&mut rng,
&mut Timer::new("spawn trip"),
);
app.primary.sim.tiny_step(map);
app.primary.sim.tiny_step(map, &mut app.primary.sim_cb);
app.recalculate_current_selection(ctx);
return Transition::Pop;
}
@ -573,7 +573,7 @@ pub fn spawn_agents_around(i: IntersectionID, app: &mut App) {
}
sim.flush_spawner(spawner, map, &mut timer);
sim.tiny_step(map);
sim.tiny_step(map, &mut app.primary.sim_cb);
}
pub fn actions(_: &App, id: ID) -> Vec<(Key, String)> {

View File

@ -185,7 +185,9 @@ impl GameplayMode {
&mut app.primary.current_flags.sim_flags.make_rng(),
timer,
);
app.primary.sim.tiny_step(&app.primary.map);
app.primary
.sim
.tiny_step(&app.primary.map, &mut app.primary.sim_cb);
// Maybe we've already got prebaked data for this map+scenario.
if !app

View File

@ -752,7 +752,9 @@ impl TutorialState {
if let Some(ref cb) = self.stage().spawn {
(cb)(app);
app.primary.sim.tiny_step(&app.primary.map);
app.primary
.sim
.tiny_step(&app.primary.map, &mut app.primary.sim_cb);
}
let last_finished_task = if self.current.stage == 0 {
@ -1088,7 +1090,7 @@ impl TutorialState {
&mut rng,
&mut Timer::new("spawn trip"),
);
app.primary.sim.tiny_step(map);
app.primary.sim.tiny_step(map, &mut app.primary.sim_cb);
// And add some noise
spawn_agents_around(app.primary.map.find_i_by_osm_id(1709145066).unwrap(), app);

View File

@ -1,4 +1,4 @@
use crate::app::App;
use crate::app::{App, FindDelayedIntersections};
use crate::common::Warping;
use crate::game::{msg, State, Transition};
use crate::helpers::ID;
@ -187,7 +187,9 @@ impl SpeedControls {
"step forwards" => {
let dt = self.composite.persistent_split_value("step forwards");
if dt == Duration::seconds(0.1) {
app.primary.sim.tiny_step(&app.primary.map);
app.primary
.sim
.tiny_step(&app.primary.map, &mut app.primary.sim_cb);
app.recalculate_current_selection(ctx);
return Some(Transition::KeepWithMouseover);
}
@ -259,9 +261,12 @@ impl SpeedControls {
let dt = multiplier * real_dt;
// TODO This should match the update frequency in ezgui. Plumb along the deadline
// or frequency to here.
app.primary
.sim
.time_limited_step(&app.primary.map, dt, Duration::seconds(0.033));
app.primary.sim.time_limited_step(
&app.primary.map,
dt,
Duration::seconds(0.033),
&mut app.primary.sim_cb,
);
app.recalculate_current_selection(ctx);
}
}
@ -476,12 +481,20 @@ impl TimeWarpScreen {
ctx: &mut EventCtx,
app: &mut App,
target: Time,
traffic_jams: bool,
mut traffic_jams: bool,
) -> Box<dyn State> {
if traffic_jams {
app.primary
.sim
.set_gridlock_checker(Some(Duration::minutes(5)));
if app.primary.sim_cb.is_none() {
app.primary.sim_cb = Some(Box::new(FindDelayedIntersections {
halt_limit: Duration::minutes(5),
report_limit: Duration::minutes(5),
currently_delayed: Vec::new(),
}));
// TODO Can we get away with less frequently? Not sure about all the edge cases
app.primary.sim.set_periodic_callback(Duration::minutes(1));
} else {
traffic_jams = false;
}
}
Box::new(TimeWarpScreen {
@ -507,24 +520,12 @@ impl State for TimeWarpScreen {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if ctx.input.nonblocking_is_update_event().is_some() {
ctx.input.use_update_event();
if let Some(problems) = app.primary.sim.time_limited_step(
app.primary.sim.time_limited_step(
&app.primary.map,
self.target - app.primary.sim.time(),
Duration::seconds(0.033),
) {
let id = ID::Intersection(problems[0].0);
app.layer = Some(Box::new(crate::layer::traffic::Dynamic::traffic_jams(
ctx, app,
)));
return Transition::Replace(Warping::new(
ctx,
id.canonical_point(&app.primary).unwrap(),
Some(10.0),
Some(id),
&mut app.primary,
));
}
&mut app.primary.sim_cb,
);
for (t, maybe_i, alert) in app.primary.sim.clear_alerts() {
// TODO Just the first :(
return Transition::Replace(msg(
@ -532,6 +533,22 @@ impl State for TimeWarpScreen {
vec![format!("At {}, near {:?}, {}", t, maybe_i, alert)],
));
}
if let Some(ref mut cb) = app.primary.sim_cb {
let di = cb.downcast_mut::<FindDelayedIntersections>().unwrap();
if let Some((i, _)) = di.currently_delayed.get(0) {
let id = ID::Intersection(*i);
app.layer = Some(Box::new(crate::layer::traffic::Dynamic::traffic_jams(
ctx, app,
)));
return Transition::Replace(Warping::new(
ctx,
id.canonical_point(&app.primary).unwrap(),
Some(10.0),
Some(id),
&mut app.primary,
));
}
}
// I'm covered in shame for not doing this from the start.
let mut txt = Text::from(Line("Let's do the time warp again!").small_heading());
@ -578,7 +595,7 @@ impl State for TimeWarpScreen {
fn on_destroy(&mut self, _: &mut EventCtx, app: &mut App) {
if self.traffic_jams {
app.primary.sim.set_gridlock_checker(None);
app.primary.sim.unset_periodic_callback();
}
}
}

View File

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
abstutil = { path = "../abstutil" }
derivative = "2.1.1"
downcast-rs = "1.1.1"
geom = { path = "../geom" }
instant = "0.1.2"
libm = "0.2.1"

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, AlertHandler, Sim, SimOptions};
pub use self::sim::{AgentProperties, AlertHandler, Sim, SimCallback, SimOptions};
pub(crate) use self::transit::TransitSimState;
pub use self::trips::{Person, PersonState, TripResult};
pub use self::trips::{TripEndpoint, TripMode};

View File

@ -1,6 +1,5 @@
use crate::{AlertHandler, Scenario, Sim, SimOptions};
use abstutil::CmdArgs;
use geom::Duration;
use map_model::{Map, MapEdits};
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
@ -29,7 +28,6 @@ impl SimFlags {
run_name: args
.optional("--run_name")
.unwrap_or_else(|| "unnamed".to_string()),
savestate_every: args.optional_parse("--savestate_every", Duration::parse),
use_freeform_policy_everywhere: args.enabled("--freeform_policy"),
dont_block_the_box: !args.enabled("--disable_block_the_box"),
recalc_lanechanging: !args.enabled("--disable_recalc_lc"),

View File

@ -20,7 +20,7 @@ pub enum Command {
UpdateLaggyHead(CarID),
UpdatePed(PedestrianID),
UpdateIntersection(IntersectionID),
Savestate(Duration),
Callback(Duration),
Pandemic(pandemic::Cmd),
FinishRemoteTrip(TripID),
}
@ -42,7 +42,7 @@ impl Command {
Command::UpdateLaggyHead(id) => CommandType::CarLaggyHead(*id),
Command::UpdatePed(id) => CommandType::Ped(*id),
Command::UpdateIntersection(id) => CommandType::Intersection(*id),
Command::Savestate(_) => CommandType::Savestate,
Command::Callback(_) => CommandType::Callback,
Command::Pandemic(ref p) => CommandType::Pandemic(p.clone()),
Command::FinishRemoteTrip(t) => CommandType::FinishRemoteTrip(*t),
}
@ -58,7 +58,7 @@ pub enum CommandType {
CarLaggyHead(CarID),
Ped(PedestrianID),
Intersection(IntersectionID),
Savestate,
Callback,
Pandemic(pandemic::Cmd),
FinishRemoteTrip(TripID),
}

View File

@ -57,10 +57,6 @@ pub struct Sim {
#[serde(skip_serializing, skip_deserializing)]
analytics: Analytics,
#[derivative(PartialEq = "ignore")]
#[serde(skip_serializing, skip_deserializing)]
check_for_gridlock: Option<(Time, Duration)>,
#[derivative(PartialEq = "ignore")]
#[serde(skip_serializing, skip_deserializing)]
alerts: AlertHandler,
@ -69,7 +65,6 @@ pub struct Sim {
#[derive(Clone)]
pub struct SimOptions {
pub run_name: String,
pub savestate_every: Option<Duration>,
pub use_freeform_policy_everywhere: bool,
pub dont_block_the_box: bool,
pub recalc_lanechanging: bool,
@ -98,7 +93,6 @@ impl SimOptions {
pub fn new(run_name: &str) -> SimOptions {
SimOptions {
run_name: run_name.to_string(),
savestate_every: None,
use_freeform_policy_everywhere: false,
dont_block_the_box: true,
recalc_lanechanging: true,
@ -113,9 +107,6 @@ impl SimOptions {
impl Sim {
pub fn new(map: &Map, opts: SimOptions, timer: &mut Timer) -> Sim {
let mut scheduler = Scheduler::new();
if let Some(d) = opts.savestate_every {
scheduler.push(Time::START_OF_DAY + d, Command::Savestate(d));
}
Sim {
driving: DrivingSimState::new(map, opts.recalc_lanechanging),
parking: ParkingSimState::new(map, timer),
@ -143,7 +134,6 @@ impl Sim {
run_name: opts.run_name,
step_count: 0,
trip_positions: None,
check_for_gridlock: None,
alerts: opts.alerts,
analytics: Analytics::new(),
@ -385,47 +375,57 @@ impl GetDrawAgents for Sim {
// Running
impl Sim {
// Advances time as minimally as possible, also limited by max_dt.
fn minimal_step(&mut self, map: &Map, max_dt: Duration) {
// Advances time as minimally as possible, also limited by max_dt. Returns true if the callback
// said to halt the sim.
fn minimal_step(
&mut self,
map: &Map,
max_dt: Duration,
maybe_cb: &mut Option<Box<dyn SimCallback>>,
) -> bool {
self.step_count += 1;
let max_time = if let Some(t) = self.scheduler.peek_next_time() {
if t > self.time + max_dt {
// Next event is after when we want to stop.
self.time += max_dt;
return;
return false;
}
t
} else {
// No events left at all
self.time += max_dt;
return;
return false;
};
let mut savestate = false;
let mut halt = false;
while let Some(time) = self.scheduler.peek_next_time() {
if time > max_time {
return;
return false;
}
if let Some(cmd) = self.scheduler.get_next() {
if self.do_step(map, time, cmd) {
savestate = true;
if self.do_step(map, time, cmd, maybe_cb) {
halt = true;
break;
}
}
}
self.trip_positions = None;
if savestate {
self.save();
}
halt
}
// If true, savestate was requested.
fn do_step(&mut self, map: &Map, time: Time, cmd: Command) -> bool {
// If true, halt simulation because the callback said so.
fn do_step(
&mut self,
map: &Map,
time: Time,
cmd: Command,
maybe_cb: &mut Option<Box<dyn SimCallback>>,
) -> bool {
self.time = time;
let mut events = Vec::new();
let mut savestate = false;
let mut halt = false;
match cmd {
Command::StartTrip(id, trip_spec, maybe_req, maybe_path) => {
self.trips.start_trip(
@ -576,10 +576,12 @@ impl Sim {
self.intersections
.update_intersection(self.time, i, map, &mut self.scheduler);
}
Command::Savestate(frequency) => {
Command::Callback(frequency) => {
self.scheduler
.push(self.time + frequency, Command::Savestate(frequency));
savestate = true;
.push(self.time + frequency, Command::Callback(frequency));
if maybe_cb.as_mut().unwrap().run(self, map) {
halt = true;
}
}
Command::Pandemic(cmd) => {
self.pandemic
@ -601,7 +603,7 @@ impl Sim {
// Record events at precisely the time they occur.
self.dispatch_events(events, map);
savestate
halt
}
fn dispatch_events(&mut self, mut events: Vec<Event>, map: &Map) {
@ -620,14 +622,22 @@ impl Sim {
}
}
pub fn timed_step(&mut self, map: &Map, dt: Duration, timer: &mut Timer) {
pub fn timed_step(
&mut self,
map: &Map,
dt: Duration,
maybe_cb: &mut Option<Box<dyn SimCallback>>,
timer: &mut Timer,
) {
let end_time = self.time + dt;
let start = Instant::now();
let mut last_update = Instant::now();
timer.start(format!("Advance sim to {}", end_time));
while self.time < end_time {
self.minimal_step(map, end_time - self.time);
if self.minimal_step(map, end_time - self.time, maybe_cb) {
break;
}
if !self.analytics.alerts.is_empty() {
match self.alerts {
AlertHandler::Print => {
@ -658,32 +668,29 @@ impl Sim {
}
timer.stop(format!("Advance sim to {}", end_time));
}
pub fn tiny_step(&mut self, map: &Map) {
self.timed_step(map, Duration::seconds(0.1), &mut Timer::throwaway());
pub fn tiny_step(&mut self, map: &Map, maybe_cb: &mut Option<Box<dyn SimCallback>>) {
self.timed_step(
map,
Duration::seconds(0.1),
maybe_cb,
&mut Timer::throwaway(),
);
}
// TODO Do this like periodic savestating instead?
pub fn set_gridlock_checker(&mut self, freq: Option<Duration>) {
if let Some(dt) = freq {
assert!(self.check_for_gridlock.is_none());
self.check_for_gridlock = Some((self.time + dt, dt));
} else {
assert!(self.check_for_gridlock.is_some());
self.check_for_gridlock = None;
}
}
// This will return delayed intersections if that's why it stops early.
pub fn time_limited_step(
&mut self,
map: &Map,
dt: Duration,
real_time_limit: Duration,
) -> Option<Vec<(IntersectionID, Time)>> {
maybe_cb: &mut Option<Box<dyn SimCallback>>,
) {
let started_at = Instant::now();
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.minimal_step(map, end_time - self.time, maybe_cb) {
break;
}
if !self.analytics.alerts.is_empty() {
match self.alerts {
AlertHandler::Print => {
@ -702,18 +709,7 @@ impl Sim {
}
}
}
if let Some((ref mut t, dt)) = self.check_for_gridlock {
if self.time >= *t {
*t += dt;
let gridlock = self.delayed_intersections(dt);
if !gridlock.is_empty() {
return Some(gridlock);
}
}
}
}
None
}
pub fn dump_before_abort(&self) {
@ -745,7 +741,7 @@ impl Sim {
let dt = time_limit.unwrap_or_else(|| Duration::seconds(30.0));
match panic::catch_unwind(panic::AssertUnwindSafe(|| {
self.timed_step(map, dt, &mut Timer::throwaway());
self.timed_step(map, dt, &mut None, &mut Timer::throwaway());
})) {
Ok(()) => {}
Err(err) => {
@ -1197,6 +1193,27 @@ impl Sim {
}
}
// Callbacks
pub trait SimCallback: downcast_rs::Downcast {
// Run at some scheduled time. If this returns true, halt simulation.
fn run(&mut self, sim: &Sim, map: &Map) -> bool;
}
downcast_rs::impl_downcast!(SimCallback);
impl Sim {
// Only one at a time supported.
pub fn set_periodic_callback(&mut self, frequency: Duration) {
// TODO Round up time nicely?
self.scheduler
.push(self.time + frequency, Command::Callback(frequency));
}
pub fn unset_periodic_callback(&mut self) {
// Frequency doesn't matter
self.scheduler
.cancel(Command::Callback(Duration::seconds(1.0)));
}
}
pub struct AgentProperties {
// TODO Of this leg of the trip only!
pub total_time: Duration,